strformat: compile-time format string parser (backport Nim 2.2) (#216)
https://github.com/nim-lang/Nim/pull/23356
This commit is contained in:
parent
1662762c01
commit
a0c085a51f
|
@ -0,0 +1,190 @@
|
|||
# Backport of https://github.com/nim-lang/Nim/pull/23356/
|
||||
|
||||
import std/strformat
|
||||
|
||||
export strformat
|
||||
|
||||
proc testCompileTime[T]() {.raises: [].} = # Generic to delay codegen
|
||||
const str = ""
|
||||
discard fmt"{str}" # If this raises, it means compile-time formatting is missing
|
||||
|
||||
when not compiles(testCompileTime[int]()):
|
||||
import strutils
|
||||
|
||||
proc mkDigit(v: int, typ: char): string {.inline.} =
|
||||
assert(v < 26)
|
||||
if v < 10:
|
||||
result = $chr(ord('0') + v)
|
||||
else:
|
||||
result = $chr(ord(if typ == 'x': 'a' else: 'A') + v - 10)
|
||||
|
||||
proc formatInt(n: SomeNumber; radix: int; spec: StandardFormatSpecifier): string =
|
||||
## Converts `n` to a string. If `n` is `SomeFloat`, it casts to `int64`.
|
||||
## Conversion is done using `radix`. If result's length is less than
|
||||
## `minimumWidth`, it aligns result to the right or left (depending on `a`)
|
||||
## with the `fill` char.
|
||||
when n is SomeUnsignedInt:
|
||||
var v = n.uint64
|
||||
let negative = false
|
||||
else:
|
||||
let n = n.int64
|
||||
let negative = n < 0
|
||||
var v =
|
||||
if negative:
|
||||
# `uint64(-n)`, but accounts for `n == low(int64)`
|
||||
uint64(not n) + 1
|
||||
else:
|
||||
uint64(n)
|
||||
|
||||
var xx = ""
|
||||
if spec.alternateForm:
|
||||
case spec.typ
|
||||
of 'X': xx = "0x"
|
||||
of 'x': xx = "0x"
|
||||
of 'b': xx = "0b"
|
||||
of 'o': xx = "0o"
|
||||
else: discard
|
||||
|
||||
if v == 0:
|
||||
result = "0"
|
||||
else:
|
||||
result = ""
|
||||
while v > typeof(v)(0):
|
||||
let d = v mod typeof(v)(radix)
|
||||
v = v div typeof(v)(radix)
|
||||
result.add(mkDigit(d.int, spec.typ))
|
||||
for idx in 0..<(result.len div 2):
|
||||
swap result[idx], result[result.len - idx - 1]
|
||||
if spec.padWithZero:
|
||||
let sign = negative or spec.sign != '-'
|
||||
let toFill = spec.minimumWidth - result.len - xx.len - ord(sign)
|
||||
if toFill > 0:
|
||||
result = repeat('0', toFill) & result
|
||||
|
||||
if negative:
|
||||
result = "-" & xx & result
|
||||
elif spec.sign != '-':
|
||||
result = spec.sign & xx & result
|
||||
else:
|
||||
result = xx & result
|
||||
|
||||
if spec.align == '<':
|
||||
for i in result.len..<spec.minimumWidth:
|
||||
result.add(spec.fill)
|
||||
else:
|
||||
let toFill = spec.minimumWidth - result.len
|
||||
if spec.align == '^':
|
||||
let half = toFill div 2
|
||||
result = repeat(spec.fill, half) & result & repeat(spec.fill, toFill - half)
|
||||
else:
|
||||
if toFill > 0:
|
||||
result = repeat(spec.fill, toFill) & result
|
||||
|
||||
proc toRadix(typ: char): int =
|
||||
case typ
|
||||
of 'x', 'X': 16
|
||||
of 'd', '\0': 10
|
||||
of 'o': 8
|
||||
of 'b': 2
|
||||
else:
|
||||
raise newException(ValueError,
|
||||
"invalid type in format string for number, expected one " &
|
||||
" of 'x', 'X', 'b', 'd', 'o' but got: " & typ)
|
||||
|
||||
proc formatValue*[T: SomeInteger](result: var string; value: T;
|
||||
specifier: static string) =
|
||||
## Standard format implementation for `SomeInteger`. It makes little
|
||||
## sense to call this directly, but it is required to exist
|
||||
## by the `&` macro.
|
||||
when specifier.len == 0:
|
||||
result.add $value
|
||||
else:
|
||||
const
|
||||
spec = parseStandardFormatSpecifier(specifier)
|
||||
radix = toRadix(spec.typ)
|
||||
|
||||
result.add formatInt(value, radix, spec)
|
||||
|
||||
proc formatFloat(
|
||||
result: var string, value: SomeFloat, fmode: FloatFormatMode,
|
||||
spec: StandardFormatSpecifier) =
|
||||
var f = formatBiggestFloat(value, fmode, spec.precision)
|
||||
var sign = false
|
||||
if value >= 0.0:
|
||||
if spec.sign != '-':
|
||||
sign = true
|
||||
if value == 0.0:
|
||||
if 1.0 / value == Inf:
|
||||
# only insert the sign if value != negZero
|
||||
f.insert($spec.sign, 0)
|
||||
else:
|
||||
f.insert($spec.sign, 0)
|
||||
else:
|
||||
sign = true
|
||||
|
||||
if spec.padWithZero:
|
||||
var signStr = ""
|
||||
if sign:
|
||||
signStr = $f[0]
|
||||
f = f[1..^1]
|
||||
|
||||
let toFill = spec.minimumWidth - f.len - ord(sign)
|
||||
if toFill > 0:
|
||||
f = repeat('0', toFill) & f
|
||||
if sign:
|
||||
f = signStr & f
|
||||
|
||||
# the default for numbers is right-alignment:
|
||||
let align = if spec.align == '\0': '>' else: spec.align
|
||||
let res = alignString(f, spec.minimumWidth, align, spec.fill)
|
||||
if spec.typ in {'A'..'Z'}:
|
||||
result.add toUpperAscii(res)
|
||||
else:
|
||||
result.add res
|
||||
|
||||
proc toFloatFormatMode(typ: char): FloatFormatMode =
|
||||
case typ
|
||||
of 'e', 'E': ffScientific
|
||||
of 'f', 'F': ffDecimal
|
||||
of 'g', 'G': ffDefault
|
||||
of '\0': ffDefault
|
||||
else:
|
||||
raise newException(ValueError,
|
||||
"invalid type in format string for number, expected one " &
|
||||
" of 'e', 'E', 'f', 'F', 'g', 'G' but got: " & typ)
|
||||
|
||||
proc formatValue*(result: var string; value: SomeFloat; specifier: static string) =
|
||||
## Standard format implementation for `SomeFloat`. It makes little
|
||||
## sense to call this directly, but it is required to exist
|
||||
## by the `&` macro.
|
||||
when specifier.len == 0:
|
||||
result.add $value
|
||||
else:
|
||||
const
|
||||
spec = parseStandardFormatSpecifier(specifier)
|
||||
fmode = toFloatFormatMode(spec.typ)
|
||||
|
||||
formatFloat(result, value, fmode, spec)
|
||||
|
||||
proc formatValue*(result: var string; value: string; specifier: static string) =
|
||||
## Standard format implementation for `string`. It makes little
|
||||
## sense to call this directly, but it is required to exist
|
||||
## by the `&` macro.
|
||||
const spec = parseStandardFormatSpecifier(specifier)
|
||||
var value =
|
||||
when spec.typ in {'s', '\0'}: value
|
||||
else: static:
|
||||
raise newException(ValueError,
|
||||
"invalid type in format string for string, expected 's', but got " &
|
||||
spec.typ)
|
||||
when spec.precision != -1:
|
||||
if spec.precision < runeLen(value):
|
||||
const precision = cast[Natural](spec.precision)
|
||||
setLen(value, Natural(runeOffset(value, precision)))
|
||||
|
||||
result.add alignString(value, spec.minimumWidth, spec.align, spec.fill)
|
||||
|
||||
proc formatValue[T: not SomeInteger](result: var string; value: T; specifier: static string) =
|
||||
mixin `$`
|
||||
formatValue(result, $value, specifier)
|
||||
|
|
@ -31,5 +31,6 @@ import
|
|||
test_ptrops,
|
||||
test_sequtils2,
|
||||
test_sets,
|
||||
test_strformat,
|
||||
test_templateutils,
|
||||
test_winacl
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import ../stew/shims/strformat, unittest2
|
||||
|
||||
{.used.}
|
||||
|
||||
static:
|
||||
assert not compiles(fmt"{dummy}")
|
||||
|
||||
suite "strformat":
|
||||
test "no raises effects":
|
||||
proc x() {.raises: [].} =
|
||||
let str = "str"
|
||||
doAssert fmt"{str}" == "str"
|
||||
|
||||
x()
|
Loading…
Reference in New Issue