This commit is contained in:
Araq 2018-01-14 23:49:53 +01:00
commit 9a60eae631
3 changed files with 114 additions and 114 deletions

View file

@ -15,27 +15,27 @@ Examples:
.. code-block:: nim .. code-block:: nim
doAssert %"""{"abc":>4}""" == " abc" doAssert &"""{"abc":>4}""" == " abc"
doAssert %"""{"abc":<4}""" == "abc " doAssert &"""{"abc":<4}""" == "abc "
doAssert %"{-12345:08}" == "-0012345" doAssert &"{-12345:08}" == "-0012345"
doAssert %"{-1:3}" == " -1" doAssert &"{-1:3}" == " -1"
doAssert %"{-1:03}" == "-01" doAssert &"{-1:03}" == "-01"
doAssert %"{16:#X}" == "0x10" doAssert &"{16:#X}" == "0x10"
doAssert %"{123.456}" == "123.456" doAssert &"{123.456}" == "123.456"
doAssert %"{123.456:>9.3f}" == " 123.456" doAssert &"{123.456:>9.3f}" == " 123.456"
doAssert %"{123.456:9.3f}" == " 123.456" doAssert &"{123.456:9.3f}" == " 123.456"
doAssert %"{123.456:9.4f}" == " 123.4560" doAssert &"{123.456:9.4f}" == " 123.4560"
doAssert %"{123.456:>9.0f}" == " 123." doAssert &"{123.456:>9.0f}" == " 123."
doAssert %"{123.456:<9.4f}" == "123.4560 " doAssert &"{123.456:<9.4f}" == "123.4560 "
doAssert %"{123.456:e}" == "1.234560e+02" doAssert &"{123.456:e}" == "1.234560e+02"
doAssert %"{123.456:>13e}" == " 1.234560e+02" doAssert &"{123.456:>13e}" == " 1.234560e+02"
doAssert %"{123.456:13e}" == " 1.234560e+02" doAssert &"{123.456:13e}" == " 1.234560e+02"
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)
@ -48,13 +48,13 @@ An expression like ``%"{key} is {value:arg} {{z}}"`` is transformed into:
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 an ``{`` or ``}`` double it.
``%`` delegates most of the work to an open overloaded set ``&`` delegates most of the work to an open overloaded set
of ``format`` procs. The required signature for a type ``T`` that supports of ``format`` procs. The required signature for a type ``T`` that supports
formatting is usually ``proc format(x: T; result: var string)`` for efficiency formatting is usually ``proc format(x: T; result: var string)`` for efficiency
but can also be ``proc format(x: T): string``. ``add`` and ``$`` procs are but can also be ``proc format(x: T): string``. ``add`` and ``$`` procs are
used as the fallback implementation. used as the fallback implementation.
This is the concrete lookup algorithm that ``%`` uses: This is the concrete lookup algorithm that ``&`` uses:
.. code-block:: nim .. code-block:: nim
@ -69,7 +69,7 @@ This is the concrete lookup algorithm that ``%`` uses:
The subexpression after the colon The subexpression after the colon
(``arg`` in ``%"{key} is {value:arg} {{z}}"``) is an optional argument (``arg`` in ``&"{key} is {value:arg} {{z}}"``) is an optional argument
passed to ``format``. passed to ``format``.
If an optional argument is present the following lookup algorithm is used: If an optional argument is present the following lookup algorithm is used:
@ -226,8 +226,8 @@ template callFormatOption(res, arg, option) {.dirty.} =
else: else:
format($arg, option, res) format($arg, option, res)
macro `%`*(pattern: string): untyped = macro `&`*(pattern: string): untyped =
## For a specification of the ``%`` macro, see the module level documentation. ## For a specification of the ``&`` macro, see the module level documentation.
runnableExamples: runnableExamples:
template check(actual, expected: string) = template check(actual, expected: string) =
doAssert actual == expected doAssert actual == expected
@ -236,113 +236,113 @@ macro `%`*(pattern: string): untyped =
# Basic tests # Basic tests
let s = "string" let s = "string"
check %"{0} {s}", "0 string" check &"{0} {s}", "0 string"
check %"{s[0..2].toUpperAscii}", "STR" check &"{s[0..2].toUpperAscii}", "STR"
check %"{-10:04}", "-010" check &"{-10:04}", "-010"
check %"{-10:<04}", "-010" check &"{-10:<04}", "-010"
check %"{-10:>04}", "-010" check &"{-10:>04}", "-010"
check %"0x{10:02X}", "0x0A" check &"0x{10:02X}", "0x0A"
check %"{10:#04X}", "0x0A" check &"{10:#04X}", "0x0A"
check %"""{"test":#>5}""", "#test" check &"""{"test":#>5}""", "#test"
check %"""{"test":>5}""", " test" check &"""{"test":>5}""", " test"
check %"""{"test":#^7}""", "#test##" check &"""{"test":#^7}""", "#test##"
check %"""{"test": <5}""", "test " check &"""{"test": <5}""", "test "
check %"""{"test":<5}""", "test " check &"""{"test":<5}""", "test "
check %"{1f:.3f}", "1.000" check &"{1f:.3f}", "1.000"
check %"Hello, {s}!", "Hello, string!" check &"Hello, {s}!", "Hello, string!"
# Tests for identifers without parenthesis # Tests for identifers without parenthesis
check %"{s} works{s}", "string worksstring" check &"{s} works{s}", "string worksstring"
check %"{s:>7}", " string" check &"{s:>7}", " string"
doAssert(not compiles(%"{s_works}")) # parsed as identifier `s_works` doAssert(not compiles(&"{s_works}")) # parsed as identifier `s_works`
# Misc general tests # Misc general tests
check %"{{}}", "{}" check &"{{}}", "{}"
check %"{0}%", "0%" check &"{0}%", "0%"
check %"{0}%asdf", "0%asdf" check &"{0}%asdf", "0%asdf"
check %("\n{\"\\n\"}\n"), "\n\n\n" check &("\n{\"\\n\"}\n"), "\n\n\n"
check %"""{"abc"}s""", "abcs" check &"""{"abc"}s""", "abcs"
# String tests # String tests
check %"""{"abc"}""", "abc" check &"""{"abc"}""", "abc"
check %"""{"abc":>4}""", " abc" check &"""{"abc":>4}""", " abc"
check %"""{"abc":<4}""", "abc " check &"""{"abc":<4}""", "abc "
check %"""{"":>4}""", " " check &"""{"":>4}""", " "
check %"""{"":<4}""", " " check &"""{"":<4}""", " "
# Int tests # Int tests
check %"{12345}", "12345" check &"{12345}", "12345"
check %"{ - 12345}", "-12345" check &"{ - 12345}", "-12345"
check %"{12345:6}", " 12345" check &"{12345:6}", " 12345"
check %"{12345:>6}", " 12345" check &"{12345:>6}", " 12345"
check %"{12345:4}", "12345" check &"{12345:4}", "12345"
check %"{12345:08}", "00012345" check &"{12345:08}", "00012345"
check %"{-12345:08}", "-0012345" check &"{-12345:08}", "-0012345"
check %"{0:0}", "0" check &"{0:0}", "0"
check %"{0:02}", "00" check &"{0:02}", "00"
check %"{-1:3}", " -1" check &"{-1:3}", " -1"
check %"{-1:03}", "-01" check &"{-1:03}", "-01"
check %"{10}", "10" check &"{10}", "10"
check %"{16:#X}", "0x10" check &"{16:#X}", "0x10"
check %"{16:^#7X}", " 0x10 " check &"{16:^#7X}", " 0x10 "
check %"{16:^+#7X}", " +0x10 " check &"{16:^+#7X}", " +0x10 "
# Hex tests # Hex tests
check %"{0:x}", "0" check &"{0:x}", "0"
check %"{-0:x}", "0" check &"{-0:x}", "0"
check %"{255:x}", "ff" check &"{255:x}", "ff"
check %"{255:X}", "FF" check &"{255:X}", "FF"
check %"{-255:x}", "-ff" check &"{-255:x}", "-ff"
check %"{-255:X}", "-FF" check &"{-255:X}", "-FF"
check %"{255:x} uNaffeCteD CaSe", "ff uNaffeCteD CaSe" check &"{255:x} uNaffeCteD CaSe", "ff uNaffeCteD CaSe"
check %"{255:X} uNaffeCteD CaSe", "FF uNaffeCteD CaSe" check &"{255:X} uNaffeCteD CaSe", "FF uNaffeCteD CaSe"
check %"{255:4x}", " ff" check &"{255:4x}", " ff"
check %"{255:04x}", "00ff" check &"{255:04x}", "00ff"
check %"{-255:4x}", " -ff" check &"{-255:4x}", " -ff"
check %"{-255:04x}", "-0ff" check &"{-255:04x}", "-0ff"
# Float tests # Float tests
check %"{123.456}", "123.456" check &"{123.456}", "123.456"
check %"{-123.456}", "-123.456" check &"{-123.456}", "-123.456"
check %"{123.456:.3f}", "123.456" check &"{123.456:.3f}", "123.456"
check %"{123.456:+.3f}", "+123.456" check &"{123.456:+.3f}", "+123.456"
check %"{-123.456:+.3f}", "-123.456" check &"{-123.456:+.3f}", "-123.456"
check %"{-123.456:.3f}", "-123.456" check &"{-123.456:.3f}", "-123.456"
check %"{123.456:1g}", "123.456" check &"{123.456:1g}", "123.456"
check %"{123.456:.1f}", "123.5" check &"{123.456:.1f}", "123.5"
check %"{123.456:.0f}", "123." check &"{123.456:.0f}", "123."
#check %"{123.456:.0f}", "123." #check &"{123.456:.0f}", "123."
check %"{123.456:>9.3f}", " 123.456" check &"{123.456:>9.3f}", " 123.456"
check %"{123.456:9.3f}", " 123.456" check &"{123.456:9.3f}", " 123.456"
check %"{123.456:>9.4f}", " 123.4560" check &"{123.456:>9.4f}", " 123.4560"
check %"{123.456:>9.0f}", " 123." check &"{123.456:>9.0f}", " 123."
check %"{123.456:<9.4f}", "123.4560 " check &"{123.456:<9.4f}", "123.4560 "
# Float (scientific) tests # Float (scientific) tests
check %"{123.456:e}", "1.234560e+02" check &"{123.456:e}", "1.234560e+02"
check %"{123.456:>13e}", " 1.234560e+02" check &"{123.456:>13e}", " 1.234560e+02"
check %"{123.456:<13e}", "1.234560e+02 " check &"{123.456:<13e}", "1.234560e+02 "
check %"{123.456:.1e}", "1.2e+02" check &"{123.456:.1e}", "1.2e+02"
check %"{123.456:.2e}", "1.23e+02" check &"{123.456:.2e}", "1.23e+02"
check %"{123.456:.3e}", "1.235e+02" check &"{123.456:.3e}", "1.235e+02"
# Note: times.format adheres to the format protocol. Test that this # Note: times.format adheres to the format protocol. Test that this
# works: # works:
import times import times
var nullTime: DateTime var nullTime: DateTime
check %"{nullTime:yyyy-mm-dd}", "0000-00-00" check &"{nullTime:yyyy-mm-dd}", "0000-00-00"
# Unicode string tests # Unicode string tests
check %"""{"αβγ"}""", "αβγ" check &"""{"αβγ"}""", "αβγ"
check %"""{"αβγ":>5}""", " αβγ" check &"""{"αβγ":>5}""", " αβγ"
check %"""{"αβγ":<5}""", "αβγ " check &"""{"αβγ":<5}""", "αβγ "
check %"""a{"a"}α{"α"}€{"€"}𐍈{"𐍈"}""", "aaαα€€𐍈𐍈" check &"""a{"a"}α{"α"}€{"€"}𐍈{"𐍈"}""", "aaαα€€𐍈𐍈"
check %"""a{"a":2}α{"α":2}€{"€":2}𐍈{"𐍈":2}""", "aa αα €€ 𐍈𐍈 " check &"""a{"a":2}α{"α":2}€{"€":2}𐍈{"𐍈":2}""", "aa αα €€ 𐍈𐍈 "
# Invalid unicode sequences should be handled as plain strings. # Invalid unicode sequences should be handled as plain strings.
# Invalid examples taken from: https://stackoverflow.com/a/3886015/1804173 # Invalid examples taken from: https://stackoverflow.com/a/3886015/1804173
let invalidUtf8 = [ let invalidUtf8 = [
@ -351,10 +351,10 @@ macro `%`*(pattern: string): untyped =
"\xf0\x28\x8c\xbc", "\xf0\x90\x28\xbc", "\xf0\x28\x8c\x28" "\xf0\x28\x8c\xbc", "\xf0\x90\x28\xbc", "\xf0\x28\x8c\x28"
] ]
for s in invalidUtf8: for s in invalidUtf8:
check %"{s:>5}", repeat(" ", 5-s.len) & s check &"{s:>5}", repeat(" ", 5-s.len) & s
if pattern.kind notin {nnkStrLit..nnkTripleStrLit}: if pattern.kind notin {nnkStrLit..nnkTripleStrLit}:
error "% only works with string literals", pattern error "& only works with string literals", pattern
let f = pattern.strVal let f = pattern.strVal
var i = 0 var i = 0
let res = genSym(nskVar, "fmtRes") let res = genSym(nskVar, "fmtRes")
@ -408,17 +408,17 @@ macro `%`*(pattern: string): untyped =
echo repr result echo repr result
template fmt*(pattern: string): untyped = template fmt*(pattern: string): untyped =
## An alias for ``%``. Helps to avoid conflicts with ``json``'s ``%`` operator. ## An alias for ``&``.
## **Examples:** ## **Examples:**
## ##
## .. code-block:: nim ## .. code-block:: nim
## import json ## import json
## import strformat except `%` ## import strformat except `&`
## ##
## let example = "oh, look no conflicts anymore" ## let example = "oh, look no conflicts anymore"
## echo fmt"{example}" ## echo fmt"{example}"
bind `%` bind `&`
%pattern &pattern
proc mkDigit(v: int, typ: char): string {.inline.} = proc mkDigit(v: int, typ: char): string {.inline.} =
assert(v < 26) assert(v < 26)
@ -573,7 +573,7 @@ proc parseStandardFormatSpecifier*(s: string; start = 0;
proc format*(value: SomeInteger; specifier: string; res: var string) = proc format*(value: SomeInteger; specifier: string; res: var 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.
let spec = parseStandardFormatSpecifier(specifier) let spec = parseStandardFormatSpecifier(specifier)
var radix = 10 var radix = 10
case spec.typ case spec.typ
@ -590,7 +590,7 @@ proc format*(value: SomeInteger; specifier: string; res: var string) =
proc format*(value: SomeReal; specifier: string; res: var string) = proc format*(value: SomeReal; specifier: string; res: var string) =
## Standard format implementation for ``SomeReal``. It makes little ## Standard format implementation for ``SomeReal``. 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 fmode = ffDefault var fmode = ffDefault
@ -622,7 +622,7 @@ proc format*(value: SomeReal; specifier: string; res: var string) =
proc format*(value: string; specifier: string; res: var string) = proc format*(value: string; specifier: string; res: var 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)
case spec.typ case spec.typ
of 's', '\0': discard of 's', '\0': discard

View file

@ -22,7 +22,7 @@ proc frexp_test(lo, hi, step: float64) =
doAssert(abs(rslt - x) < eps) doAssert(abs(rslt - x) < eps)
when manualTest: when manualTest:
echo %("x: {x:10.3f} exp: {exp:4d} frac: {frac:24.20f} check: {$(abs(rslt - x) < eps):-5s} {rslt: 9.3f}") echo fmt("x: {x:10.3f} exp: {exp:4d} frac: {frac:24.20f} check: {$(abs(rslt - x) < eps):-5s} {rslt: 9.3f}")
x += step x += step
when manualTest: when manualTest:

View file

@ -9,5 +9,5 @@ type Obj = object
proc `$`(o: Obj): string = "foobar" proc `$`(o: Obj): string = "foobar"
var o: Obj var o: Obj
doAssert %"{o}" == "foobar" doAssert fmt"{o}" == "foobar"
doAssert %"{o:10}" == "foobar " doAssert fmt"{o:10}" == "foobar "