Improve the strformat module (#17106)

* Improve the strformat module

Improve documentation
Use runnableExamples

* Fix overflow error for low(int64)

Add test

* Use assert in runnableExamples

* Improve tstrformat

Put tests in proc & test VM
Put tests in blocks
Add c, js targets

* Use doAssert in tests

* Disable JS & VM tests
This commit is contained in:
konsumlamm 2021-02-20 18:06:29 +01:00 • committed by GitHub
commit bd199a8d31
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 636 additions and 671 deletions

View file

@ -9,163 +9,148 @@
##[ ##[
String `interpolation`:idx: / `format`:idx: inspired by String `interpolation`:idx: / `format`:idx: inspired by
Python's ``f``-strings. Python's f-strings.
``fmt`` vs. ``&`` # `fmt` vs. `&`
=================
You can use either ``fmt`` or the unary ``&`` operator for formatting. The You can use either `fmt` or the unary `&` operator for formatting. The
difference between them is subtle but important. difference between them is subtle but important.
The ``fmt"{expr}"`` syntax is more aesthetically pleasing, but it hides a small The `fmt"{expr}"` syntax is more aesthetically pleasing, but it hides a small
gotcha. The string is a gotcha. The string is a
`generalized raw string literal <manual.html#lexical-analysis-generalized-raw-string-literals>`_. `generalized raw string literal <manual.html#lexical-analysis-generalized-raw-string-literals>`_.
This has some surprising effects: This has some surprising effects:
]##
.. code-block:: nim runnableExamples:
let msg = "hello"
assert fmt"{msg}\n" == "hello\\n"
import strformat ##[
let msg = "hello" Because the literal is a raw string literal, the `\n` is not interpreted as
doAssert fmt"{msg}\n" == "hello\\n"
Because the literal is a raw string literal, the ``\n`` is not interpreted as
an escape sequence. an escape sequence.
There are multiple ways to get around this, including the use of the ``&`` There are multiple ways to get around this, including the use of the `&` operator:
operator: ]##
.. code-block:: nim runnableExamples:
let msg = "hello"
import strformat assert &"{msg}\n" == "hello\n"
let msg = "hello"
doAssert &"{msg}\n" == "hello\n" assert fmt"{msg}{'\n'}" == "hello\n"
assert fmt("{msg}\n") == "hello\n"
doAssert fmt"{msg}{'\n'}" == "hello\n" assert "{msg}\n".fmt == "hello\n"
doAssert fmt("{msg}\n") == "hello\n"
doAssert "{msg}\n".fmt == "hello\n"
##[
The choice of style is up to you. The choice of style is up to you.
Formatting strings # Formatting strings
================== ]##
.. code-block:: nim runnableExamples:
assert &"""{"abc":>4}""" == " abc"
assert &"""{"abc":<4}""" == "abc "
import strformat ##[
# Formatting floats
]##
doAssert &"""{"abc":>4}""" == " abc" runnableExamples:
doAssert &"""{"abc":<4}""" == "abc " assert fmt"{-12345:08}" == "-0012345"
assert fmt"{-1:3}" == " -1"
assert fmt"{-1:03}" == "-01"
assert fmt"{16:#X}" == "0x10"
Formatting floats assert fmt"{123.456}" == "123.456"
================= assert fmt"{123.456:>9.3f}" == " 123.456"
assert fmt"{123.456:9.3f}" == " 123.456"
assert fmt"{123.456:9.4f}" == " 123.4560"
assert fmt"{123.456:>9.0f}" == " 123."
assert fmt"{123.456:<9.4f}" == "123.4560 "
.. code-block:: nim assert fmt"{123.456:e}" == "1.234560e+02"
assert fmt"{123.456:>13e}" == " 1.234560e+02"
assert fmt"{123.456:13e}" == " 1.234560e+02"
import strformat ##[
doAssert fmt"{-12345:08}" == "-0012345" # Debugging strings
doAssert fmt"{-1:3}" == " -1"
doAssert fmt"{-1:03}" == "-01"
doAssert fmt"{16:#X}" == "0x10"
doAssert fmt"{123.456}" == "123.456" `fmt"{expr=}"` expands to `fmt"expr={expr}"` namely the text of the expression,
doAssert fmt"{123.456:>9.3f}" == " 123.456"
doAssert fmt"{123.456:9.3f}" == " 123.456"
doAssert fmt"{123.456:9.4f}" == " 123.4560"
doAssert fmt"{123.456:>9.0f}" == " 123."
doAssert fmt"{123.456:<9.4f}" == "123.4560 "
doAssert fmt"{123.456:e}" == "1.234560e+02"
doAssert fmt"{123.456:>13e}" == " 1.234560e+02"
doAssert fmt"{123.456:13e}" == " 1.234560e+02"
Debugging strings
=================
``fmt"{expr=}"`` expands to ``fmt"expr={expr}"`` namely the text of the expression,
an equal sign and the results of evaluated expression. an equal sign and the results of evaluated expression.
]##
.. code-block:: nim runnableExamples:
assert fmt"{123.456=}" == "123.456=123.456"
assert fmt"{123.456=:>9.3f}" == "123.456= 123.456"
import strformat let x = "hello"
doAssert fmt"{123.456=}" == "123.456=123.456" assert fmt"{x=}" == "x=hello"
doAssert fmt"{123.456=:>9.3f}" == "123.456= 123.456" assert fmt"{x =}" == "x =hello"
let x = "hello" let y = 3.1415926
doAssert fmt"{x=}" == "x=hello" assert fmt"{y=:.2f}" == fmt"y={y:.2f}"
doAssert fmt"{x =}" == "x =hello" assert fmt"{y=}" == fmt"y={y}"
assert fmt"{y = : <8}" == fmt"y = 3.14159 "
let y = 3.1415926
doAssert fmt"{y=:.2f}" == fmt"y={y:.2f}"
doAssert fmt"{y=}" == fmt"y={y}"
doAssert fmt"{y = : <8}" == fmt"y = 3.14159 "
proc hello(a: string, b: float): int = 12
let a = "hello"
let b = 3.1415926
doAssert fmt"{hello(x, y) = }" == "hello(x, y) = 12"
doAssert fmt"{x.hello(y) = }" == "x.hello(y) = 12"
doAssert fmt"{hello x, y = }" == "hello x, y = 12"
proc hello(a: string, b: float): int = 12
assert fmt"{hello(x, y) = }" == "hello(x, y) = 12"
assert fmt"{x.hello(y) = }" == "x.hello(y) = 12"
assert fmt"{hello x, y = }" == "hello x, y = 12"
##[
Note that it is space sensitive: Note that it is space sensitive:
]##
.. code-block:: nim runnableExamples:
let x = "12"
assert fmt"{x=}" == "x=12"
assert fmt"{x =:}" == "x =12"
assert fmt"{x =}" == "x =12"
assert fmt"{x= :}" == "x= 12"
assert fmt"{x= }" == "x= 12"
assert fmt"{x = :}" == "x = 12"
assert fmt"{x = }" == "x = 12"
assert fmt"{x = :}" == "x = 12"
assert fmt"{x = }" == "x = 12"
import strformat ##[
let x = "12" # Implementation details
doAssert fmt"{x=}" == "x=12"
doAssert fmt"{x =:}" == "x =12"
doAssert fmt"{x =}" == "x =12"
doAssert fmt"{x= :}" == "x= 12"
doAssert fmt"{x= }" == "x= 12"
doAssert fmt"{x = :}" == "x = 12"
doAssert fmt"{x = }" == "x = 12"
doAssert fmt"{x = :}" == "x = 12"
doAssert fmt"{x = }" == "x = 12"
An expression like `&"{key} is {value:arg} {{z}}"` is transformed into:
Implementation details
======================
An expression like ``&"{key} is {value:arg} {{z}}"`` is transformed into:
.. code-block:: nim .. code-block:: nim
var temp = newStringOfCap(educatedCapGuess) var temp = newStringOfCap(educatedCapGuess)
temp.formatValue key, "" temp.formatValue(key, "")
temp.add " is " temp.add(" is ")
temp.formatValue value, arg temp.formatValue(value, arg)
temp.add " {z}" temp.add(" {z}")
temp temp
Parts of the string that are enclosed in the curly braces are interpreted Parts of the string that are enclosed in the curly braces are interpreted
as Nim code, to escape an ``{`` or ``}`` double it. as Nim code, to escape a `{` or `}`, double it.
``&`` delegates most of the work to an open overloaded set `&` delegates most of the work to an open overloaded set
of ``formatValue`` procs. The required signature for a type ``T`` that supports of `formatValue` procs. The required signature for a type `T` that supports
formatting is usually ``proc formatValue(result: var string; x: T; specifier: string)``. formatting is usually `proc formatValue(result: var string; x: T; specifier: string)`.
The subexpression after the colon The subexpression after the colon
(``arg`` in ``&"{key} is {value:arg} {{z}}"``) is optional. It will be passed as (`arg` in `&"{key} is {value:arg} {{z}}"`) is optional. It will be passed as
the last argument to ``formatValue``. When the colon with the subexpression it is the last argument to `formatValue`. When the colon with the subexpression it is
left out, an empty string will be taken instead. left out, an empty string will be taken instead.
For strings and numeric types the optional argument is a so-called For strings and numeric types the optional argument is a so-called
"standard format specifier". "standard format specifier".
# Standard format specifiers for strings, integers and floats
Standard format specifier for strings, integers and floats
==========================================================
The general form of a standard format specifier is:: The general form of a standard format specifier is::
[[fill]align][sign][#][0][minimumwidth][.precision][type] [[fill]align][sign][#][0][minimumwidth][.precision][type]
The square brackets ``[]`` indicate an optional element. The square brackets `[]` indicate an optional element.
The optional align flag can be one of the following: The optional 'align' flag can be one of the following:
'<' '<'
Forces the field to be left-aligned within the available Forces the field to be left-aligned within the available
@ -191,17 +176,17 @@ The 'sign' option is only valid for numeric types, and can be one of the followi
================= ==================================================== ================= ====================================================
Sign Meaning Sign Meaning
================= ==================================================== ================= ====================================================
``+`` Indicates that a sign should be used for both `+` Indicates that a sign should be used for both
positive as well as negative numbers. positive as well as negative numbers.
``-`` Indicates that a sign should be used only for `-` Indicates that a sign should be used only for
negative numbers (this is the default behavior). negative numbers (this is the default behavior).
(space) Indicates that a leading space should be used on (space) Indicates that a leading space should be used on
positive numbers. positive numbers.
================= ==================================================== ================= ====================================================
If the '#' character is present, integers use the 'alternate form' for formatting. If the '#' character is present, integers use the 'alternate form' for formatting.
This means that binary, octal, and hexadecimal output will be prefixed This means that binary, octal and hexadecimal output will be prefixed
with '0b', '0o', and '0x', respectively. with '0b', '0o' and '0x', respectively.
'width' is a decimal integer defining the minimum field width. If not specified, 'width' is a decimal integer defining the minimum field width. If not specified,
then the field width will be determined by the content. then the field width will be determined by the content.
@ -218,48 +203,44 @@ Finally, the 'type' determines how the data should be presented.
The available integer presentation types are: The available integer presentation types are:
================= ==================================================== ================= ====================================================
Type Result Type Result
================= ==================================================== ================= ====================================================
``b`` Binary. Outputs the number in base 2. `b` Binary. Outputs the number in base 2.
``d`` Decimal Integer. Outputs the number in base 10. `d` Decimal Integer. Outputs the number in base 10.
``o`` Octal format. Outputs the number in base 8. `o` Octal format. Outputs the number in base 8.
``x`` Hex format. Outputs the number in base 16, using `x` Hex format. Outputs the number in base 16, using
lower-case letters for the digits above 9. lower-case letters for the digits above 9.
``X`` Hex format. Outputs the number in base 16, using `X` Hex format. Outputs the number in base 16, using
uppercase letters for the digits above 9. uppercase letters for the digits above 9.
(None) the same as 'd' (None) The same as 'd'.
================= ==================================================== ================= ====================================================
The available floating point presentation types are: The available floating point presentation types are:
================= ==================================================== ================= ====================================================
Type Result Type Result
================= ==================================================== ================= ====================================================
``e`` Exponent notation. Prints the number in scientific `e` Exponent notation. Prints the number in scientific
notation using the letter 'e' to indicate the notation using the letter 'e' to indicate the
exponent. exponent.
``E`` Exponent notation. Same as 'e' except it converts `E` Exponent notation. Same as 'e' except it converts
the number to uppercase. the number to uppercase.
``f`` Fixed point. Displays the number as a fixed-point `f` Fixed point. Displays the number as a fixed-point
number. number.
``F`` Fixed point. Same as 'f' except it converts the `F` Fixed point. Same as 'f' except it converts the
number to uppercase. number to uppercase.
``g`` General format. This prints the number as a `g` General format. This prints the number as a
fixed-point number, unless the number is too fixed-point number, unless the number is too
large, in which case it switches to 'e' large, in which case it switches to 'e'
exponent notation. exponent notation.
``G`` General format. Same as 'g' except switches to 'E' `G` General format. Same as 'g' except it switches to 'E'
if the number gets to large. if the number gets to large.
(None) similar to 'g', except that it prints at least one (None) Similar to 'g', except that it prints at least one
digit after the decimal point. digit after the decimal point.
================= ==================================================== ================= ====================================================
# Limitations
Limitations
===========
Because of the well defined order how templates and macros are Because of the well defined order how templates and macros are
expanded, strformat cannot expand template arguments: expanded, strformat cannot expand template arguments:
@ -272,44 +253,40 @@ expanded, strformat cannot expand template arguments:
let x = "abc" let x = "abc"
myTemplate(x) myTemplate(x)
First the template ``myTemplate`` is expanded, where every identifier First the template `myTemplate` is expanded, where every identifier
``arg`` is substituted with its argument. The ``arg`` inside the `arg` is substituted with its argument. The `arg` inside the
format string is not seen by this process, because it is part of a format string is not seen by this process, because it is part of a
quoted string literal. It is not an identifier yet. Then the strformat quoted string literal. It is not an identifier yet. Then the strformat
macro creates the ``arg`` identifier from the string literal. An macro creates the `arg` identifier from the string literal, an
identifier that cannot be resolved anymore. identifier that cannot be resolved anymore.
The workaround for this is to bind the template argument to a new local variable. The workaround for this is to bind the template argument to a new local variable.
.. code-block:: nim .. code-block:: nim
template myTemplate(arg: untyped): untyped = template myTemplate(arg: untyped): untyped =
block: block:
let arg1 {.inject.} = arg let arg1 {.inject.} = arg
echo "arg is: ", arg1 echo "arg is: ", arg1
echo &"--- {arg1} ---" echo &"--- {arg1} ---"
The use of ``{.inject.}`` here is necessary again because of template The use of `{.inject.}` here is necessary again because of template
expansion order and hygienic templates. But since we generally want to expansion order and hygienic templates. But since we generally want to
keep the hygienicness of ``myTemplate``, and we do not want ``arg1`` keep the hygiene of `myTemplate`, and we do not want `arg1`
to be injected into the context where ``myTemplate`` is expanded, to be injected into the context where `myTemplate` is expanded,
everything is wrapped in a ``block``. everything is wrapped in a `block`.
# Future directions
Future directions A curly expression with commas in it like `{x, argA, argB}` could be
================= transformed to `formatValue(result, x, argA, argB)` in order to support
A curly expression with commas in it like ``{x, argA, argB}`` could be
transformed to ``formatValue(result, x, argA, argB)`` in order to support
formatters that do not need to parse a custom language within a custom formatters that do not need to parse a custom language within a custom
language but instead prefer to use Nim's existing syntax. This also language but instead prefer to use Nim's existing syntax. This would also
helps in readability since there is only so much you can cram into help with readability, since there is only so much you can cram into
single letter DSLs. single letter DSLs.
]## ]##
import macros, parseutils, unicode import std/[macros, parseutils, unicode]
import strutils except format import std/strutils except format
proc mkDigit(v: int, typ: char): string {.inline.} = proc mkDigit(v: int, typ: char): string {.inline.} =
assert(v < 26) assert(v < 26)
@ -318,10 +295,9 @@ proc mkDigit(v: int, typ: char): string {.inline.} =
else: else:
result = $chr(ord(if typ == 'x': 'a' else: 'A') + v - 10) result = $chr(ord(if typ == 'x': 'a' else: 'A') + v - 10)
proc alignString*(s: string, minimumWidth: int; align = '\0'; proc alignString*(s: string, minimumWidth: int; align = '\0'; fill = ' '): string =
fill = ' '): string = ## Aligns `s` using the `fill` char.
## Aligns ``s`` using ``fill`` char. ## This is only of interest if you want to write a custom `format` proc that
## This is only of interest if you want to write a custom ``format`` proc that
## should support the standard format specifiers. ## should support the standard format specifiers.
if minimumWidth == 0: if minimumWidth == 0:
result = s result = s
@ -343,28 +319,30 @@ type
fill*, align*: char ## Desired fill and alignment. fill*, align*: char ## Desired fill and alignment.
sign*: char ## Desired sign. sign*: char ## Desired sign.
alternateForm*: bool ## Whether to prefix binary, octal and hex numbers alternateForm*: bool ## Whether to prefix binary, octal and hex numbers
## with ``0b``, ``0o``, ``0x``. ## with `0b`, `0o`, `0x`.
padWithZero*: bool ## Whether to pad with zeros rather than spaces. padWithZero*: bool ## Whether to pad with zeros rather than spaces.
minimumWidth*, precision*: int ## Desired minimum width and precision. minimumWidth*, precision*: int ## Desired minimum width and precision.
typ*: char ## Type like 'f', 'g' or 'd'. typ*: char ## Type like 'f', 'g' or 'd'.
endPosition*: int ## End position in the format specifier after endPosition*: int ## End position in the format specifier after
## ``parseStandardFormatSpecifier`` returned. ## `parseStandardFormatSpecifier` returned.
proc formatInt(n: SomeNumber; radix: int; proc formatInt(n: SomeNumber; radix: int; spec: StandardFormatSpecifier): string =
spec: StandardFormatSpecifier): string = ## Converts `n` to a string. If `n` is `SomeFloat`, it casts to `int64`.
## Converts ``n`` to string. If ``n`` is `SomeFloat`, it casts to `int64`. ## Conversion is done using `radix`. If result's length is less than
## Conversion is done using ``radix``. If result's length is lesser than ## `minimumWidth`, it aligns result to the right or left (depending on `a`)
## ``minimumWidth``, it aligns result to the right or left (depending on ``a``) ## with the `fill` char.
## with ``fill`` char.
when n is SomeUnsignedInt: when n is SomeUnsignedInt:
var v = n.uint64 var v = n.uint64
let negative = false let negative = false
else: else:
var v = n.int64 let n = n.int64
let negative = v.int64 < 0 let negative = n < 0
if negative: var v =
# FIXME: overflow error for low(int64) if negative:
v = v * -1 # `uint64(-n)`, but accounts for `n == low(int64)`
uint64(not n) + 1
else:
uint64(n)
var xx = "" var xx = ""
if spec.alternateForm: if spec.alternateForm:
@ -417,9 +395,9 @@ proc parseStandardFormatSpecifier*(s: string; start = 0;
## ##
## [[fill]align][sign][#][0][minimumwidth][.precision][type] ## [[fill]align][sign][#][0][minimumwidth][.precision][type]
## ##
## This is only of interest if you want to write a custom ``format`` proc that ## This is only of interest if you want to write a custom `format` proc that
## should support the standard format specifiers. If ``ignoreUnknownSuffix`` is true, ## should support the standard format specifiers. If `ignoreUnknownSuffix` is true,
## an unknown suffix after the ``type`` field is not an error. ## an unknown suffix after the `type` field is not an error.
const alignChars = {'<', '>', '^'} const alignChars = {'<', '>', '^'}
result.fill = ' ' result.fill = ' '
result.align = '\0' result.align = '\0'
@ -441,7 +419,7 @@ proc parseStandardFormatSpecifier*(s: string; start = 0;
result.alternateForm = true result.alternateForm = true
inc i inc i
if i+1 < s.len and s[i] == '0' and s[i+1] in {'0'..'9'}: if i + 1 < s.len and s[i] == '0' and s[i+1] in {'0'..'9'}:
result.padWithZero = true result.padWithZero = true
inc i inc i
@ -463,10 +441,10 @@ proc parseStandardFormatSpecifier*(s: string; start = 0;
"invalid format string, cannot parse: " & s[i..^1]) "invalid format string, cannot parse: " & s[i..^1])
proc formatValue*[T: SomeInteger](result: var string; value: T; proc formatValue*[T: SomeInteger](result: var string; value: T;
specifier: string) = specifier: string) =
## Standard format implementation for ``SomeInteger``. It makes little ## Standard format implementation for `SomeInteger`. It makes little
## sense to call this directly, but it is required to exist ## sense to call this directly, but it is required to exist
## by the ``&`` macro. ## by the `&` macro.
if specifier.len == 0: if specifier.len == 0:
result.add $value result.add $value
return return
@ -484,9 +462,9 @@ proc formatValue*[T: SomeInteger](result: var string; value: T;
result.add formatInt(value, radix, spec) result.add formatInt(value, radix, spec)
proc formatValue*(result: var string; value: SomeFloat; specifier: string) = proc formatValue*(result: var string; value: SomeFloat; specifier: string) =
## Standard format implementation for ``SomeFloat``. It makes little ## Standard format implementation for `SomeFloat`. It makes little
## sense to call this directly, but it is required to exist ## sense to call this directly, but it is required to exist
## by the ``&`` macro. ## by the `&` macro.
if specifier.len == 0: if specifier.len == 0:
result.add $value result.add $value
return return
@ -541,9 +519,9 @@ proc formatValue*(result: var string; value: SomeFloat; specifier: string) =
result.add res result.add res
proc formatValue*(result: var string; value: string; specifier: string) = proc formatValue*(result: var string; value: string; specifier: string) =
## Standard format implementation for ``string``. It makes little ## Standard format implementation for `string`. It makes little
## sense to call this directly, but it is required to exist ## sense to call this directly, but it is required to exist
## by the ``&`` macro. ## by the `&` macro.
let spec = parseStandardFormatSpecifier(specifier) let spec = parseStandardFormatSpecifier(specifier)
var value = value var value = value
case spec.typ case spec.typ
@ -557,8 +535,7 @@ proc formatValue*(result: var string; value: string; specifier: string) =
setLen(value, runeOffset(value, spec.precision)) setLen(value, runeOffset(value, spec.precision))
result.add alignString(value, spec.minimumWidth, spec.align, spec.fill) result.add alignString(value, spec.minimumWidth, spec.align, spec.fill)
proc formatValue[T: not SomeInteger](result: var string; value: T; proc formatValue[T: not SomeInteger](result: var string; value: T; specifier: string) =
specifier: string) =
mixin `$` mixin `$`
formatValue(result, $value, specifier) formatValue(result, $value, specifier)
@ -647,16 +624,18 @@ proc strformatImpl(pattern: NimNode; openChar, closeChar: char): NimNode =
echo repr result echo repr result
macro `&`*(pattern: string): untyped = strformatImpl(pattern, '{', '}') macro `&`*(pattern: string): untyped = strformatImpl(pattern, '{', '}')
## For a specification of the ``&`` macro, see the module level documentation. ## For a specification of the `&` macro, see the module level documentation.
macro fmt*(pattern: string): untyped = strformatImpl(pattern, '{', '}') macro fmt*(pattern: string): untyped = strformatImpl(pattern, '{', '}')
## An alias for ``&``. ## An alias for `& <#&.m,string>`_.
macro fmt*(pattern: string; openChar, closeChar: char): untyped = macro fmt*(pattern: string; openChar, closeChar: char): untyped =
## Use ``openChar`` instead of '{' and ``closeChar`` instead of '}' ## The same as `fmt <#fmt.m,string>`_, but uses `openChar` instead of `'{'`
## and `closeChar` instead of `'}'`.
runnableExamples: runnableExamples:
let testInt = 123 let testInt = 123
doAssert "<testInt>".fmt('<', '>') == "123" assert "<testInt>".fmt('<', '>') == "123"
doAssert """(()"foo" & "bar"())""".fmt(')', '(') == "(foobar)" assert """(()"foo" & "bar"())""".fmt(')', '(') == "(foobar)"
doAssert """ ""{"123+123"}"" """.fmt('"', '"') == " \"{246}\" " assert """ ""{"123+123"}"" """.fmt('"', '"') == " \"{246}\" "
strformatImpl(pattern, openChar.intVal.char, closeChar.intVal.char) strformatImpl(pattern, openChar.intVal.char, closeChar.intVal.char)

View file

@ -1,515 +1,501 @@
discard """ # xxx: test js target
action: "run"
output: '''Received (name: "Foo", species: "Bar")'''
"""
# issue #7632
import genericstrformat import genericstrformat
import strutils, times import std/[strformat, strutils, times]
proc main() =
block: # issue #7632
doAssert works(5) == "formatted 5"
doAssert fails0(6) == "formatted 6"
doAssert fails(7) == "formatted 7"
doAssert fails2[0](8) == "formatted 8"
doAssert works(5) == "formatted 5" block: # other tests
doAssert fails0(6) == "formatted 6" type Obj = object
doAssert fails(7) == "formatted 7"
doAssert fails2[0](8) == "formatted 8"
# other tests proc `$`(o: Obj): string = "foobar"
import strformat # for custom types, formatValue needs to be overloaded.
template formatValue(result: var string; value: Obj; specifier: string) =
result.formatValue($value, specifier)
type Obj = object var o: Obj
doAssert fmt"{o}" == "foobar"
doAssert fmt"{o:10}" == "foobar "
proc `$`(o: Obj): string = "foobar" doAssert fmt"{o=}" == "o=foobar"
doAssert fmt"{o=:10}" == "o=foobar "
# for custom types, formatValue needs to be overloaded. block: # see issue #7933
template formatValue(result: var string; value: Obj; specifier: string) = var str = "abc"
result.formatValue($value, specifier) doAssert fmt">7.1 :: {str:>7.1}" == ">7.1 :: a"
doAssert fmt">7.2 :: {str:>7.2}" == ">7.2 :: ab"
doAssert fmt">7.3 :: {str:>7.3}" == ">7.3 :: abc"
doAssert fmt">7.9 :: {str:>7.9}" == ">7.9 :: abc"
doAssert fmt">7.0 :: {str:>7.0}" == ">7.0 :: "
doAssert fmt" 7.1 :: {str:7.1}" == " 7.1 :: a "
doAssert fmt" 7.2 :: {str:7.2}" == " 7.2 :: ab "
doAssert fmt" 7.3 :: {str:7.3}" == " 7.3 :: abc "
doAssert fmt" 7.9 :: {str:7.9}" == " 7.9 :: abc "
doAssert fmt" 7.0 :: {str:7.0}" == " 7.0 :: "
doAssert fmt"^7.1 :: {str:^7.1}" == "^7.1 :: a "
doAssert fmt"^7.2 :: {str:^7.2}" == "^7.2 :: ab "
doAssert fmt"^7.3 :: {str:^7.3}" == "^7.3 :: abc "
doAssert fmt"^7.9 :: {str:^7.9}" == "^7.9 :: abc "
doAssert fmt"^7.0 :: {str:^7.0}" == "^7.0 :: "
var o: Obj doAssert fmt">7.1 :: {str=:>7.1}" == ">7.1 :: str= a"
doAssert fmt"{o}" == "foobar" doAssert fmt">7.2 :: {str=:>7.2}" == ">7.2 :: str= ab"
doAssert fmt"{o:10}" == "foobar " doAssert fmt">7.3 :: {str=:>7.3}" == ">7.3 :: str= abc"
doAssert fmt">7.9 :: {str=:>7.9}" == ">7.9 :: str= abc"
doAssert fmt">7.0 :: {str=:>7.0}" == ">7.0 :: str= "
doAssert fmt" 7.1 :: {str=:7.1}" == " 7.1 :: str=a "
doAssert fmt" 7.2 :: {str=:7.2}" == " 7.2 :: str=ab "
doAssert fmt" 7.3 :: {str=:7.3}" == " 7.3 :: str=abc "
doAssert fmt" 7.9 :: {str=:7.9}" == " 7.9 :: str=abc "
doAssert fmt" 7.0 :: {str=:7.0}" == " 7.0 :: str= "
doAssert fmt"^7.1 :: {str=:^7.1}" == "^7.1 :: str= a "
doAssert fmt"^7.2 :: {str=:^7.2}" == "^7.2 :: str= ab "
doAssert fmt"^7.3 :: {str=:^7.3}" == "^7.3 :: str= abc "
doAssert fmt"^7.9 :: {str=:^7.9}" == "^7.9 :: str= abc "
doAssert fmt"^7.0 :: {str=:^7.0}" == "^7.0 :: str= "
str = "äöüe\u0309\u0319o\u0307\u0359"
doAssert fmt"^7.1 :: {str:^7.1}" == "^7.1 :: ä "
doAssert fmt"^7.2 :: {str:^7.2}" == "^7.2 :: äö "
doAssert fmt"^7.3 :: {str:^7.3}" == "^7.3 :: äöü "
doAssert fmt"^7.0 :: {str:^7.0}" == "^7.0 :: "
doAssert fmt"{o=}" == "o=foobar" doAssert fmt"^7.1 :: {str=:^7.1}" == "^7.1 :: str= ä "
doAssert fmt"{o=:10}" == "o=foobar " doAssert fmt"^7.2 :: {str=:^7.2}" == "^7.2 :: str= äö "
doAssert fmt"^7.3 :: {str=:^7.3}" == "^7.3 :: str= äöü "
doAssert fmt"^7.0 :: {str=:^7.0}" == "^7.0 :: str= "
# this is actually wrong, but the unicode module has no support for graphemes
doAssert fmt"^7.4 :: {str:^7.4}" == "^7.4 :: äöüe "
doAssert fmt"^7.9 :: {str:^7.9}" == "^7.9 :: äöüe\u0309\u0319o\u0307\u0359"
doAssert fmt"^7.4 :: {str=:^7.4}" == "^7.4 :: str= äöüe "
doAssert fmt"^7.9 :: {str=:^7.9}" == "^7.9 :: str=äöüe\u0309\u0319o\u0307\u0359"
# see issue #7933 block: # see issue #7932
var str = "abc" doAssert fmt"{15:08}" == "00000015" # int, works
doAssert fmt">7.1 :: {str:>7.1}" == ">7.1 :: a" doAssert fmt"{1.5:08}" == "000001.5" # float, works
doAssert fmt">7.2 :: {str:>7.2}" == ">7.2 :: ab" doAssert fmt"{1.5:0>8}" == "000001.5" # workaround using fill char works for positive floats
doAssert fmt">7.3 :: {str:>7.3}" == ">7.3 :: abc" doAssert fmt"{-1.5:0>8}" == "0000-1.5" # even that does not work for negative floats
doAssert fmt">7.9 :: {str:>7.9}" == ">7.9 :: abc" doAssert fmt"{-1.5:08}" == "-00001.5" # works
doAssert fmt">7.0 :: {str:>7.0}" == ">7.0 :: " doAssert fmt"{1.5:+08}" == "+00001.5" # works
doAssert fmt" 7.1 :: {str:7.1}" == " 7.1 :: a " doAssert fmt"{1.5: 08}" == " 00001.5" # works
doAssert fmt" 7.2 :: {str:7.2}" == " 7.2 :: ab "
doAssert fmt" 7.3 :: {str:7.3}" == " 7.3 :: abc "
doAssert fmt" 7.9 :: {str:7.9}" == " 7.9 :: abc "
doAssert fmt" 7.0 :: {str:7.0}" == " 7.0 :: "
doAssert fmt"^7.1 :: {str:^7.1}" == "^7.1 :: a "
doAssert fmt"^7.2 :: {str:^7.2}" == "^7.2 :: ab "
doAssert fmt"^7.3 :: {str:^7.3}" == "^7.3 :: abc "
doAssert fmt"^7.9 :: {str:^7.9}" == "^7.9 :: abc "
doAssert fmt"^7.0 :: {str:^7.0}" == "^7.0 :: "
doAssert fmt">7.1 :: {str=:>7.1}" == ">7.1 :: str= a" doAssert fmt"{15=:08}" == "15=00000015" # int, works
doAssert fmt">7.2 :: {str=:>7.2}" == ">7.2 :: str= ab" doAssert fmt"{1.5=:08}" == "1.5=000001.5" # float, works
doAssert fmt">7.3 :: {str=:>7.3}" == ">7.3 :: str= abc" doAssert fmt"{1.5=:0>8}" == "1.5=000001.5" # workaround using fill char works for positive floats
doAssert fmt">7.9 :: {str=:>7.9}" == ">7.9 :: str= abc" doAssert fmt"{-1.5=:0>8}" == "-1.5=0000-1.5" # even that does not work for negative floats
doAssert fmt">7.0 :: {str=:>7.0}" == ">7.0 :: str= " doAssert fmt"{-1.5=:08}" == "-1.5=-00001.5" # works
doAssert fmt" 7.1 :: {str=:7.1}" == " 7.1 :: str=a " doAssert fmt"{1.5=:+08}" == "1.5=+00001.5" # works
doAssert fmt" 7.2 :: {str=:7.2}" == " 7.2 :: str=ab " doAssert fmt"{1.5=: 08}" == "1.5= 00001.5" # works
doAssert fmt" 7.3 :: {str=:7.3}" == " 7.3 :: str=abc "
doAssert fmt" 7.9 :: {str=:7.9}" == " 7.9 :: str=abc "
doAssert fmt" 7.0 :: {str=:7.0}" == " 7.0 :: str= "
doAssert fmt"^7.1 :: {str=:^7.1}" == "^7.1 :: str= a "
doAssert fmt"^7.2 :: {str=:^7.2}" == "^7.2 :: str= ab "
doAssert fmt"^7.3 :: {str=:^7.3}" == "^7.3 :: str= abc "
doAssert fmt"^7.9 :: {str=:^7.9}" == "^7.9 :: str= abc "
doAssert fmt"^7.0 :: {str=:^7.0}" == "^7.0 :: str= "
str = "äöüe\u0309\u0319o\u0307\u0359"
doAssert fmt"^7.1 :: {str:^7.1}" == "^7.1 :: ä "
doAssert fmt"^7.2 :: {str:^7.2}" == "^7.2 :: äö "
doAssert fmt"^7.3 :: {str:^7.3}" == "^7.3 :: äöü "
doAssert fmt"^7.0 :: {str:^7.0}" == "^7.0 :: "
doAssert fmt"^7.1 :: {str=:^7.1}" == "^7.1 :: str= ä " block: # only add explicitly requested sign if value != -0.0 (neg zero)
doAssert fmt"^7.2 :: {str=:^7.2}" == "^7.2 :: str= äö " doAssert fmt"{-0.0:g}" == "-0"
doAssert fmt"^7.3 :: {str=:^7.3}" == "^7.3 :: str= äöü " doAssert fmt"{-0.0:+g}" == "-0"
doAssert fmt"^7.0 :: {str=:^7.0}" == "^7.0 :: str= " doAssert fmt"{-0.0: g}" == "-0"
# this is actually wrong, but the unicode module has no support for graphemes doAssert fmt"{0.0:g}" == "0"
doAssert fmt"^7.4 :: {str:^7.4}" == "^7.4 :: äöüe " doAssert fmt"{0.0:+g}" == "+0"
doAssert fmt"^7.9 :: {str:^7.9}" == "^7.9 :: äöüe\u0309\u0319o\u0307\u0359" doAssert fmt"{0.0: g}" == " 0"
doAssert fmt"^7.4 :: {str=:^7.4}" == "^7.4 :: str= äöüe " doAssert fmt"{-0.0=:g}" == "-0.0=-0"
doAssert fmt"^7.9 :: {str=:^7.9}" == "^7.9 :: str=äöüe\u0309\u0319o\u0307\u0359" doAssert fmt"{-0.0=:+g}" == "-0.0=-0"
doAssert fmt"{-0.0=: g}" == "-0.0=-0"
doAssert fmt"{0.0=:g}" == "0.0=0"
doAssert fmt"{0.0=:+g}" == "0.0=+0"
doAssert fmt"{0.0=: g}" == "0.0= 0"
# see issue #7932 block: # seq format
doAssert fmt"{15:08}" == "00000015" # int, works let data1 = [1'i64, 10000'i64, 10000000'i64]
doAssert fmt"{1.5:08}" == "000001.5" # float, works let data2 = [10000000'i64, 100'i64, 1'i64]
doAssert fmt"{1.5:0>8}" == "000001.5" # workaround using fill char works for positive floats
doAssert fmt"{-1.5:0>8}" == "0000-1.5" # even that does not work for negative floats
doAssert fmt"{-1.5:08}" == "-00001.5" # works
doAssert fmt"{1.5:+08}" == "+00001.5" # works
doAssert fmt"{1.5: 08}" == " 00001.5" # works
doAssert fmt"{15=:08}" == "15=00000015" # int, works proc formatValue(result: var string; value: (array|seq|openArray); specifier: string) =
doAssert fmt"{1.5=:08}" == "1.5=000001.5" # float, works result.add "["
doAssert fmt"{1.5=:0>8}" == "1.5=000001.5" # workaround using fill char works for positive floats for i, it in value:
doAssert fmt"{-1.5=:0>8}" == "-1.5=0000-1.5" # even that does not work for negative floats if i != 0:
doAssert fmt"{-1.5=:08}" == "-1.5=-00001.5" # works result.add ", "
doAssert fmt"{1.5=:+08}" == "1.5=+00001.5" # works result.formatValue(it, specifier)
doAssert fmt"{1.5=: 08}" == "1.5= 00001.5" # works result.add "]"
# only add explicitly requested sign if value != -0.0 (neg zero) doAssert fmt"data1: {data1:8} #" == "data1: [ 1, 10000, 10000000] #"
doAssert fmt"{-0.0:g}" == "-0" doAssert fmt"data2: {data2:8} =" == "data2: [10000000, 100, 1] ="
doAssert fmt"{-0.0:+g}" == "-0"
doAssert fmt"{-0.0: g}" == "-0"
doAssert fmt"{0.0:g}" == "0"
doAssert fmt"{0.0:+g}" == "+0"
doAssert fmt"{0.0: g}" == " 0"
doAssert fmt"{-0.0=:g}" == "-0.0=-0" doAssert fmt"data1: {data1=:8} #" == "data1: data1=[ 1, 10000, 10000000] #"
doAssert fmt"{-0.0=:+g}" == "-0.0=-0" doAssert fmt"data2: {data2=:8} =" == "data2: data2=[10000000, 100, 1] ="
doAssert fmt"{-0.0=: g}" == "-0.0=-0"
doAssert fmt"{0.0=:g}" == "0.0=0"
doAssert fmt"{0.0=:+g}" == "0.0=+0"
doAssert fmt"{0.0=: g}" == "0.0= 0"
# seq format block: # custom format Value
type
Vec2[T] = object
x,y: T
let data1 = [1'i64, 10000'i64, 10000000'i64] proc formatValue[T](result: var string; value: Vec2[T]; specifier: string) =
let data2 = [10000000'i64, 100'i64, 1'i64] result.add '['
result.formatValue value.x, specifier
proc formatValue(result: var string; value: (array|seq|openArray); specifier: string) =
result.add "["
for i, it in value:
if i != 0:
result.add ", " result.add ", "
result.formatValue(it, specifier) result.formatValue value.y, specifier
result.add "]" result.add "]"
doAssert fmt"data1: {data1:8} #" == "data1: [ 1, 10000, 10000000] #" let v1 = Vec2[float32](x:1.0, y: 2.0)
doAssert fmt"data2: {data2:8} =" == "data2: [10000000, 100, 1] =" let v2 = Vec2[int32](x:1, y: 1337)
doAssert fmt"v1: {v1:+08} v2: {v2:>4}" == "v1: [+0000001, +0000002] v2: [ 1, 1337]"
doAssert fmt"data1: {data1=:8} #" == "data1: data1=[ 1, 10000, 10000000] #" doAssert fmt"v1: {v1=:+08} v2: {v2=:>4}" == "v1: v1=[+0000001, +0000002] v2: v2=[ 1, 1337]"
doAssert fmt"data2: {data2=:8} =" == "data2: data2=[10000000, 100, 1] ="
block: # bug #11012
# custom format Value type
Animal = object
type name, species: string
Vec2[T] = object AnimalRef = ref Animal
x,y: T
proc print_object(animalAddr: AnimalRef): string =
proc formatValue[T](result: var string; value: Vec2[T]; specifier: string) = fmt"Received {animalAddr[]}"
result.add '['
result.formatValue value.x, specifier doAssert print_object(AnimalRef(name: "Foo", species: "Bar")) == """Received (name: "Foo", species: "Bar")"""
result.add ", "
result.formatValue value.y, specifier block: # bug #11723
result.add "]" let pos: Positive = 64
doAssert fmt"{pos:3}" == " 64"
let v1 = Vec2[float32](x:1.0, y: 2.0) doAssert fmt"{pos:3b}" == "1000000"
let v2 = Vec2[int32](x:1, y: 1337) doAssert fmt"{pos:3d}" == " 64"
doAssert fmt"v1: {v1:+08} v2: {v2:>4}" == "v1: [+0000001, +0000002] v2: [ 1, 1337]" doAssert fmt"{pos:3o}" == "100"
doAssert fmt"v1: {v1=:+08} v2: {v2=:>4}" == "v1: v1=[+0000001, +0000002] v2: v2=[ 1, 1337]" doAssert fmt"{pos:3x}" == " 40"
doAssert fmt"{pos:3X}" == " 40"
# bug #11012
doAssert fmt"{pos=:3}" == "pos= 64"
type doAssert fmt"{pos=:3b}" == "pos=1000000"
Animal = object doAssert fmt"{pos=:3d}" == "pos= 64"
name, species: string doAssert fmt"{pos=:3o}" == "pos=100"
AnimalRef = ref Animal doAssert fmt"{pos=:3x}" == "pos= 40"
doAssert fmt"{pos=:3X}" == "pos= 40"
proc print_object(animalAddr: AnimalRef) =
echo fmt"Received {animalAddr[]}" let nat: Natural = 64
doAssert fmt"{nat:3}" == " 64"
print_object(AnimalRef(name: "Foo", species: "Bar")) doAssert fmt"{nat:3b}" == "1000000"
doAssert fmt"{nat:3d}" == " 64"
# bug #11723 doAssert fmt"{nat:3o}" == "100"
doAssert fmt"{nat:3x}" == " 40"
let pos: Positive = 64 doAssert fmt"{nat:3X}" == " 40"
doAssert fmt"{pos:3}" == " 64"
doAssert fmt"{pos:3b}" == "1000000" doAssert fmt"{nat=:3}" == "nat= 64"
doAssert fmt"{pos:3d}" == " 64" doAssert fmt"{nat=:3b}" == "nat=1000000"
doAssert fmt"{pos:3o}" == "100" doAssert fmt"{nat=:3d}" == "nat= 64"
doAssert fmt"{pos:3x}" == " 40" doAssert fmt"{nat=:3o}" == "nat=100"
doAssert fmt"{pos:3X}" == " 40" doAssert fmt"{nat=:3x}" == "nat= 40"
doAssert fmt"{nat=:3X}" == "nat= 40"
doAssert fmt"{pos=:3}" == "pos= 64"
doAssert fmt"{pos=:3b}" == "pos=1000000" block: # bug #12612
doAssert fmt"{pos=:3d}" == "pos= 64" proc my_proc() =
doAssert fmt"{pos=:3o}" == "pos=100" const value = "value"
doAssert fmt"{pos=:3x}" == "pos= 40" const a = &"{value}"
doAssert fmt"{pos=:3X}" == "pos= 40" doAssert a == value
const b = &"{value=}"
let nat: Natural = 64 doAssert b == "value=" & value
doAssert fmt"{nat:3}" == " 64"
doAssert fmt"{nat:3b}" == "1000000" my_proc()
doAssert fmt"{nat:3d}" == " 64"
doAssert fmt"{nat:3o}" == "100" block:
doAssert fmt"{nat:3x}" == " 40" template fmt(pattern: string; openCloseChar: char): untyped =
doAssert fmt"{nat:3X}" == " 40" fmt(pattern, openCloseChar, openCloseChar)
doAssert fmt"{nat=:3}" == "nat= 64" let
doAssert fmt"{nat=:3b}" == "nat=1000000" testInt = 123
doAssert fmt"{nat=:3d}" == "nat= 64" testStr = "foobar"
doAssert fmt"{nat=:3o}" == "nat=100" testFlt = 3.141592
doAssert fmt"{nat=:3x}" == "nat= 40" doAssert ">><<".fmt('<', '>') == "><"
doAssert fmt"{nat=:3X}" == "nat= 40" doAssert " >> << ".fmt('<', '>') == " > < "
doAssert "<<>>".fmt('<', '>') == "<>"
# bug #12612 doAssert " << >> ".fmt('<', '>') == " < > "
proc my_proc = doAssert "''".fmt('\'') == "'"
const value = "value" doAssert "''''".fmt('\'') == "''"
const a = &"{value}" doAssert "'' ''".fmt('\'') == "' '"
doAssert a == value doAssert "<testInt>".fmt('<', '>') == "123"
doAssert "<testInt>".fmt('<', '>') == "123"
const b = &"{value=}" doAssert "'testFlt:1.2f'".fmt('\'') == "3.14"
doAssert b == "value=" & value doAssert "<testInt><testStr>".fmt('<', '>') == "123foobar"
doAssert """ ""{"123+123"}"" """.fmt('"') == " \"{246}\" "
my_proc() doAssert "(((testFlt:1.2f)))((111))".fmt('(', ')') == "(3.14)(111)"
doAssert """(()"foo" & "bar"())""".fmt(')', '(') == "(foobar)"
block: doAssert "{}abc`testStr' `testFlt:1.2f' `1+1' ``".fmt('`', '\'') == "{}abcfoobar 3.14 2 `"
template fmt(pattern: string; openCloseChar: char): untyped = doAssert """x = '"foo" & "bar"'
fmt(pattern, openCloseChar, openCloseChar) y = '123 + 111'
z = '3 in {2..7}'
let """.fmt('\'') ==
testInt = 123 """x = foobar
testStr = "foobar" y = 234
testFlt = 3.141592 z = true
doAssert ">><<".fmt('<', '>') == "><" """
doAssert " >> << ".fmt('<', '>') == " > < "
doAssert "<<>>".fmt('<', '>') == "<>" block: # tests from the very own strformat documentation!
doAssert " << >> ".fmt('<', '>') == " < > " let msg = "hello"
doAssert "''".fmt('\'') == "'" doAssert fmt"{msg}\n" == "hello\\n"
doAssert "''''".fmt('\'') == "''"
doAssert "'' ''".fmt('\'') == "' '" doAssert &"{msg}\n" == "hello\n"
doAssert "<testInt>".fmt('<', '>') == "123"
doAssert "<testInt>".fmt('<', '>') == "123" doAssert fmt"{msg}{'\n'}" == "hello\n"
doAssert "'testFlt:1.2f'".fmt('\'') == "3.14" doAssert fmt("{msg}\n") == "hello\n"
doAssert "<testInt><testStr>".fmt('<', '>') == "123foobar" doAssert "{msg}\n".fmt == "hello\n"
doAssert """ ""{"123+123"}"" """.fmt('"') == " \"{246}\" "
doAssert "(((testFlt:1.2f)))((111))".fmt('(', ')') == "(3.14)(111)" doAssert fmt"{msg=}\n" == "msg=hello\\n"
doAssert """(()"foo" & "bar"())""".fmt(')', '(') == "(foobar)"
doAssert "{}abc`testStr' `testFlt:1.2f' `1+1' ``".fmt('`', '\'') == "{}abcfoobar 3.14 2 `" doAssert &"{msg=}\n" == "msg=hello\n"
doAssert """x = '"foo" & "bar"'
y = '123 + 111' doAssert fmt"{msg=}{'\n'}" == "msg=hello\n"
z = '3 in {2..7}' doAssert fmt("{msg=}\n") == "msg=hello\n"
""".fmt('\'') == doAssert "{msg=}\n".fmt == "msg=hello\n"
"""x = foobar
y = 234 doAssert &"""{"abc":>4}""" == " abc"
z = true doAssert &"""{"abc":<4}""" == "abc "
"""
doAssert fmt"{-12345:08}" == "-0012345"
doAssert fmt"{-1:3}" == " -1"
# tests from the very own strformat documentation! doAssert fmt"{-1:03}" == "-01"
doAssert fmt"{16:#X}" == "0x10"
let msg = "hello"
doAssert fmt"{msg}\n" == "hello\\n" doAssert fmt"{123.456}" == "123.456"
doAssert fmt"{123.456:>9.3f}" == " 123.456"
doAssert &"{msg}\n" == "hello\n" doAssert fmt"{123.456:9.3f}" == " 123.456"
doAssert fmt"{123.456:9.4f}" == " 123.4560"
doAssert fmt"{msg}{'\n'}" == "hello\n" doAssert fmt"{123.456:>9.0f}" == " 123."
doAssert fmt("{msg}\n") == "hello\n" doAssert fmt"{123.456:<9.4f}" == "123.4560 "
doAssert "{msg}\n".fmt == "hello\n"
doAssert fmt"{123.456:e}" == "1.234560e+02"
doAssert fmt"{msg=}\n" == "msg=hello\\n" doAssert fmt"{123.456:>13e}" == " 1.234560e+02"
doAssert fmt"{123.456:13e}" == " 1.234560e+02"
doAssert &"{msg=}\n" == "msg=hello\n"
doAssert &"""{"abc"=:>4}""" == "\"abc\"= abc"
doAssert fmt"{msg=}{'\n'}" == "msg=hello\n" doAssert &"""{"abc"=:<4}""" == "\"abc\"=abc "
doAssert fmt("{msg=}\n") == "msg=hello\n"
doAssert "{msg=}\n".fmt == "msg=hello\n" doAssert fmt"{-12345=:08}" == "-12345=-0012345"
doAssert fmt"{-1=:3}" == "-1= -1"
doAssert &"""{"abc":>4}""" == " abc" doAssert fmt"{-1=:03}" == "-1=-01"
doAssert &"""{"abc":<4}""" == "abc " doAssert fmt"{16=:#X}" == "16=0x10"
doAssert fmt"{-12345:08}" == "-0012345" doAssert fmt"{123.456=}" == "123.456=123.456"
doAssert fmt"{-1:3}" == " -1" doAssert fmt"{123.456=:>9.3f}" == "123.456= 123.456"
doAssert fmt"{-1:03}" == "-01" doAssert fmt"{123.456=:9.3f}" == "123.456= 123.456"
doAssert fmt"{16:#X}" == "0x10" doAssert fmt"{123.456=:9.4f}" == "123.456= 123.4560"
doAssert fmt"{123.456=:>9.0f}" == "123.456= 123."
doAssert fmt"{123.456}" == "123.456" doAssert fmt"{123.456=:<9.4f}" == "123.456=123.4560 "
doAssert fmt"{123.456:>9.3f}" == " 123.456"
doAssert fmt"{123.456:9.3f}" == " 123.456" doAssert fmt"{123.456=:e}" == "123.456=1.234560e+02"
doAssert fmt"{123.456:9.4f}" == " 123.4560" doAssert fmt"{123.456=:>13e}" == "123.456= 1.234560e+02"
doAssert fmt"{123.456:>9.0f}" == " 123." doAssert fmt"{123.456=:13e}" == "123.456= 1.234560e+02"
doAssert fmt"{123.456:<9.4f}" == "123.4560 "
block: # tests for debug format string
doAssert fmt"{123.456:e}" == "1.234560e+02" var name = "hello"
doAssert fmt"{123.456:>13e}" == " 1.234560e+02" let age = 21
doAssert fmt"{123.456:13e}" == " 1.234560e+02" const hobby = "swim"
doAssert fmt"{age*9 + 16=}" == "age*9 + 16=205"
doAssert &"name: {name =}\nage: { age =: >7}\nhobby: { hobby= : 8}" ==
doAssert &"""{"abc"=:>4}""" == "\"abc\"= abc" "name: name =hello\nage: age = 21\nhobby: hobby= swim "
doAssert &"""{"abc"=:<4}""" == "\"abc\"=abc " doAssert fmt"{age == 12}" == "false"
doAssert fmt"{name.toUpperAscii() = }" == "name.toUpperAscii() = HELLO"
doAssert fmt"{-12345=:08}" == "-12345=-0012345" doAssert fmt"{name.toUpperAscii( ) = }" == "name.toUpperAscii( ) = HELLO"
doAssert fmt"{-1=:3}" == "-1= -1" doAssert fmt"{ toUpperAscii( s = name ) = }" == " toUpperAscii( s = name ) = HELLO"
doAssert fmt"{-1=:03}" == "-1=-01" doAssert fmt"{ strutils.toUpperAscii( s = name ) = }" == " strutils.toUpperAscii( s = name ) = HELLO"
doAssert fmt"{16=:#X}" == "16=0x10" doAssert fmt"{age==12}" == "false"
doAssert fmt"{age!= 12}" == "true"
doAssert fmt"{123.456=}" == "123.456=123.456" doAssert fmt"{age <= 12}" == "false"
doAssert fmt"{123.456=:>9.3f}" == "123.456= 123.456" for i in 1 .. 10:
doAssert fmt"{123.456=:9.3f}" == "123.456= 123.456" doAssert fmt"{age.float =: .2f}" == "age.float = 21.00"
doAssert fmt"{123.456=:9.4f}" == "123.456= 123.4560" doAssert fmt"{age.float() =:.3f}" == "age.float() =21.000"
doAssert fmt"{123.456=:>9.0f}" == "123.456= 123." doAssert fmt"{float age= :.3f}" == "float age= 21.000"
doAssert fmt"{123.456=:<9.4f}" == "123.456=123.4560 " doAssert fmt"{12 == int(`!=`(age, 12))}" == "false"
doAssert fmt"{0==1}" == "false"
doAssert fmt"{123.456=:e}" == "123.456=1.234560e+02"
doAssert fmt"{123.456=:>13e}" == "123.456= 1.234560e+02" block: # It is space sensitive.
doAssert fmt"{123.456=:13e}" == "123.456= 1.234560e+02" let x = "12"
doAssert fmt"{x=:}" == "x=12"
## tests for debug format string doAssert fmt"{x=}" == "x=12"
block: doAssert fmt"{x =:}" == "x =12"
var name = "hello" doAssert fmt"{x =}" == "x =12"
let age = 21 doAssert fmt"{x= :}" == "x= 12"
const hobby = "swim" doAssert fmt"{x= }" == "x= 12"
doAssert fmt"{age*9 + 16=}" == "age*9 + 16=205" doAssert fmt"{x = :}" == "x = 12"
doAssert &"name: {name =}\nage: { age =: >7}\nhobby: { hobby= : 8}" == doAssert fmt"{x = }" == "x = 12"
"name: name =hello\nage: age = 21\nhobby: hobby= swim " doAssert fmt"{x = :}" == "x = 12"
doAssert fmt"{age == 12}" == "false" doAssert fmt"{x = }" == "x = 12"
doAssert fmt"{name.toUpperAscii() = }" == "name.toUpperAscii() = HELLO"
doAssert fmt"{name.toUpperAscii( ) = }" == "name.toUpperAscii( ) = HELLO" block:
doAssert fmt"{ toUpperAscii( s = name ) = }" == " toUpperAscii( s = name ) = HELLO" let x = "hello"
doAssert fmt"{ strutils.toUpperAscii( s = name ) = }" == " strutils.toUpperAscii( s = name ) = HELLO" doAssert fmt"{x=}" == "x=hello"
doAssert fmt"{age==12}" == "false" doAssert fmt"{x =}" == "x =hello"
doAssert fmt"{age!= 12}" == "true"
doAssert fmt"{age <= 12}" == "false" let y = 3.1415926
for i in 1 .. 10: doAssert fmt"{y=:.2f}" == fmt"y={y:.2f}"
doAssert fmt"{age.float =: .2f}" == "age.float = 21.00" doAssert fmt"{y=}" == fmt"y={y}"
doAssert fmt"{age.float() =:.3f}" == "age.float() =21.000" doAssert fmt"{y = : <8}" == fmt"y = 3.14159 "
doAssert fmt"{float age= :.3f}" == "float age= 21.000"
doAssert fmt"{12 == int(`!=`(age, 12))}" == "false" proc hello(a: string, b: float): int = 12
doAssert fmt"{0==1}" == "false" template foo(a: string, b: float): int = 18
doAssert fmt"{hello(x, y)=}" == "hello(x, y)=12"
# It is space sensitive. doAssert fmt"{hello(x, y) =}" == "hello(x, y) =12"
block: doAssert fmt"{hello(x, y)= }" == "hello(x, y)= 12"
let x = "12" doAssert fmt"{hello(x, y) = }" == "hello(x, y) = 12"
doAssert fmt"{x=:}" == "x=12"
doAssert fmt"{x=}" == "x=12" doAssert fmt"{hello x, y=}" == "hello x, y=12"
doAssert fmt"{x =:}" == "x =12" doAssert fmt"{hello x, y =}" == "hello x, y =12"
doAssert fmt"{x =}" == "x =12" doAssert fmt"{hello x, y= }" == "hello x, y= 12"
doAssert fmt"{x= :}" == "x= 12" doAssert fmt"{hello x, y = }" == "hello x, y = 12"
doAssert fmt"{x= }" == "x= 12"
doAssert fmt"{x = :}" == "x = 12" doAssert fmt"{x.hello(y)=}" == "x.hello(y)=12"
doAssert fmt"{x = }" == "x = 12" doAssert fmt"{x.hello(y) =}" == "x.hello(y) =12"
doAssert fmt"{x = :}" == "x = 12" doAssert fmt"{x.hello(y)= }" == "x.hello(y)= 12"
doAssert fmt"{x = }" == "x = 12" doAssert fmt"{x.hello(y) = }" == "x.hello(y) = 12"
block: doAssert fmt"{foo(x, y)=}" == "foo(x, y)=18"
let x = "hello" doAssert fmt"{foo(x, y) =}" == "foo(x, y) =18"
doAssert fmt"{x=}" == "x=hello" doAssert fmt"{foo(x, y)= }" == "foo(x, y)= 18"
doAssert fmt"{x =}" == "x =hello" doAssert fmt"{foo(x, y) = }" == "foo(x, y) = 18"
doAssert fmt"{x.foo(y)=}" == "x.foo(y)=18"
let y = 3.1415926 doAssert fmt"{x.foo(y) =}" == "x.foo(y) =18"
doAssert fmt"{y=:.2f}" == fmt"y={y:.2f}" doAssert fmt"{x.foo(y)= }" == "x.foo(y)= 18"
doAssert fmt"{y=}" == fmt"y={y}" doAssert fmt"{x.foo(y) = }" == "x.foo(y) = 18"
doAssert fmt"{y = : <8}" == fmt"y = 3.14159 "
block:
proc hello(a: string, b: float): int = 12 template check(actual, expected: string) =
template foo(a: string, b: float): int = 18 doAssert actual == expected
doAssert fmt"{hello(x, y)=}" == "hello(x, y)=12" # Basic tests
doAssert fmt"{hello(x, y) =}" == "hello(x, y) =12" let s = "string"
doAssert fmt"{hello(x, y)= }" == "hello(x, y)= 12" check &"{0} {s}", "0 string"
doAssert fmt"{hello(x, y) = }" == "hello(x, y) = 12" check &"{s[0..2].toUpperAscii}", "STR"
check &"{-10:04}", "-010"
doAssert fmt"{hello x, y=}" == "hello x, y=12" check &"{-10:<04}", "-010"
doAssert fmt"{hello x, y =}" == "hello x, y =12" check &"{-10:>04}", "-010"
doAssert fmt"{hello x, y= }" == "hello x, y= 12" check &"0x{10:02X}", "0x0A"
doAssert fmt"{hello x, y = }" == "hello x, y = 12"
check &"{10:#04X}", "0x0A"
doAssert fmt"{x.hello(y)=}" == "x.hello(y)=12"
doAssert fmt"{x.hello(y) =}" == "x.hello(y) =12" check &"""{"test":#>5}""", "#test"
doAssert fmt"{x.hello(y)= }" == "x.hello(y)= 12" check &"""{"test":>5}""", " test"
doAssert fmt"{x.hello(y) = }" == "x.hello(y) = 12"
check &"""{"test":#^7}""", "#test##"
doAssert fmt"{foo(x, y)=}" == "foo(x, y)=18"
doAssert fmt"{foo(x, y) =}" == "foo(x, y) =18" check &"""{"test": <5}""", "test "
doAssert fmt"{foo(x, y)= }" == "foo(x, y)= 18" check &"""{"test":<5}""", "test "
doAssert fmt"{foo(x, y) = }" == "foo(x, y) = 18" check &"{1f:.3f}", "1.000"
check &"Hello, {s}!", "Hello, string!"
doAssert fmt"{x.foo(y)=}" == "x.foo(y)=18"
doAssert fmt"{x.foo(y) =}" == "x.foo(y) =18" # Tests for identifiers without parenthesis
doAssert fmt"{x.foo(y)= }" == "x.foo(y)= 18" check &"{s} works{s}", "string worksstring"
doAssert fmt"{x.foo(y) = }" == "x.foo(y) = 18" check &"{s:>7}", " string"
doAssert(not compiles(&"{s_works}")) # parsed as identifier `s_works`
block:
template check(actual, expected: string) = # Misc general tests
doAssert actual == expected check &"{{}}", "{}"
check &"{0}%", "0%"
# Basic tests check &"{0}%asdf", "0%asdf"
let s = "string" check &("\n{\"\\n\"}\n"), "\n\n\n"
check &"{0} {s}", "0 string" check &"""{"abc"}s""", "abcs"
check &"{s[0..2].toUpperAscii}", "STR"
check &"{-10:04}", "-010" # String tests
check &"{-10:<04}", "-010" check &"""{"abc"}""", "abc"
check &"{-10:>04}", "-010" check &"""{"abc":>4}""", " abc"
check &"0x{10:02X}", "0x0A" check &"""{"abc":<4}""", "abc "
check &"""{"":>4}""", " "
check &"{10:#04X}", "0x0A" check &"""{"":<4}""", " "
check &"""{"test":#>5}""", "#test" # Int tests
check &"""{"test":>5}""", " test" check &"{12345}", "12345"
check &"{ - 12345}", "-12345"
check &"""{"test":#^7}""", "#test##" check &"{12345:6}", " 12345"
check &"{12345:>6}", " 12345"
check &"""{"test": <5}""", "test " check &"{12345:4}", "12345"
check &"""{"test":<5}""", "test " check &"{12345:08}", "00012345"
check &"{1f:.3f}", "1.000" check &"{-12345:08}", "-0012345"
check &"Hello, {s}!", "Hello, string!" check &"{0:0}", "0"
check &"{0:02}", "00"
# Tests for identifiers without parenthesis check &"{-1:3}", " -1"
check &"{s} works{s}", "string worksstring" check &"{-1:03}", "-01"
check &"{s:>7}", " string" check &"{10}", "10"
doAssert(not compiles(&"{s_works}")) # parsed as identifier `s_works` check &"{16:#X}", "0x10"
check &"{16:^#7X}", " 0x10 "
# Misc general tests check &"{16:^+#7X}", " +0x10 "
check &"{{}}", "{}"
check &"{0}%", "0%" # Hex tests
check &"{0}%asdf", "0%asdf" check &"{0:x}", "0"
check &("\n{\"\\n\"}\n"), "\n\n\n" check &"{-0:x}", "0"
check &"""{"abc"}s""", "abcs" check &"{255:x}", "ff"
check &"{255:X}", "FF"
# String tests check &"{-255:x}", "-ff"
check &"""{"abc"}""", "abc" check &"{-255:X}", "-FF"
check &"""{"abc":>4}""", " abc" check &"{255:x} uNaffeCteD CaSe", "ff uNaffeCteD CaSe"
check &"""{"abc":<4}""", "abc " check &"{255:X} uNaffeCteD CaSe", "FF uNaffeCteD CaSe"
check &"""{"":>4}""", " " check &"{255:4x}", " ff"
check &"""{"":<4}""", " " check &"{255:04x}", "00ff"
check &"{-255:4x}", " -ff"
# Int tests check &"{-255:04x}", "-0ff"
check &"{12345}", "12345"
check &"{ - 12345}", "-12345" # Float tests
check &"{12345:6}", " 12345" check &"{123.456}", "123.456"
check &"{12345:>6}", " 12345" check &"{-123.456}", "-123.456"
check &"{12345:4}", "12345" check &"{123.456:.3f}", "123.456"
check &"{12345:08}", "00012345" check &"{123.456:+.3f}", "+123.456"
check &"{-12345:08}", "-0012345" check &"{-123.456:+.3f}", "-123.456"
check &"{0:0}", "0" check &"{-123.456:.3f}", "-123.456"
check &"{0:02}", "00" check &"{123.456:1g}", "123.456"
check &"{-1:3}", " -1" check &"{123.456:.1f}", "123.5"
check &"{-1:03}", "-01" check &"{123.456:.0f}", "123."
check &"{10}", "10" check &"{123.456:>9.3f}", " 123.456"
check &"{16:#X}", "0x10" check &"{123.456:9.3f}", " 123.456"
check &"{16:^#7X}", " 0x10 " check &"{123.456:>9.4f}", " 123.4560"
check &"{16:^+#7X}", " +0x10 " check &"{123.456:>9.0f}", " 123."
check &"{123.456:<9.4f}", "123.4560 "
# Hex tests
check &"{0:x}", "0" # Float (scientific) tests
check &"{-0:x}", "0" check &"{123.456:e}", "1.234560e+02"
check &"{255:x}", "ff" check &"{123.456:>13e}", " 1.234560e+02"
check &"{255:X}", "FF" check &"{123.456:<13e}", "1.234560e+02 "
check &"{-255:x}", "-ff" check &"{123.456:.1e}", "1.2e+02"
check &"{-255:X}", "-FF" check &"{123.456:.2e}", "1.23e+02"
check &"{255:x} uNaffeCteD CaSe", "ff uNaffeCteD CaSe" check &"{123.456:.3e}", "1.235e+02"
check &"{255:X} uNaffeCteD CaSe", "FF uNaffeCteD CaSe"
check &"{255:4x}", " ff" # Note: times.format adheres to the format protocol. Test that this
check &"{255:04x}", "00ff" # works:
check &"{-255:4x}", " -ff"
check &"{-255:04x}", "-0ff" var dt = initDateTime(01, mJan, 2000, 00, 00, 00)
check &"{dt:yyyy-MM-dd}", "2000-01-01"
# Float tests
check &"{123.456}", "123.456" var tm = fromUnix(0)
check &"{-123.456}", "-123.456" discard &"{tm}"
check &"{123.456:.3f}", "123.456"
check &"{123.456:+.3f}", "+123.456" var noww = now()
check &"{-123.456:+.3f}", "-123.456" check &"{noww}", $noww
check &"{-123.456:.3f}", "-123.456"
check &"{123.456:1g}", "123.456" # Unicode string tests
check &"{123.456:.1f}", "123.5" check &"""{"αβγ"}""", "αβγ"
check &"{123.456:.0f}", "123." check &"""{"αβγ":>5}""", " αβγ"
check &"{123.456:>9.3f}", " 123.456" check &"""{"αβγ":<5}""", "αβγ "
check &"{123.456:9.3f}", " 123.456" check &"""a{"a"}α{"α"}€{"€"}𐍈{"𐍈"}""", "aaαα€€𐍈𐍈"
check &"{123.456:>9.4f}", " 123.4560" check &"""a{"a":2}α{"α":2}€{"€":2}𐍈{"𐍈":2}""", "aa αα €€ 𐍈𐍈 "
check &"{123.456:>9.0f}", " 123." # Invalid unicode sequences should be handled as plain strings.
check &"{123.456:<9.4f}", "123.4560 " # Invalid examples taken from: https://stackoverflow.com/a/3886015/1804173
let invalidUtf8 = [
# Float (scientific) tests "\xc3\x28", "\xa0\xa1",
check &"{123.456:e}", "1.234560e+02" "\xe2\x28\xa1", "\xe2\x82\x28",
check &"{123.456:>13e}", " 1.234560e+02" "\xf0\x28\x8c\xbc", "\xf0\x90\x28\xbc", "\xf0\x28\x8c\x28"
check &"{123.456:<13e}", "1.234560e+02 " ]
check &"{123.456:.1e}", "1.2e+02" for s in invalidUtf8:
check &"{123.456:.2e}", "1.23e+02" check &"{s:>5}", repeat(" ", 5-s.len) & s
check &"{123.456:.3e}", "1.235e+02"
# bug #11089
# Note: times.format adheres to the format protocol. Test that this let flfoo: float = 1.0
# works: check &"{flfoo}", "1.0"
var dt = initDateTime(01, mJan, 2000, 00, 00, 00) # bug #11092
check &"{dt:yyyy-MM-dd}", "2000-01-01" check &"{high(int64)}", "9223372036854775807"
check &"{low(int64)}", "-9223372036854775808"
var tm = fromUnix(0)
discard &"{tm}" doAssert fmt"{'a'} {'b'}" == "a b"
var noww = now() block: # test low(int64)
check &"{noww}", $noww doAssert &"{low(int64):-}" == "-9223372036854775808"
# Unicode string tests # xxx static: main()
check &"""{"αβγ"}""", "αβγ" main()
check &"""{"αβγ":>5}""", " αβγ"
check &"""{"αβγ":<5}""", "αβγ "
check &"""a{"a"}α{"α"}€{"€"}𐍈{"𐍈"}""", "aaαα€€𐍈𐍈"
check &"""a{"a":2}α{"α":2}€{"€":2}𐍈{"𐍈":2}""", "aa αα €€ 𐍈𐍈 "
# Invalid unicode sequences should be handled as plain strings.
# Invalid examples taken from: https://stackoverflow.com/a/3886015/1804173
let invalidUtf8 = [
"\xc3\x28", "\xa0\xa1",
"\xe2\x28\xa1", "\xe2\x82\x28",
"\xf0\x28\x8c\xbc", "\xf0\x90\x28\xbc", "\xf0\x28\x8c\x28"
]
for s in invalidUtf8:
check &"{s:>5}", repeat(" ", 5-s.len) & s
# bug #11089
let flfoo: float = 1.0
check &"{flfoo}", "1.0"
# bug #11092
check &"{high(int64)}", "9223372036854775807"
check &"{low(int64)}", "-9223372036854775808"
doAssert fmt"{'a'} {'b'}" == "a b"