diff options
author | Araq <rumpf_a@web.de> | 2015-04-29 22:24:50 +0200 |
---|---|---|
committer | Araq <rumpf_a@web.de> | 2015-04-29 22:24:50 +0200 |
commit | 6d05ae26e66189c72af64f9ade054f9686e004be (patch) | |
tree | aa587202856bb526e97300e48b547df96c70e652 /tests/tuples | |
parent | 813a4f1d83c836c31f515b0923309681cdf12299 (diff) | |
download | Nim-6d05ae26e66189c72af64f9ade054f9686e004be.tar.gz |
fixes regression caused by code cleanups
Diffstat (limited to 'tests/tuples')
-rw-r--r-- | tests/tuples/tuple_with_nil.nim | 766 |
1 files changed, 766 insertions, 0 deletions
diff --git a/tests/tuples/tuple_with_nil.nim b/tests/tuples/tuple_with_nil.nim new file mode 100644 index 000000000..26e4ae85e --- /dev/null +++ b/tests/tuples/tuple_with_nil.nim @@ -0,0 +1,766 @@ +import macros +from strutils import IdentStartChars +import parseutils +import unicode +import math +import fenv +import unsigned +import pegs +import streams + +type + FormatError = object of Exception ## Error in the format string. + + Writer = concept W + ## Writer to output a character `c`. + when (NimMajor, NimMinor, NimPatch) > (0, 10, 2): + write(W, 'c') + else: + block: + var x: W + write(x, char) + + FmtAlign = enum ## Format alignment + faDefault ## default for given format type + faLeft ## left aligned + faRight ## right aligned + faCenter ## centered + faPadding ## right aligned, fill characters after sign (numbers only) + + FmtSign = enum ## Format sign + fsMinus ## only unary minus, no reservered sign space for positive numbers + fsPlus ## unary minus and unary plus + fsSpace ## unary minus and reserved space for positive numbers + + FmtType = enum ## Format type + ftDefault ## default format for given parameter type + ftStr ## string + ftChar ## character + ftDec ## decimal integer + ftBin ## binary integer + ftOct ## octal integer + ftHex ## hexadecimal integer + ftFix ## real number in fixed point notation + ftSci ## real number in scientific notation + ftGen ## real number in generic form (either fixed point or scientific) + ftPercent ## real number multiplied by 100 and % added + + Format = tuple ## Formatting information. + typ: FmtType ## format type + precision: int ## floating point precision + width: int ## minimal width + fill: string ## the fill character, UTF8 + align: FmtAlign ## aligment + sign: FmtSign ## sign notation + baseprefix: bool ## whether binary, octal, hex should be prefixed by 0b, 0x, 0o + upcase: bool ## upper case letters in hex or exponential formats + comma: bool ## + arysep: string ## separator for array elements + + PartKind = enum pkStr, pkFmt + + Part = object + ## Information of a part of the target string. + case kind: PartKind ## type of the part + of pkStr: + str: string ## literal string + of pkFmt: + arg: int ## position argument + fmt: string ## format string + field: string ## field of argument to be accessed + index: int ## array index of argument to be accessed + nested: bool ## true if the argument contains nested formats + +const + DefaultPrec = 6 ## Default precision for floating point numbers. + DefaultFmt: Format = (ftDefault, -1, -1, nil, faDefault, fsMinus, false, false, false, nil) + ## Default format corresponding to the empty format string, i.e. + ## `x.format("") == x.format(DefaultFmt)`. + round_nums = [0.5, 0.05, 0.005, 0.0005, 0.00005, 0.000005, 0.0000005, 0.00000005] + ## Rounding offset for floating point numbers up to precision 8. + +proc write(s: var string; c: char) = + s.add(c) + +proc has(c: Captures; i: range[0..pegs.MaxSubpatterns-1]): bool {.nosideeffect, inline.} = + ## Tests whether `c` contains a non-empty capture `i`. + let b = c.bounds(i) + result = b.first <= b.last + +proc get(str: string; c: Captures; i: range[0..MaxSubpatterns-1]; def: char): char {.nosideeffect, inline.} = + ## If capture `i` is non-empty return that portion of `str` casted + ## to `char`, otherwise return `def`. + result = if c.has(i): str[c.bounds(i).first] else: def + +proc get(str: string; c: Captures; i: range[0..MaxSubpatterns-1]; def: string; begoff: int = 0): string {.nosideeffect, inline.} = + ## If capture `i` is non-empty return that portion of `str` as + ## string, otherwise return `def`. + let b = c.bounds(i) + result = if c.has(i): str.substr(b.first + begoff, b.last) else: def + +proc get(str: string; c: Captures; i: range[0..MaxSubpatterns-1]; def: int; begoff: int = 0): int {.nosideeffect, inline.} = + ## If capture `i` is non-empty return that portion of `str` + ## converted to int, otherwise return `def`. + if c.has(i): + discard str.parseInt(result, c.bounds(i).first + begoff) + else: + result = def + +proc parse(fmt: string): Format {.nosideeffect.} = + # Converts the format string `fmt` into a `Format` structure. + let p = + sequence(capture(?sequence(anyRune(), &charSet({'<', '>', '=', '^'}))), + capture(?charSet({'<', '>', '=', '^'})), + capture(?charSet({'-', '+', ' '})), + capture(?charSet({'#'})), + capture(?(+digits())), + capture(?charSet({','})), + capture(?sequence(charSet({'.'}), +digits())), + capture(?charSet({'b', 'c', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'n', 'o', 's', 'x', 'X', '%'})), + capture(?sequence(charSet({'a'}), *pegs.any()))) + # let p=peg"{(_&[<>=^])?}{[<>=^]?}{[-+ ]?}{[#]?}{[0-9]+?}{[,]?}{([.][0-9]+)?}{[bcdeEfFgGnosxX%]?}{(a.*)?}" + + var caps: Captures + if fmt.rawmatch(p, 0, caps) < 0: + raise newException(FormatError, "Invalid format string") + + result.fill = fmt.get(caps, 0, nil) + + case fmt.get(caps, 1, 0.char) + of '<': result.align = faLeft + of '>': result.align = faRight + of '^': result.align = faCenter + of '=': result.align = faPadding + else: result.align = faDefault + + case fmt.get(caps, 2, '-') + of '-': result.sign = fsMinus + of '+': result.sign = fsPlus + of ' ': result.sign = fsSpace + else: result.sign = fsMinus + + result.baseprefix = caps.has(3) + + result.width = fmt.get(caps, 4, -1) + + if caps.has(4) and fmt[caps.bounds(4).first] == '0': + if result.fill != nil: + raise newException(FormatError, "Leading 0 in with not allowed with explicit fill character") + if result.align != faDefault: + raise newException(FormatError, "Leading 0 in with not allowed with explicit alignment") + result.fill = "0" + result.align = faPadding + + result.comma = caps.has(5) + + result.precision = fmt.get(caps, 6, -1, 1) + + case fmt.get(caps, 7, 0.char) + of 's': result.typ = ftStr + of 'c': result.typ = ftChar + of 'd', 'n': result.typ = ftDec + of 'b': result.typ = ftBin + of 'o': result.typ = ftOct + of 'x': result.typ = ftHex + of 'X': result.typ = ftHex; result.upcase = true + of 'f', 'F': result.typ = ftFix + of 'e': result.typ = ftSci + of 'E': result.typ = ftSci; result.upcase = true + of 'g': result.typ = ftGen + of 'G': result.typ = ftGen; result.upcase = true + of '%': result.typ = ftPercent + else: result.typ = ftDefault + + result.arysep = fmt.get(caps, 8, nil, 1) + +proc getalign(fmt: Format; defalign: FmtAlign; slen: int) : tuple[left, right:int] {.nosideeffect.} = + ## Returns the number of left and right padding characters for a + ## given format alignment and width of the object to be printed. + ## + ## `fmt` + ## the format data + ## `default` + ## if `fmt.align == faDefault`, then this alignment is used + ## `slen` + ## the width of the object to be printed. + ## + ## The returned values `(left, right)` will be as minimal as possible + ## so that `left + slen + right >= fmt.width`. + result.left = 0 + result.right = 0 + if (fmt.width >= 0) and (slen < fmt.width): + let alg = if fmt.align == faDefault: defalign else: fmt.align + case alg: + of faLeft: result.right = fmt.width - slen + of faRight, faPadding: result.left = fmt.width - slen + of faCenter: + result.left = (fmt.width - slen) div 2 + result.right = fmt.width - slen - result.left + else: discard + +proc writefill(o: var Writer; fmt: Format; n: int; signum: int = 0) = + ## Write characters for filling. This function also writes the sign + ## of a numeric format and handles the padding alignment + ## accordingly. + ## + ## `o` + ## output object + ## `add` + ## output function + ## `fmt` + ## format to be used (important for padding aligment) + ## `n` + ## the number of filling characters to be written + ## `signum` + ## the sign of the number to be written, < 0 negative, > 0 positive, = 0 zero + if fmt.align == faPadding and signum != 0: + if signum < 0: write(o, '-') + elif fmt.sign == fsPlus: write(o, '+') + elif fmt.sign == fsSpace: write(o, ' ') + + if fmt.fill == nil: + for i in 1..n: write(o, ' ') + else: + for i in 1..n: + for c in fmt.fill: + write(o, c) + + if fmt.align != faPadding and signum != 0: + if signum < 0: write(o, '-') + elif fmt.sign == fsPlus: write(o, '+') + elif fmt.sign == fsSpace: write(o, ' ') + +proc writeformat(o: var Writer; s: string; fmt: Format) = + ## Write string `s` according to format `fmt` using output object + ## `o` and output function `add`. + if fmt.typ notin {ftDefault, ftStr}: + raise newException(FormatError, "String variable must have 's' format type") + + # compute alignment + let len = if fmt.precision < 0: runelen(s) else: min(runelen(s), fmt.precision) + var alg = getalign(fmt, faLeft, len) + writefill(o, fmt, alg.left) + var pos = 0 + for i in 0..len-1: + let rlen = runeLenAt(s, pos) + for j in pos..pos+rlen-1: write(o, s[j]) + pos += rlen + writefill(o, fmt, alg.right) + +proc writeformat(o: var Writer; c: char; fmt: Format) = + ## Write character `c` according to format `fmt` using output object + ## `o` and output function `add`. + if not (fmt.typ in {ftChar, ftDefault}): + raise newException(FormatError, "Character variable must have 'c' format type") + + # compute alignment + var alg = getalign(fmt, faLeft, 1) + writefill(o, fmt, alg.left) + write(o, c) + writefill(o, fmt, alg.right) + +proc writeformat(o: var Writer; c: Rune; fmt: Format) = + ## Write rune `c` according to format `fmt` using output object + ## `o` and output function `add`. + if not (fmt.typ in {ftChar, ftDefault}): + raise newException(FormatError, "Character variable must have 'c' format type") + + # compute alignment + var alg = getalign(fmt, faLeft, 1) + writefill(o, fmt, alg.left) + let s = c.toUTF8 + for c in s: write(o, c) + writefill(o, fmt, alg.right) + +proc abs(x: SomeUnsignedInt): SomeUnsignedInt {.inline.} = x + ## Return the absolute value of the unsigned int `x`. + +proc writeformat(o: var Writer; i: SomeInteger; fmt: Format) = + ## Write integer `i` according to format `fmt` using output object + ## `o` and output function `add`. + var fmt = fmt + if fmt.typ == ftDefault: + fmt.typ = ftDec + if not (fmt.typ in {ftBin, ftOct, ftHex, ftDec}): + raise newException(FormatError, "Integer variable must of one of the following types: b,o,x,X,d,n") + + var base: type(i) + var len = 0 + case fmt.typ: + of ftDec: + base = 10 + of ftBin: + base = 2 + if fmt.baseprefix: len += 2 + of ftOct: + base = 8 + if fmt.baseprefix: len += 2 + of ftHex: + base = 16 + if fmt.baseprefix: len += 2 + else: assert(false) + + if fmt.sign != fsMinus or i < 0: len.inc + + var x: type(i) = abs(i) + var irev: type(i) = 0 + var ilen = 0 + while x > 0.SomeInteger: + len.inc + ilen.inc + irev = irev * base + x mod base + x = x div base + if ilen == 0: + ilen.inc + len.inc + + var alg = getalign(fmt, faRight, len) + writefill(o, fmt, alg.left, if i >= 0.SomeInteger: 1 else: -1) + if fmt.baseprefix: + case fmt.typ + of ftBin: + write(o, '0') + write(o, 'b') + of ftOct: + write(o, '0') + write(o, 'o') + of ftHex: + write(o, '0') + write(o, 'x') + else: + raise newException(FormatError, "# only allowed with b, o, x or X") + while ilen > 0: + ilen.dec + let c = irev mod base + irev = irev div base + if c < 10: + write(o, ('0'.int + c.int).char) + elif fmt.upcase: + write(o, ('A'.int + c.int - 10).char) + else: + write(o, ('a'.int + c.int - 10).char) + writefill(o, fmt, alg.right) + +proc writeformat(o: var Writer; p: pointer; fmt: Format) = + ## Write pointer `i` according to format `fmt` using output object + ## `o` and output function `add`. + ## + ## Pointers are casted to unsigned int and formated as hexadecimal + ## with prefix unless specified otherwise. + var f = fmt + if f.typ == 0.char: + f.typ = 'x' + f.baseprefix = true + writeformat(o, add, cast[uint](p), f) + +proc writeformat(o: var Writer; x: SomeReal; fmt: Format) = + ## Write real number `x` according to format `fmt` using output + ## object `o` and output function `add`. + var fmt = fmt + # handle default format + if fmt.typ == ftDefault: + fmt.typ = ftGen + if fmt.precision < 0: fmt.precision = DefaultPrec + if not (fmt.typ in {ftFix, ftSci, ftGen, ftPercent}): + raise newException(FormatError, "Integer variable must of one of the following types: f,F,e,E,g,G,%") + + let positive = x >= 0 and classify(x) != fcNegZero + var len = 0 + + if fmt.sign != fsMinus or not positive: len.inc + + var prec = if fmt.precision < 0: DefaultPrec else: fmt.precision + var y = abs(x) + var exp = 0 + var numstr, frstr: array[0..31, char] + var numlen, frbeg, frlen = 0 + + if fmt.typ == ftPercent: y *= 100 + + case classify(x): + of fcNan: + numstr[0..2] = ['n', 'a', 'n'] + numlen = 3 + of fcInf, fcNegInf: + numstr[0..2] = ['f', 'n', 'i'] + numlen = 3 + of fcZero, fcNegZero: + numstr[0] = '0' + numlen = 1 + else: # a usual fractional number + if not (fmt.typ in {ftFix, ftPercent}): # not fixed point + exp = int(floor(log10(y))) + if fmt.typ == ftGen: + if prec == 0: prec = 1 + if -4 <= exp and exp < prec: + prec = prec-1-exp + exp = 0 + else: + prec = prec - 1 + len += 4 # exponent + else: + len += 4 # exponent + # shift y so that 1 <= abs(y) < 2 + if exp > 0: y /= pow(10.SomeReal, abs(exp).SomeReal) + elif exp < 0: y *= pow(10.SomeReal, abs(exp).SomeReal) + elif fmt.typ == ftPercent: + len += 1 # percent sign + + # handle rounding by adding +0.5 * LSB + if prec < len(round_nums): y += round_nums[prec] + + # split into integer and fractional part + var mult = 1'i64 + for i in 1..prec: mult *= 10 + var num = y.int64 + var fr = ((y - num.SomeReal) * mult.SomeReal).int64 + # build integer part string + while num != 0: + numstr[numlen] = ('0'.int + (num mod 10)).char + numlen.inc + num = num div 10 + if numlen == 0: + numstr[0] = '0' + numlen.inc + # build fractional part string + while fr != 0: + frstr[frlen] = ('0'.int + (fr mod 10)).char + frlen.inc + fr = fr div 10 + while frlen < prec: + frstr[frlen] = '0' + frlen.inc + # possible remove trailing 0 + if fmt.typ == ftGen: + while frbeg < frlen and frstr[frbeg] == '0': frbeg.inc + # update length of string + len += numlen; + if frbeg < frlen: + len += 1 + frlen - frbeg # decimal point and fractional string + + let alg = getalign(fmt, faRight, len) + writefill(o, fmt, alg.left, if positive: 1 else: -1) + for i in (numlen-1).countdown(0): write(o, numstr[i]) + if frbeg < frlen: + write(o, '.') + for i in (frlen-1).countdown(frbeg): write(o, frstr[i]) + if fmt.typ == ftSci or (fmt.typ == ftGen and exp != 0): + write(o, if fmt.upcase: 'E' else: 'e') + if exp >= 0: + write(o, '+') + else: + write(o, '-') + exp = -exp + if exp < 10: + write(o, '0') + write(o, ('0'.int + exp).char) + else: + var i=0 + while exp > 0: + numstr[i] = ('0'.int + exp mod 10).char + i+=1 + exp = exp div 10 + while i>0: + i-=1 + write(o, numstr[i]) + if fmt.typ == ftPercent: write(o, '%') + writefill(o, fmt, alg.right) + +proc writeformat(o: var Writer; b: bool; fmt: Format) = + ## Write boolean value `b` according to format `fmt` using output + ## object `o`. A boolean may be formatted numerically or as string. + ## In the former case true is written as 1 and false as 0, in the + ## latter the strings "true" and "false" are shown, respectively. + ## The default is string format. + if fmt.typ in {ftStr, ftDefault}: + writeformat(o, + if b: "true" + else: "false", + fmt) + elif fmt.typ in {ftBin, ftOct, ftHex, ftDec}: + writeformat(o, + if b: 1 + else: 0, + fmt) + else: + raise newException(FormatError, "Boolean values must of one of the following types: s,b,o,x,X,d,n") + +proc writeformat(o: var Writer; ary: openarray[any]; fmt: Format) = + ## Write array `ary` according to format `fmt` using output object + ## `o` and output function `add`. + if ary.len == 0: return + + var sep: string + var nxtfmt = fmt + if fmt.arysep == nil: + sep = "\t" + elif fmt.arysep.len == 0: + sep = "" + else: + let sepch = fmt.arysep[0] + let nxt = 1 + skipUntil(fmt.arysep, sepch, 1) + if nxt >= 1: + nxtfmt.arysep = fmt.arysep.substr(nxt) + sep = fmt.arysep.substr(1, nxt-1) + else: + nxtfmt.arysep = "" + sep = fmt.arysep.substr(1) + writeformat(o, ary[0], nxtfmt) + for i in 1..ary.len-1: + for c in sep: write(o, c) + writeformat(o, ary[i], nxtfmt) + +proc addformat[T](o: var Writer; x: T; fmt: Format = DefaultFmt) {.inline.} = + ## Write `x` formatted with `fmt` to `o`. + writeformat(o, x, fmt) + +proc addformat[T](o: var Writer; x: T; fmt: string) {.inline.} = + ## The same as `addformat(o, x, parse(fmt))`. + addformat(o, x, fmt.parse) + +proc addformat(s: var string; x: string) {.inline.} = + ## Write `x` to `s`. This is a fast specialized version for + ## appending unformatted strings. + add(s, x) + +proc addformat(f: File; x: string) {.inline.} = + ## Write `x` to `f`. This is a fast specialized version for + ## writing unformatted strings to a file. + write(f, x) + +proc addformat[T](f: File; x: T; fmt: Format = DefaultFmt) {.inline.} = + ## Write `x` to file `f` using format `fmt`. + var g = f + writeformat(g, x, fmt) + +proc addformat[T](f: File; x: T; fmt: string) {.inline.} = + ## Write `x` to file `f` using format string `fmt`. This is the same + ## as `addformat(f, x, parse(fmt))` + addformat(f, x, parse(fmt)) + +proc addformat(s: Stream; x: string) {.inline.} = + ## Write `x` to `s`. This is a fast specialized version for + ## writing unformatted strings to a stream. + write(s, x) + +proc addformat[T](s: Stream; x: T; fmt: Format = DefaultFmt) {.inline.} = + ## Write `x` to stream `s` using format `fmt`. + var g = s + writeformat(g, x, fmt) + +proc addformat[T](s: Stream; x: T; fmt: string) {.inline.} = + ## Write `x` to stream `s` using format string `fmt`. This is the same + ## as `addformat(s, x, parse(fmt))` + addformat(s, x, parse(fmt)) + +proc format[T](x: T; fmt: Format): string = + ## Return `x` formatted as a string according to format `fmt`. + result = "" + addformat(result, x, fmt) + +proc format[T](x: T; fmt: string): string = + ## Return `x` formatted as a string according to format string `fmt`. + result = format(x, fmt.parse) + +proc format[T](x: T): string {.inline.} = + ## Return `x` formatted as a string according to the default format. + ## The default format corresponds to an empty format string. + var fmt {.global.} : Format = DefaultFmt + result = format(x, fmt) + +proc unquoted(s: string): string {.compileTime.} = + ## Return `s` {{ and }} by single { and }, respectively. + result = "" + var pos = 0 + while pos < s.len: + let nxt = pos + skipUntil(s, {'{', '}'}) + result.add(s.substr(pos, nxt)) + pos = nxt + 2 + +proc splitfmt(s: string): seq[Part] {.compiletime, nosideeffect.} = + ## Split format string `s` into a sequence of "parts". + ## + + ## Each part is either a literal string or a format specification. A + ## format specification is a substring of the form + ## "{[arg][:format]}" where `arg` is either empty or a number + ## refering to the arg-th argument and an additional field or array + ## index. The format string is a string accepted by `parse`. + let subpeg = sequence(capture(digits()), + capture(?sequence(charSet({'.'}), *pegs.identStartChars(), *identChars())), + capture(?sequence(charSet({'['}), +digits(), charSet({']'}))), + capture(?sequence(charSet({':'}), *pegs.any()))) + result = @[] + var pos = 0 + while true: + let oppos = pos + skipUntil(s, {'{', '}'}, pos) + # reached the end + if oppos >= s.len: + if pos < s.len: + result.add(Part(kind: pkStr, str: s.substr(pos).unquoted)) + return + # skip double + if oppos + 1 < s.len and s[oppos] == s[oppos+1]: + result.add(Part(kind: pkStr, str: s.substr(pos, oppos))) + pos = oppos + 2 + continue + if s[oppos] == '}': + error("Single '}' encountered in format string") + if oppos > pos: + result.add(Part(kind: pkStr, str: s.substr(pos, oppos-1).unquoted)) + # find matching closing } + var lvl = 1 + var nested = false + pos = oppos + while lvl > 0: + pos.inc + pos = pos + skipUntil(s, {'{', '}'}, pos) + if pos >= s.len: + error("Single '{' encountered in format string") + if s[pos] == '{': + lvl.inc + if lvl == 2: + nested = true + if lvl > 2: + error("Too many nested format levels") + else: + lvl.dec + let clpos = pos + var fmtpart = Part(kind: pkFmt, arg: -1, fmt: s.substr(oppos+1, clpos-1), field: nil, index: int.high, nested: nested) + if fmtpart.fmt.len > 0: + var m: array[0..3, string] + if not fmtpart.fmt.match(subpeg, m): + error("invalid format string") + + if m[1] != nil and m[1].len > 0: + fmtpart.field = m[1].substr(1) + if m[2] != nil and m[2].len > 0: + discard parseInt(m[2].substr(1, m[2].len-2), fmtpart.index) + + if m[0].len > 0: discard parseInt(m[0], fmtpart.arg) + if m[3] == nil or m[3].len == 0: + fmtpart.fmt = "" + elif m[3][0] == ':': + fmtpart.fmt = m[3].substr(1) + else: + fmtpart.fmt = m[3] + result.add(fmtpart) + pos = clpos + 1 + +proc literal(s: string): NimNode {.compiletime, nosideeffect.} = + ## Return the nim literal of string `s`. This handles the case if + ## `s` is nil. + result = if s == nil: newNilLit() else: newLit(s) + +proc literal(b: bool): NimNode {.compiletime, nosideeffect.} = + ## Return the nim literal of boolean `b`. This is either `true` + ## or `false` symbol. + result = if b: "true".ident else: "false".ident + +proc literal[T](x: T): NimNode {.compiletime, nosideeffect.} = + ## Return the nim literal of value `x`. + when type(x) is enum: + result = ($x).ident + else: + result = newLit(x) + +proc generatefmt(fmtstr: string; + args: var openarray[tuple[arg:NimNode, cnt:int]]; + arg: var int;): seq[tuple[val, fmt:NimNode]] {.compiletime.} = + ## fmtstr + ## the format string + ## args + ## array of expressions for the arguments + ## arg + ## the number of the next argument for automatic parsing + ## + ## If arg is < 0 then the functions assumes that explicit numbering + ## must be used, otherwise automatic numbering is used starting at + ## `arg`. The value of arg is updated according to the number of + ## arguments being used. If arg == 0 then automatic and manual + ## numbering is not decided (because no explicit manual numbering is + ## fixed und no automatically numbered argument has been used so + ## far). + ## + ## The function returns a list of pairs `(val, fmt)` where `val` is + ## an expression to be formatted and `fmt` is the format string (or + ## Format). Therefore, the resulting string can be generated by + ## concatenating expressions `val.format(fmt)`. If `fmt` is `nil` + ## then `val` is a (literal) string expression. + try: + result = @[] + for part in splitfmt(fmtstr): + case part.kind + of pkStr: result.add((newLit(part.str), nil)) + of pkFmt: + # first compute the argument expression + # start with the correct index + var argexpr : NimNode + if part.arg >= 0: + if arg > 0: + error("Cannot switch from automatic field numbering to manual field specification") + if part.arg >= args.len: + error("Invalid explicit argument index: " & $part.arg) + argexpr = args[part.arg].arg + args[part.arg].cnt = args[part.arg].cnt + 1 + arg = -1 + else: + if arg < 0: + error("Cannot switch from manual field specification to automatic field numbering") + if arg >= args.len: + error("Too few arguments for format string") + argexpr = args[arg].arg + args[arg].cnt = args[arg].cnt + 1 + arg.inc + # possible field access + if part.field != nil and part.field.len > 0: + argexpr = newDotExpr(argexpr, part.field.ident) + # possible array access + if part.index < int.high: + argexpr = newNimNode(nnkBracketExpr).add(argexpr, newLit(part.index)) + # now the expression for the format data + var fmtexpr: NimNode + if part.nested: + # nested format string. Compute the format string by + # concatenating the parts of the substring. + for e in generatefmt(part.fmt, args, arg): + var newexpr = if part.fmt == nil: e.val else: newCall(bindsym"format", e.val, e.fmt) + if fmtexpr != nil and fmtexpr.kind != nnkNilLit: + fmtexpr = infix(fmtexpr, "&", newexpr) + else: + fmtexpr = newexpr + else: + # literal format string, precompute the format data + fmtexpr = newNimNode(nnkPar) + for field, val in part.fmt.parse.fieldPairs: + fmtexpr.add(newNimNode(nnkExprColonExpr).add(field.ident, literal(val))) + # add argument + result.add((argexpr, fmtexpr)) + finally: + discard + +proc addfmtfmt(fmtstr: string; args: NimNode; retvar: NimNode): NimNode {.compileTime.} = + var argexprs = newseq[tuple[arg:NimNode; cnt:int]](args.len) + result = newNimNode(nnkStmtListExpr) + # generate let bindings for arguments + for i in 0..args.len-1: + let argsym = gensym(nskLet, "arg" & $i) + result.add(newLetStmt(argsym, args[i])) + argexprs[i].arg = argsym + # add result values + var arg = 0 + for e in generatefmt(fmtstr, argexprs, arg): + if e.fmt == nil or e.fmt.kind == nnkNilLit: + result.add(newCall(bindsym"addformat", retvar, e.val)) + else: + result.add(newCall(bindsym"addformat", retvar, e.val, e.fmt)) + for i, arg in argexprs: + if arg.cnt == 0: + warning("Argument " & $(i+1) & " `" & args[i].repr & "` is not used in format string") + +macro addfmt(s: var string, fmtstr: string{lit}, args: varargs[expr]): expr = + ## The same as `s.add(fmtstr.fmt(args...))` but faster. + result = addfmtfmt($fmtstr, args, s) + +var s: string = "" +s.addfmt("a:{}", 42) |