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:
import strformat
let msg = "hello" let msg = "hello"
doAssert fmt"{msg}\n" == "hello\\n" assert fmt"{msg}\n" == "hello\\n"
Because the literal is a raw string literal, the ``\n`` is not interpreted as ##[
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:
import strformat
let msg = "hello" let msg = "hello"
doAssert &"{msg}\n" == "hello\n" assert &"{msg}\n" == "hello\n"
doAssert fmt"{msg}{'\n'}" == "hello\n" assert fmt"{msg}{'\n'}" == "hello\n"
doAssert fmt("{msg}\n") == "hello\n" assert fmt("{msg}\n") == "hello\n"
doAssert "{msg}\n".fmt == "hello\n" assert "{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"
import strformat assert fmt"{123.456=:>9.3f}" == "123.456= 123.456"
doAssert fmt"{123.456=}" == "123.456=123.456"
doAssert fmt"{123.456=:>9.3f}" == "123.456= 123.456"
let x = "hello" let x = "hello"
doAssert fmt"{x=}" == "x=hello" assert fmt"{x=}" == "x=hello"
doAssert fmt"{x =}" == "x =hello" assert fmt"{x =}" == "x =hello"
let y = 3.1415926 let y = 3.1415926
doAssert fmt"{y=:.2f}" == fmt"y={y:.2f}" assert fmt"{y=:.2f}" == fmt"y={y:.2f}"
doAssert fmt"{y=}" == fmt"y={y}" assert fmt"{y=}" == fmt"y={y}"
doAssert fmt"{y = : <8}" == fmt"y = 3.14159 " assert fmt"{y = : <8}" == fmt"y = 3.14159 "
proc hello(a: string, b: float): int = 12 proc hello(a: string, b: float): int = 12
let a = "hello" assert fmt"{hello(x, y) = }" == "hello(x, y) = 12"
let b = 3.1415926 assert fmt"{x.hello(y) = }" == "x.hello(y) = 12"
doAssert fmt"{hello(x, y) = }" == "hello(x, y) = 12" assert 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"
##[
Note that it is space sensitive: Note that it is space sensitive:
]##
.. code-block:: nim runnableExamples:
import strformat
let x = "12" let x = "12"
doAssert fmt"{x=}" == "x=12" assert fmt"{x=}" == "x=12"
doAssert fmt"{x =:}" == "x =12" assert fmt"{x =:}" == "x =12"
doAssert fmt"{x =}" == "x =12" assert fmt"{x =}" == "x =12"
doAssert fmt"{x= :}" == "x= 12" assert fmt"{x= :}" == "x= 12"
doAssert fmt"{x= }" == "x= 12" assert fmt"{x= }" == "x= 12"
doAssert fmt"{x = :}" == "x = 12" assert fmt"{x = :}" == "x = 12"
doAssert fmt"{x = }" == "x = 12" assert fmt"{x = }" == "x = 12"
doAssert fmt"{x = :}" == "x = 12" assert fmt"{x = :}" == "x = 12"
doAssert fmt"{x = }" == "x = 12" assert fmt"{x = }" == "x = 12"
##[
# Implementation details
Implementation details An expression like `&"{key} is {value:arg} {{z}}"` is transformed into:
======================
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
var v =
if negative: if negative:
# FIXME: overflow error for low(int64) # `uint64(-n)`, but accounts for `n == low(int64)`
v = v * -1 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
@ -464,9 +442,9 @@ proc parseStandardFormatSpecifier*(s: string; start = 0;
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,127 +1,118 @@
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) =
type Obj = object
proc `$`(o: Obj): string = "foobar"
# for custom types, formatValue needs to be overloaded.
template formatValue(result: var string; value: Obj; specifier: string) =
result.formatValue($value, specifier) result.formatValue($value, specifier)
var o: Obj var o: Obj
doAssert fmt"{o}" == "foobar" doAssert fmt"{o}" == "foobar"
doAssert fmt"{o:10}" == "foobar " doAssert fmt"{o:10}" == "foobar "
doAssert fmt"{o=}" == "o=foobar" doAssert fmt"{o=}" == "o=foobar"
doAssert fmt"{o=:10}" == "o=foobar " doAssert fmt"{o=:10}" == "o=foobar "
block: # see issue #7933
var str = "abc"
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 :: "
# see issue #7933 doAssert fmt">7.1 :: {str=:>7.1}" == ">7.1 :: str= a"
var str = "abc" doAssert fmt">7.2 :: {str=:>7.2}" == ">7.2 :: str= ab"
doAssert fmt">7.1 :: {str:>7.1}" == ">7.1 :: a" doAssert fmt">7.3 :: {str=:>7.3}" == ">7.3 :: str= abc"
doAssert fmt">7.2 :: {str:>7.2}" == ">7.2 :: ab" doAssert fmt">7.9 :: {str=:>7.9}" == ">7.9 :: str= abc"
doAssert fmt">7.3 :: {str:>7.3}" == ">7.3 :: abc" doAssert fmt">7.0 :: {str=:>7.0}" == ">7.0 :: str= "
doAssert fmt">7.9 :: {str:>7.9}" == ">7.9 :: abc" doAssert fmt" 7.1 :: {str=:7.1}" == " 7.1 :: str=a "
doAssert fmt">7.0 :: {str:>7.0}" == ">7.0 :: " doAssert fmt" 7.2 :: {str=:7.2}" == " 7.2 :: str=ab "
doAssert fmt" 7.1 :: {str:7.1}" == " 7.1 :: a " doAssert fmt" 7.3 :: {str=:7.3}" == " 7.3 :: str=abc "
doAssert fmt" 7.2 :: {str:7.2}" == " 7.2 :: ab " doAssert fmt" 7.9 :: {str=:7.9}" == " 7.9 :: str=abc "
doAssert fmt" 7.3 :: {str:7.3}" == " 7.3 :: abc " doAssert fmt" 7.0 :: {str=:7.0}" == " 7.0 :: str= "
doAssert fmt" 7.9 :: {str:7.9}" == " 7.9 :: abc " doAssert fmt"^7.1 :: {str=:^7.1}" == "^7.1 :: str= a "
doAssert fmt" 7.0 :: {str:7.0}" == " 7.0 :: " doAssert fmt"^7.2 :: {str=:^7.2}" == "^7.2 :: str= ab "
doAssert fmt"^7.1 :: {str:^7.1}" == "^7.1 :: a " doAssert fmt"^7.3 :: {str=:^7.3}" == "^7.3 :: str= abc "
doAssert fmt"^7.2 :: {str:^7.2}" == "^7.2 :: ab " doAssert fmt"^7.9 :: {str=:^7.9}" == "^7.9 :: str= abc "
doAssert fmt"^7.3 :: {str:^7.3}" == "^7.3 :: abc " doAssert fmt"^7.0 :: {str=:^7.0}" == "^7.0 :: str= "
doAssert fmt"^7.9 :: {str:^7.9}" == "^7.9 :: abc " str = "äöüe\u0309\u0319o\u0307\u0359"
doAssert fmt"^7.0 :: {str:^7.0}" == "^7.0 :: " 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= a" doAssert fmt"^7.1 :: {str=:^7.1}" == "^7.1 :: str= ä "
doAssert fmt">7.2 :: {str=:>7.2}" == ">7.2 :: str= ab" doAssert fmt"^7.2 :: {str=:^7.2}" == "^7.2 :: str= äö "
doAssert fmt">7.3 :: {str=:>7.3}" == ">7.3 :: str= abc" doAssert fmt"^7.3 :: {str=:^7.3}" == "^7.3 :: str= äöü "
doAssert fmt">7.9 :: {str=:>7.9}" == ">7.9 :: str= abc" doAssert fmt"^7.0 :: {str=:^7.0}" == "^7.0 :: 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.1 :: {str=:7.1}" == " 7.1 :: str=a " doAssert fmt"^7.4 :: {str:^7.4}" == "^7.4 :: äöüe "
doAssert fmt" 7.2 :: {str=:7.2}" == " 7.2 :: str=ab " doAssert fmt"^7.9 :: {str:^7.9}" == "^7.9 :: äöüe\u0309\u0319o\u0307\u0359"
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= ä " doAssert fmt"^7.4 :: {str=:^7.4}" == "^7.4 :: str= äöüe "
doAssert fmt"^7.2 :: {str=:^7.2}" == "^7.2 :: str= äö " doAssert fmt"^7.9 :: {str=:^7.9}" == "^7.9 :: str=äöüe\u0309\u0319o\u0307\u0359"
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 " block: # see issue #7932
doAssert fmt"^7.9 :: {str=:^7.9}" == "^7.9 :: str=äöüe\u0309\u0319o\u0307\u0359" doAssert fmt"{15:08}" == "00000015" # int, works
doAssert fmt"{1.5:08}" == "000001.5" # float, works
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
# see issue #7932 doAssert fmt"{15=:08}" == "15=00000015" # int, works
doAssert fmt"{15:08}" == "00000015" # int, works doAssert fmt"{1.5=:08}" == "1.5=000001.5" # float, works
doAssert fmt"{1.5:08}" == "000001.5" # float, works doAssert fmt"{1.5=:0>8}" == "1.5=000001.5" # workaround using fill char works for positive floats
doAssert fmt"{1.5:0>8}" == "000001.5" # workaround using fill char works for positive floats doAssert fmt"{-1.5=:0>8}" == "-1.5=0000-1.5" # even that does not work for negative floats
doAssert fmt"{-1.5:0>8}" == "0000-1.5" # even that does not work for negative floats doAssert fmt"{-1.5=:08}" == "-1.5=-00001.5" # works
doAssert fmt"{-1.5:08}" == "-00001.5" # works doAssert fmt"{1.5=:+08}" == "1.5=+00001.5" # works
doAssert fmt"{1.5:+08}" == "+00001.5" # works doAssert fmt"{1.5=: 08}" == "1.5= 00001.5" # works
doAssert fmt"{1.5: 08}" == " 00001.5" # works
doAssert fmt"{15=:08}" == "15=00000015" # int, works block: # only add explicitly requested sign if value != -0.0 (neg zero)
doAssert fmt"{1.5=:08}" == "1.5=000001.5" # float, works doAssert fmt"{-0.0:g}" == "-0"
doAssert fmt"{1.5=:0>8}" == "1.5=000001.5" # workaround using fill char works for positive floats doAssert fmt"{-0.0:+g}" == "-0"
doAssert fmt"{-1.5=:0>8}" == "-1.5=0000-1.5" # even that does not work for negative floats doAssert fmt"{-0.0: g}" == "-0"
doAssert fmt"{-1.5=:08}" == "-1.5=-00001.5" # works doAssert fmt"{0.0:g}" == "0"
doAssert fmt"{1.5=:+08}" == "1.5=+00001.5" # works doAssert fmt"{0.0:+g}" == "+0"
doAssert fmt"{1.5=: 08}" == "1.5= 00001.5" # works doAssert fmt"{0.0: g}" == " 0"
# only add explicitly requested sign if value != -0.0 (neg zero) doAssert fmt"{-0.0=:g}" == "-0.0=-0"
doAssert fmt"{-0.0:g}" == "-0" doAssert fmt"{-0.0=:+g}" == "-0.0=-0"
doAssert fmt"{-0.0:+g}" == "-0" doAssert fmt"{-0.0=: g}" == "-0.0=-0"
doAssert fmt"{-0.0: g}" == "-0" doAssert fmt"{0.0=:g}" == "0.0=0"
doAssert fmt"{0.0:g}" == "0" doAssert fmt"{0.0=:+g}" == "0.0=+0"
doAssert fmt"{0.0:+g}" == "+0" doAssert fmt"{0.0=: g}" == "0.0= 0"
doAssert fmt"{0.0: g}" == " 0"
doAssert fmt"{-0.0=:g}" == "-0.0=-0" block: # seq format
doAssert fmt"{-0.0=:+g}" == "-0.0=-0" let data1 = [1'i64, 10000'i64, 10000000'i64]
doAssert fmt"{-0.0=: g}" == "-0.0=-0" let data2 = [10000000'i64, 100'i64, 1'i64]
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 proc formatValue(result: var string; value: (array|seq|openArray); specifier: string) =
let data1 = [1'i64, 10000'i64, 10000000'i64]
let data2 = [10000000'i64, 100'i64, 1'i64]
proc formatValue(result: var string; value: (array|seq|openArray); specifier: string) =
result.add "[" result.add "["
for i, it in value: for i, it in value:
if i != 0: if i != 0:
@ -129,77 +120,73 @@ proc formatValue(result: var string; value: (array|seq|openArray); specifier: st
result.formatValue(it, specifier) result.formatValue(it, specifier)
result.add "]" result.add "]"
doAssert fmt"data1: {data1:8} #" == "data1: [ 1, 10000, 10000000] #" doAssert fmt"data1: {data1:8} #" == "data1: [ 1, 10000, 10000000] #"
doAssert fmt"data2: {data2:8} =" == "data2: [10000000, 100, 1] =" doAssert fmt"data2: {data2:8} =" == "data2: [10000000, 100, 1] ="
doAssert fmt"data1: {data1=:8} #" == "data1: data1=[ 1, 10000, 10000000] #" doAssert fmt"data1: {data1=:8} #" == "data1: data1=[ 1, 10000, 10000000] #"
doAssert fmt"data2: {data2=:8} =" == "data2: data2=[10000000, 100, 1] =" doAssert fmt"data2: {data2=:8} =" == "data2: data2=[10000000, 100, 1] ="
# custom format Value block: # custom format Value
type
type
Vec2[T] = object Vec2[T] = object
x,y: T x,y: T
proc formatValue[T](result: var string; value: Vec2[T]; specifier: string) = proc formatValue[T](result: var string; value: Vec2[T]; specifier: string) =
result.add '[' result.add '['
result.formatValue value.x, specifier result.formatValue value.x, specifier
result.add ", " result.add ", "
result.formatValue value.y, specifier result.formatValue value.y, specifier
result.add "]" result.add "]"
let v1 = Vec2[float32](x:1.0, y: 2.0) let v1 = Vec2[float32](x:1.0, y: 2.0)
let v2 = Vec2[int32](x:1, y: 1337) let v2 = Vec2[int32](x:1, y: 1337)
doAssert fmt"v1: {v1:+08} v2: {v2:>4}" == "v1: [+0000001, +0000002] v2: [ 1, 1337]" doAssert fmt"v1: {v1:+08} v2: {v2:>4}" == "v1: [+0000001, +0000002] v2: [ 1, 1337]"
doAssert fmt"v1: {v1=:+08} v2: {v2=:>4}" == "v1: v1=[+0000001, +0000002] v2: v2=[ 1, 1337]" doAssert fmt"v1: {v1=:+08} v2: {v2=:>4}" == "v1: v1=[+0000001, +0000002] v2: v2=[ 1, 1337]"
# bug #11012 block: # bug #11012
type
type
Animal = object Animal = object
name, species: string name, species: string
AnimalRef = ref Animal AnimalRef = ref Animal
proc print_object(animalAddr: AnimalRef) = proc print_object(animalAddr: AnimalRef): string =
echo fmt"Received {animalAddr[]}" fmt"Received {animalAddr[]}"
print_object(AnimalRef(name: "Foo", species: "Bar")) doAssert print_object(AnimalRef(name: "Foo", species: "Bar")) == """Received (name: "Foo", species: "Bar")"""
# bug #11723 block: # bug #11723
let pos: Positive = 64
doAssert fmt"{pos:3}" == " 64"
doAssert fmt"{pos:3b}" == "1000000"
doAssert fmt"{pos:3d}" == " 64"
doAssert fmt"{pos:3o}" == "100"
doAssert fmt"{pos:3x}" == " 40"
doAssert fmt"{pos:3X}" == " 40"
let pos: Positive = 64 doAssert fmt"{pos=:3}" == "pos= 64"
doAssert fmt"{pos:3}" == " 64" doAssert fmt"{pos=:3b}" == "pos=1000000"
doAssert fmt"{pos:3b}" == "1000000" doAssert fmt"{pos=:3d}" == "pos= 64"
doAssert fmt"{pos:3d}" == " 64" doAssert fmt"{pos=:3o}" == "pos=100"
doAssert fmt"{pos:3o}" == "100" doAssert fmt"{pos=:3x}" == "pos= 40"
doAssert fmt"{pos:3x}" == " 40" doAssert fmt"{pos=:3X}" == "pos= 40"
doAssert fmt"{pos:3X}" == " 40"
doAssert fmt"{pos=:3}" == "pos= 64" let nat: Natural = 64
doAssert fmt"{pos=:3b}" == "pos=1000000" doAssert fmt"{nat:3}" == " 64"
doAssert fmt"{pos=:3d}" == "pos= 64" doAssert fmt"{nat:3b}" == "1000000"
doAssert fmt"{pos=:3o}" == "pos=100" doAssert fmt"{nat:3d}" == " 64"
doAssert fmt"{pos=:3x}" == "pos= 40" doAssert fmt"{nat:3o}" == "100"
doAssert fmt"{pos=:3X}" == "pos= 40" doAssert fmt"{nat:3x}" == " 40"
doAssert fmt"{nat:3X}" == " 40"
doAssert fmt"{nat=:3}" == "nat= 64"
doAssert fmt"{nat=:3b}" == "nat=1000000"
doAssert fmt"{nat=:3d}" == "nat= 64"
doAssert fmt"{nat=:3o}" == "nat=100"
doAssert fmt"{nat=:3x}" == "nat= 40"
doAssert fmt"{nat=:3X}" == "nat= 40"
let nat: Natural = 64 block: # bug #12612
doAssert fmt"{nat:3}" == " 64" proc my_proc() =
doAssert fmt"{nat:3b}" == "1000000"
doAssert fmt"{nat:3d}" == " 64"
doAssert fmt"{nat:3o}" == "100"
doAssert fmt"{nat:3x}" == " 40"
doAssert fmt"{nat:3X}" == " 40"
doAssert fmt"{nat=:3}" == "nat= 64"
doAssert fmt"{nat=:3b}" == "nat=1000000"
doAssert fmt"{nat=:3d}" == "nat= 64"
doAssert fmt"{nat=:3o}" == "nat=100"
doAssert fmt"{nat=:3x}" == "nat= 40"
doAssert fmt"{nat=:3X}" == "nat= 40"
# bug #12612
proc my_proc =
const value = "value" const value = "value"
const a = &"{value}" const a = &"{value}"
doAssert a == value doAssert a == value
@ -207,9 +194,9 @@ proc my_proc =
const b = &"{value=}" const b = &"{value=}"
doAssert b == "value=" & value doAssert b == "value=" & value
my_proc() my_proc()
block: block:
template fmt(pattern: string; openCloseChar: char): untyped = template fmt(pattern: string; openCloseChar: char): untyped =
fmt(pattern, openCloseChar, openCloseChar) fmt(pattern, openCloseChar, openCloseChar)
@ -241,67 +228,63 @@ block:
z = true z = true
""" """
block: # tests from the very own strformat documentation!
let msg = "hello"
doAssert fmt"{msg}\n" == "hello\\n"
# tests from the very own strformat documentation! doAssert &"{msg}\n" == "hello\n"
let msg = "hello" doAssert fmt"{msg}{'\n'}" == "hello\n"
doAssert fmt"{msg}\n" == "hello\\n" doAssert fmt("{msg}\n") == "hello\n"
doAssert "{msg}\n".fmt == "hello\n"
doAssert &"{msg}\n" == "hello\n" doAssert fmt"{msg=}\n" == "msg=hello\\n"
doAssert fmt"{msg}{'\n'}" == "hello\n" doAssert &"{msg=}\n" == "msg=hello\n"
doAssert fmt("{msg}\n") == "hello\n"
doAssert "{msg}\n".fmt == "hello\n"
doAssert fmt"{msg=}\n" == "msg=hello\\n" doAssert fmt"{msg=}{'\n'}" == "msg=hello\n"
doAssert fmt("{msg=}\n") == "msg=hello\n"
doAssert "{msg=}\n".fmt == "msg=hello\n"
doAssert &"{msg=}\n" == "msg=hello\n" doAssert &"""{"abc":>4}""" == " abc"
doAssert &"""{"abc":<4}""" == "abc "
doAssert fmt"{msg=}{'\n'}" == "msg=hello\n" doAssert fmt"{-12345:08}" == "-0012345"
doAssert fmt("{msg=}\n") == "msg=hello\n" doAssert fmt"{-1:3}" == " -1"
doAssert "{msg=}\n".fmt == "msg=hello\n" doAssert fmt"{-1:03}" == "-01"
doAssert fmt"{16:#X}" == "0x10"
doAssert &"""{"abc":>4}""" == " abc" doAssert fmt"{123.456}" == "123.456"
doAssert &"""{"abc":<4}""" == "abc " 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"{-12345:08}" == "-0012345" doAssert fmt"{123.456:e}" == "1.234560e+02"
doAssert fmt"{-1:3}" == " -1" doAssert fmt"{123.456:>13e}" == " 1.234560e+02"
doAssert fmt"{-1:03}" == "-01" doAssert fmt"{123.456:13e}" == " 1.234560e+02"
doAssert fmt"{16:#X}" == "0x10"
doAssert fmt"{123.456}" == "123.456" doAssert &"""{"abc"=:>4}""" == "\"abc\"= abc"
doAssert fmt"{123.456:>9.3f}" == " 123.456" doAssert &"""{"abc"=:<4}""" == "\"abc\"=abc "
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"{-12345=:08}" == "-12345=-0012345"
doAssert fmt"{123.456:>13e}" == " 1.234560e+02" doAssert fmt"{-1=:3}" == "-1= -1"
doAssert fmt"{123.456:13e}" == " 1.234560e+02" doAssert fmt"{-1=:03}" == "-1=-01"
doAssert fmt"{16=:#X}" == "16=0x10"
doAssert fmt"{123.456=}" == "123.456=123.456"
doAssert fmt"{123.456=:>9.3f}" == "123.456= 123.456"
doAssert fmt"{123.456=:9.3f}" == "123.456= 123.456"
doAssert fmt"{123.456=:9.4f}" == "123.456= 123.4560"
doAssert fmt"{123.456=:>9.0f}" == "123.456= 123."
doAssert fmt"{123.456=:<9.4f}" == "123.456=123.4560 "
doAssert &"""{"abc"=:>4}""" == "\"abc\"= abc" doAssert fmt"{123.456=:e}" == "123.456=1.234560e+02"
doAssert &"""{"abc"=:<4}""" == "\"abc\"=abc " doAssert fmt"{123.456=:>13e}" == "123.456= 1.234560e+02"
doAssert fmt"{123.456=:13e}" == "123.456= 1.234560e+02"
doAssert fmt"{-12345=:08}" == "-12345=-0012345" block: # tests for debug format string
doAssert fmt"{-1=:3}" == "-1= -1"
doAssert fmt"{-1=:03}" == "-1=-01"
doAssert fmt"{16=:#X}" == "16=0x10"
doAssert fmt"{123.456=}" == "123.456=123.456"
doAssert fmt"{123.456=:>9.3f}" == "123.456= 123.456"
doAssert fmt"{123.456=:9.3f}" == "123.456= 123.456"
doAssert fmt"{123.456=:9.4f}" == "123.456= 123.4560"
doAssert fmt"{123.456=:>9.0f}" == "123.456= 123."
doAssert fmt"{123.456=:<9.4f}" == "123.456=123.4560 "
doAssert fmt"{123.456=:e}" == "123.456=1.234560e+02"
doAssert fmt"{123.456=:>13e}" == "123.456= 1.234560e+02"
doAssert fmt"{123.456=:13e}" == "123.456= 1.234560e+02"
## tests for debug format string
block:
var name = "hello" var name = "hello"
let age = 21 let age = 21
const hobby = "swim" const hobby = "swim"
@ -323,9 +306,7 @@ block:
doAssert fmt"{12 == int(`!=`(age, 12))}" == "false" doAssert fmt"{12 == int(`!=`(age, 12))}" == "false"
doAssert fmt"{0==1}" == "false" doAssert fmt"{0==1}" == "false"
block: # It is space sensitive.
# It is space sensitive.
block:
let x = "12" let x = "12"
doAssert fmt"{x=:}" == "x=12" doAssert fmt"{x=:}" == "x=12"
doAssert fmt"{x=}" == "x=12" doAssert fmt"{x=}" == "x=12"
@ -338,12 +319,11 @@ block:
doAssert fmt"{x = :}" == "x = 12" doAssert fmt"{x = :}" == "x = 12"
doAssert fmt"{x = }" == "x = 12" doAssert fmt"{x = }" == "x = 12"
block: block:
let x = "hello" let x = "hello"
doAssert fmt"{x=}" == "x=hello" doAssert fmt"{x=}" == "x=hello"
doAssert fmt"{x =}" == "x =hello" doAssert fmt"{x =}" == "x =hello"
let y = 3.1415926 let y = 3.1415926
doAssert fmt"{y=:.2f}" == fmt"y={y:.2f}" doAssert fmt"{y=:.2f}" == fmt"y={y:.2f}"
doAssert fmt"{y=}" == fmt"y={y}" doAssert fmt"{y=}" == fmt"y={y}"
@ -377,7 +357,7 @@ block:
doAssert fmt"{x.foo(y)= }" == "x.foo(y)= 18" doAssert fmt"{x.foo(y)= }" == "x.foo(y)= 18"
doAssert fmt"{x.foo(y) = }" == "x.foo(y) = 18" doAssert fmt"{x.foo(y) = }" == "x.foo(y) = 18"
block: block:
template check(actual, expected: string) = template check(actual, expected: string) =
doAssert actual == expected doAssert actual == expected
@ -513,3 +493,9 @@ block:
check &"{low(int64)}", "-9223372036854775808" check &"{low(int64)}", "-9223372036854775808"
doAssert fmt"{'a'} {'b'}" == "a b" doAssert fmt"{'a'} {'b'}" == "a b"
block: # test low(int64)
doAssert &"{low(int64):-}" == "-9223372036854775808"
# xxx static: main()
main()