summary refs log tree commit diff stats
path: root/lib/pure/strformat.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure/strformat.nim')
-rw-r--r--lib/pure/strformat.nim158
1 files changed, 112 insertions, 46 deletions
diff --git a/lib/pure/strformat.nim b/lib/pure/strformat.nim
index 3fedff07b..7d093ebb3 100644
--- a/lib/pure/strformat.nim
+++ b/lib/pure/strformat.nim
@@ -133,13 +133,14 @@ runnableExamples:
 
 An expression like `&"{key} is {value:arg} {{z}}"` is transformed into:
 
-.. code-block:: nim
+  ```nim
   var temp = newStringOfCap(educatedCapGuess)
   temp.formatValue(key, "")
   temp.add(" is ")
   temp.formatValue(value, arg)
   temp.add(" {z}")
   temp
+  ```
 
 Parts of the string that are enclosed in the curly braces are interpreted
 as Nim code. To escape a `{` or `}`, double it.
@@ -263,6 +264,12 @@ The available floating point presentation types are:
                          exponent notation.
 `G`                      General format. Same as `g` except it switches to `E`
                          if the number gets to large.
+`i`                      Complex General format. This is only supported for
+                         complex numbers, which it prints using the mathematical
+                         (RE+IMj) format. The real and imaginary parts are printed
+                         using the general format `g` by default, but it is
+                         possible to combine this format with one of the other
+                         formats (e.g `jf`).
 (None)                   Similar to `g`, except that it prints at least one
                          digit after the decimal point.
 =================        ====================================================
@@ -272,13 +279,14 @@ The available floating point presentation types are:
 Because of the well defined order how templates and macros are
 expanded, strformat cannot expand template arguments:
 
-.. code-block:: nim
+  ```nim
   template myTemplate(arg: untyped): untyped =
     echo "arg is: ", arg
     echo &"--- {arg} ---"
 
   let x = "abc"
   myTemplate(x)
+  ```
 
 First the template `myTemplate` is expanded, where every identifier
 `arg` is substituted with its argument. The `arg` inside the
@@ -289,12 +297,13 @@ identifier that cannot be resolved anymore.
 
 The workaround for this is to bind the template argument to a new local variable.
 
-.. code-block:: nim
+  ```nim
   template myTemplate(arg: untyped): untyped =
     block:
       let arg1 {.inject.} = arg
       echo "arg is: ", arg1
       echo &"--- {arg1} ---"
+  ```
 
 The use of `{.inject.}` here is necessary again because of template
 expansion order and hygienic templates. But since we generally want to
@@ -313,8 +322,8 @@ help with readability, since there is only so much you can cram into
 single letter DSLs.
 ]##
 
-import macros, parseutils, unicode
-import strutils except format
+import std/[macros, parseutils, unicode]
+import std/strutils except format
 
 when defined(nimPreviewSlimSystem):
   import std/assertions
@@ -472,50 +481,48 @@ proc parseStandardFormatSpecifier*(s: string; start = 0;
     raise newException(ValueError,
       "invalid format string, cannot parse: " & s[i..^1])
 
+proc toRadix(typ: char): int =
+  case typ
+  of 'x', 'X': 16
+  of 'd', '\0': 10
+  of 'o': 8
+  of 'b': 2
+  else:
+    raise newException(ValueError,
+      "invalid type in format string for number, expected one " &
+      " of 'x', 'X', 'b', 'd', 'o' but got: " & typ)
+
 proc formatValue*[T: SomeInteger](result: var string; value: T;
-                                  specifier: string) =
+                                  specifier: static string) =
   ## Standard format implementation for `SomeInteger`. It makes little
   ## sense to call this directly, but it is required to exist
   ## by the `&` macro.
-  if specifier.len == 0:
+  when specifier.len == 0:
     result.add $value
-    return
-  let spec = parseStandardFormatSpecifier(specifier)
-  var radix = 10
-  case spec.typ
-  of 'x', 'X': radix = 16
-  of 'd', '\0': discard
-  of 'b': radix = 2
-  of 'o': radix = 8
   else:
-    raise newException(ValueError,
-      "invalid type in format string for number, expected one " &
-      " of 'x', 'X', 'b', 'd', 'o' but got: " & spec.typ)
-  result.add formatInt(value, radix, spec)
+    const
+      spec = parseStandardFormatSpecifier(specifier)
+      radix = toRadix(spec.typ)
 
-proc formatValue*(result: var string; value: SomeFloat; specifier: string) =
-  ## Standard format implementation for `SomeFloat`. It makes little
+    result.add formatInt(value, radix, spec)
+
+proc formatValue*[T: SomeInteger](result: var string; value: T;
+                                  specifier: string) =
+  ## Standard format implementation for `SomeInteger`. It makes little
   ## sense to call this directly, but it is required to exist
   ## by the `&` macro.
   if specifier.len == 0:
     result.add $value
-    return
-  let spec = parseStandardFormatSpecifier(specifier)
-
-  var fmode = ffDefault
-  case spec.typ
-  of 'e', 'E':
-    fmode = ffScientific
-  of 'f', 'F':
-    fmode = ffDecimal
-  of 'g', 'G':
-    fmode = ffDefault
-  of '\0': discard
   else:
-    raise newException(ValueError,
-      "invalid type in format string for number, expected one " &
-      " of 'e', 'E', 'f', 'F', 'g', 'G' but got: " & spec.typ)
+    let
+      spec = parseStandardFormatSpecifier(specifier)
+      radix = toRadix(spec.typ)
+
+    result.add formatInt(value, radix, spec)
 
+proc formatFloat(
+    result: var string, value: SomeFloat, fmode: FloatFormatMode,
+    spec: StandardFormatSpecifier) =
   var f = formatBiggestFloat(value, fmode, spec.precision)
   var sign = false
   if value >= 0.0:
@@ -550,23 +557,83 @@ proc formatValue*(result: var string; value: SomeFloat; specifier: string) =
   else:
     result.add res
 
+proc toFloatFormatMode(typ: char): FloatFormatMode =
+  case typ
+  of 'e', 'E': ffScientific
+  of 'f', 'F': ffDecimal
+  of 'g', 'G': ffDefault
+  of '\0': ffDefault
+  else:
+    raise newException(ValueError,
+      "invalid type in format string for number, expected one " &
+      " of 'e', 'E', 'f', 'F', 'g', 'G' but got: " & typ)
+
+proc formatValue*(result: var string; value: SomeFloat; specifier: static string) =
+  ## Standard format implementation for `SomeFloat`. It makes little
+  ## sense to call this directly, but it is required to exist
+  ## by the `&` macro.
+  when specifier.len == 0:
+    result.add $value
+  else:
+    const
+      spec = parseStandardFormatSpecifier(specifier)
+      fmode = toFloatFormatMode(spec.typ)
+
+    formatFloat(result, value, fmode, spec)
+
+proc formatValue*(result: var string; value: SomeFloat; specifier: string) =
+  ## Standard format implementation for `SomeFloat`. It makes little
+  ## sense to call this directly, but it is required to exist
+  ## by the `&` macro.
+  if specifier.len == 0:
+    result.add $value
+  else:
+    let
+      spec = parseStandardFormatSpecifier(specifier)
+      fmode = toFloatFormatMode(spec.typ)
+
+    formatFloat(result, value, fmode, spec)
+
+proc formatValue*(result: var string; value: string; specifier: static string) =
+  ## Standard format implementation for `string`. It makes little
+  ## sense to call this directly, but it is required to exist
+  ## by the `&` macro.
+  const spec = parseStandardFormatSpecifier(specifier)
+  var value =
+    when spec.typ in {'s', '\0'}: value
+    else: static:
+      raise newException(ValueError,
+        "invalid type in format string for string, expected 's', but got " &
+        spec.typ)
+  when spec.precision != -1:
+    if spec.precision < runeLen(value):
+      const precision = cast[Natural](spec.precision)
+      setLen(value, Natural(runeOffset(value, precision)))
+
+  result.add alignString(value, spec.minimumWidth, spec.align, spec.fill)
+
 proc formatValue*(result: var string; value: string; specifier: string) =
   ## Standard format implementation for `string`. It makes little
   ## sense to call this directly, but it is required to exist
   ## by the `&` macro.
   let spec = parseStandardFormatSpecifier(specifier)
-  var value = value
-  case spec.typ
-  of 's', '\0': discard
-  else:
-    raise newException(ValueError,
-      "invalid type in format string for string, expected 's', but got " &
-      spec.typ)
+  var value =
+    if spec.typ in {'s', '\0'}: value
+    else:
+      raise newException(ValueError,
+        "invalid type in format string for string, expected 's', but got " &
+        spec.typ)
   if spec.precision != -1:
     if spec.precision < runeLen(value):
-      setLen(value, runeOffset(value, spec.precision))
+      let precision = cast[Natural](spec.precision)
+      setLen(value, Natural(runeOffset(value, precision)))
+
   result.add alignString(value, spec.minimumWidth, spec.align, spec.fill)
 
+proc formatValue[T: not SomeInteger](result: var string; value: T; specifier: static string) =
+  mixin `$`
+  formatValue(result, $value, specifier)
+
 proc formatValue[T: not SomeInteger](result: var string; value: T; specifier: string) =
   mixin `$`
   formatValue(result, $value, specifier)
@@ -663,8 +730,7 @@ proc strformatImpl(f: string; openChar, closeChar: char,
         strlit.add closeChar
         inc i, 2
       else:
-        doAssert false, "invalid format string: '$1' instead of '$1$1'" % $closeChar
-        inc i
+        raiseAssert "invalid format string: '$1' instead of '$1$1'" % $closeChar
     else:
       strlit.add f[i]
       inc i