tuple_with_nil.nim 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  1. import macros
  2. import parseutils
  3. import unicode
  4. import math
  5. import pegs
  6. import streams
  7. type
  8. FormatError = object of CatchableError ## Error in the format string.
  9. Writer = concept W
  10. ## Writer to output a character `c`.
  11. write(W, 'c')
  12. FmtAlign = enum ## Format alignment
  13. faDefault ## default for given format type
  14. faLeft ## left aligned
  15. faRight ## right aligned
  16. faCenter ## centered
  17. faPadding ## right aligned, fill characters after sign (numbers only)
  18. FmtSign = enum ## Format sign
  19. fsMinus ## only unary minus, no reservered sign space for positive numbers
  20. fsPlus ## unary minus and unary plus
  21. fsSpace ## unary minus and reserved space for positive numbers
  22. FmtType = enum ## Format type
  23. ftDefault ## default format for given parameter type
  24. ftStr ## string
  25. ftChar ## character
  26. ftDec ## decimal integer
  27. ftBin ## binary integer
  28. ftOct ## octal integer
  29. ftHex ## hexadecimal integer
  30. ftFix ## real number in fixed point notation
  31. ftSci ## real number in scientific notation
  32. ftGen ## real number in generic form (either fixed point or scientific)
  33. ftPercent ## real number multiplied by 100 and % added
  34. Format = tuple ## Formatting information.
  35. typ: FmtType ## format type
  36. precision: int ## floating point precision
  37. width: int ## minimal width
  38. fill: string ## the fill character, UTF8
  39. align: FmtAlign ## alignment
  40. sign: FmtSign ## sign notation
  41. baseprefix: bool ## whether binary, octal, hex should be prefixed by 0b, 0x, 0o
  42. upcase: bool ## upper case letters in hex or exponential formats
  43. comma: bool ##
  44. arysep: string ## separator for array elements
  45. PartKind = enum pkStr, pkFmt
  46. Part = object
  47. ## Information of a part of the target string.
  48. case kind: PartKind ## type of the part
  49. of pkStr:
  50. str: string ## literal string
  51. of pkFmt:
  52. arg: int ## position argument
  53. fmt: string ## format string
  54. field: string ## field of argument to be accessed
  55. index: int ## array index of argument to be accessed
  56. nested: bool ## true if the argument contains nested formats
  57. const
  58. DefaultPrec = 6 ## Default precision for floating point numbers.
  59. DefaultFmt: Format = (ftDefault, -1, -1, "", faDefault, fsMinus, false, false, false, "")
  60. ## Default format corresponding to the empty format string, i.e.
  61. ## `x.format("") == x.format(DefaultFmt)`.
  62. round_nums = [0.5, 0.05, 0.005, 0.0005, 0.00005, 0.000005, 0.0000005, 0.00000005]
  63. ## Rounding offset for floating point numbers up to precision 8.
  64. proc write(s: var string; c: char) =
  65. s.add(c)
  66. proc has(c: Captures; i: range[0..pegs.MaxSubpatterns-1]): bool {.nosideeffect, inline.} =
  67. ## Tests whether `c` contains a non-empty capture `i`.
  68. let b = c.bounds(i)
  69. result = b.first <= b.last
  70. proc get(str: string; c: Captures; i: range[0..MaxSubpatterns-1]; def: char): char {.nosideeffect, inline.} =
  71. ## If capture `i` is non-empty return that portion of `str` cast
  72. ## to `char`, otherwise return `def`.
  73. result = if c.has(i): str[c.bounds(i).first] else: def
  74. proc get(str: string; c: Captures; i: range[0..MaxSubpatterns-1]; def: string; begoff: int = 0): string {.nosideeffect, inline.} =
  75. ## If capture `i` is non-empty return that portion of `str` as
  76. ## string, otherwise return `def`.
  77. let b = c.bounds(i)
  78. result = if c.has(i): str.substr(b.first + begoff, b.last) else: def
  79. proc get(str: string; c: Captures; i: range[0..MaxSubpatterns-1]; def: int; begoff: int = 0): int {.nosideeffect, inline.} =
  80. ## If capture `i` is non-empty return that portion of `str`
  81. ## converted to int, otherwise return `def`.
  82. if c.has(i):
  83. discard str.parseInt(result, c.bounds(i).first + begoff)
  84. else:
  85. result = def
  86. proc parse(fmt: string): Format {.nosideeffect.} =
  87. # Converts the format string `fmt` into a `Format` structure.
  88. let p =
  89. sequence(capture(?sequence(anyRune(), &charSet({'<', '>', '=', '^'}))),
  90. capture(?charSet({'<', '>', '=', '^'})),
  91. capture(?charSet({'-', '+', ' '})),
  92. capture(?charSet({'#'})),
  93. capture(?(+digits())),
  94. capture(?charSet({','})),
  95. capture(?sequence(charSet({'.'}), +digits())),
  96. capture(?charSet({'b', 'c', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'n', 'o', 's', 'x', 'X', '%'})),
  97. capture(?sequence(charSet({'a'}), *pegs.any())))
  98. # let p=peg"{(_&[<>=^])?}{[<>=^]?}{[-+ ]?}{[#]?}{[0-9]+?}{[,]?}{([.][0-9]+)?}{[bcdeEfFgGnosxX%]?}{(a.*)?}"
  99. var caps: Captures
  100. if fmt.rawmatch(p, 0, caps) < 0:
  101. raise newException(FormatError, "Invalid format string")
  102. result.fill = fmt.get(caps, 0, "")
  103. case fmt.get(caps, 1, 0.char)
  104. of '<': result.align = faLeft
  105. of '>': result.align = faRight
  106. of '^': result.align = faCenter
  107. of '=': result.align = faPadding
  108. else: result.align = faDefault
  109. case fmt.get(caps, 2, '-')
  110. of '-': result.sign = fsMinus
  111. of '+': result.sign = fsPlus
  112. of ' ': result.sign = fsSpace
  113. else: result.sign = fsMinus
  114. result.baseprefix = caps.has(3)
  115. result.width = fmt.get(caps, 4, -1)
  116. if caps.has(4) and fmt[caps.bounds(4).first] == '0':
  117. if result.fill != "":
  118. raise newException(FormatError, "Leading 0 in with not allowed with explicit fill character")
  119. if result.align != faDefault:
  120. raise newException(FormatError, "Leading 0 in with not allowed with explicit alignment")
  121. result.fill = "0"
  122. result.align = faPadding
  123. result.comma = caps.has(5)
  124. result.precision = fmt.get(caps, 6, -1, 1)
  125. case fmt.get(caps, 7, 0.char)
  126. of 's': result.typ = ftStr
  127. of 'c': result.typ = ftChar
  128. of 'd', 'n': result.typ = ftDec
  129. of 'b': result.typ = ftBin
  130. of 'o': result.typ = ftOct
  131. of 'x': result.typ = ftHex
  132. of 'X': result.typ = ftHex; result.upcase = true
  133. of 'f', 'F': result.typ = ftFix
  134. of 'e': result.typ = ftSci
  135. of 'E': result.typ = ftSci; result.upcase = true
  136. of 'g': result.typ = ftGen
  137. of 'G': result.typ = ftGen; result.upcase = true
  138. of '%': result.typ = ftPercent
  139. else: result.typ = ftDefault
  140. result.arysep = fmt.get(caps, 8, "", 1)
  141. proc getalign(fmt: Format; defalign: FmtAlign; slen: int) : tuple[left, right:int] {.nosideeffect.} =
  142. ## Returns the number of left and right padding characters for a
  143. ## given format alignment and width of the object to be printed.
  144. ##
  145. ## `fmt`
  146. ## the format data
  147. ## `default`
  148. ## if `fmt.align == faDefault`, then this alignment is used
  149. ## `slen`
  150. ## the width of the object to be printed.
  151. ##
  152. ## The returned values `(left, right)` will be as minimal as possible
  153. ## so that `left + slen + right >= fmt.width`.
  154. result.left = 0
  155. result.right = 0
  156. if (fmt.width >= 0) and (slen < fmt.width):
  157. let alg = if fmt.align == faDefault: defalign else: fmt.align
  158. case alg:
  159. of faLeft: result.right = fmt.width - slen
  160. of faRight, faPadding: result.left = fmt.width - slen
  161. of faCenter:
  162. result.left = (fmt.width - slen) div 2
  163. result.right = fmt.width - slen - result.left
  164. else: discard
  165. proc writefill(o: var Writer; fmt: Format; n: int; signum: int = 0) =
  166. ## Write characters for filling. This function also writes the sign
  167. ## of a numeric format and handles the padding alignment
  168. ## accordingly.
  169. ##
  170. ## `o`
  171. ## output object
  172. ## `add`
  173. ## output function
  174. ## `fmt`
  175. ## format to be used (important for padding alignment)
  176. ## `n`
  177. ## the number of filling characters to be written
  178. ## `signum`
  179. ## the sign of the number to be written, < 0 negative, > 0 positive, = 0 zero
  180. if fmt.align == faPadding and signum != 0:
  181. if signum < 0: write(o, '-')
  182. elif fmt.sign == fsPlus: write(o, '+')
  183. elif fmt.sign == fsSpace: write(o, ' ')
  184. if fmt.fill.len == 0:
  185. for i in 1..n: write(o, ' ')
  186. else:
  187. for i in 1..n:
  188. for c in fmt.fill:
  189. write(o, c)
  190. if fmt.align != faPadding and signum != 0:
  191. if signum < 0: write(o, '-')
  192. elif fmt.sign == fsPlus: write(o, '+')
  193. elif fmt.sign == fsSpace: write(o, ' ')
  194. proc writeformat(o: var Writer; s: string; fmt: Format) =
  195. ## Write string `s` according to format `fmt` using output object
  196. ## `o` and output function `add`.
  197. if fmt.typ notin {ftDefault, ftStr}:
  198. raise newException(FormatError, "String variable must have 's' format type")
  199. # compute alignment
  200. let len = if fmt.precision < 0: runelen(s) else: min(runelen(s), fmt.precision)
  201. var alg = getalign(fmt, faLeft, len)
  202. writefill(o, fmt, alg.left)
  203. var pos = 0
  204. for i in 0..len-1:
  205. let rlen = runeLenAt(s, pos)
  206. for j in pos..pos+rlen-1: write(o, s[j])
  207. pos += rlen
  208. writefill(o, fmt, alg.right)
  209. proc writeformat(o: var Writer; c: char; fmt: Format) =
  210. ## Write character `c` according to format `fmt` using output object
  211. ## `o` and output function `add`.
  212. if not (fmt.typ in {ftChar, ftDefault}):
  213. raise newException(FormatError, "Character variable must have 'c' format type")
  214. # compute alignment
  215. var alg = getalign(fmt, faLeft, 1)
  216. writefill(o, fmt, alg.left)
  217. write(o, c)
  218. writefill(o, fmt, alg.right)
  219. proc writeformat(o: var Writer; c: Rune; fmt: Format) =
  220. ## Write rune `c` according to format `fmt` using output object
  221. ## `o` and output function `add`.
  222. if not (fmt.typ in {ftChar, ftDefault}):
  223. raise newException(FormatError, "Character variable must have 'c' format type")
  224. # compute alignment
  225. var alg = getalign(fmt, faLeft, 1)
  226. writefill(o, fmt, alg.left)
  227. let s = c.toUTF8
  228. for c in s: write(o, c)
  229. writefill(o, fmt, alg.right)
  230. proc abs(x: SomeUnsignedInt): SomeUnsignedInt {.inline.} = x
  231. ## Return the absolute value of the unsigned int `x`.
  232. proc writeformat(o: var Writer; i: SomeInteger; fmt: Format) =
  233. ## Write integer `i` according to format `fmt` using output object
  234. ## `o` and output function `add`.
  235. var fmt = fmt
  236. if fmt.typ == ftDefault:
  237. fmt.typ = ftDec
  238. if not (fmt.typ in {ftBin, ftOct, ftHex, ftDec}):
  239. raise newException(FormatError, "Integer variable must of one of the following types: b,o,x,X,d,n")
  240. var base: type(i)
  241. var len = 0
  242. case fmt.typ:
  243. of ftDec:
  244. base = 10
  245. of ftBin:
  246. base = 2
  247. if fmt.baseprefix: len += 2
  248. of ftOct:
  249. base = 8
  250. if fmt.baseprefix: len += 2
  251. of ftHex:
  252. base = 16
  253. if fmt.baseprefix: len += 2
  254. else: assert(false)
  255. if fmt.sign != fsMinus or i < 0: len.inc
  256. var x: type(i) = abs(i)
  257. var irev: type(i) = 0
  258. var ilen = 0
  259. while x > 0.SomeInteger:
  260. len.inc
  261. ilen.inc
  262. irev = irev * base + x mod base
  263. x = x div base
  264. if ilen == 0:
  265. ilen.inc
  266. len.inc
  267. var alg = getalign(fmt, faRight, len)
  268. writefill(o, fmt, alg.left, if i >= 0.SomeInteger: 1 else: -1)
  269. if fmt.baseprefix:
  270. case fmt.typ
  271. of ftBin:
  272. write(o, '0')
  273. write(o, 'b')
  274. of ftOct:
  275. write(o, '0')
  276. write(o, 'o')
  277. of ftHex:
  278. write(o, '0')
  279. write(o, 'x')
  280. else:
  281. raise newException(FormatError, "# only allowed with b, o, x or X")
  282. while ilen > 0:
  283. ilen.dec
  284. let c = irev mod base
  285. irev = irev div base
  286. if c < 10:
  287. write(o, ('0'.int + c.int).char)
  288. elif fmt.upcase:
  289. write(o, ('A'.int + c.int - 10).char)
  290. else:
  291. write(o, ('a'.int + c.int - 10).char)
  292. writefill(o, fmt, alg.right)
  293. proc writeformat(o: var Writer; p: pointer; fmt: Format) =
  294. ## Write pointer `i` according to format `fmt` using output object
  295. ## `o` and output function `add`.
  296. ##
  297. ## Pointers are cast to unsigned int and formatted as hexadecimal
  298. ## with prefix unless specified otherwise.
  299. var f = fmt
  300. if f.typ == 0.char:
  301. f.typ = 'x'
  302. f.baseprefix = true
  303. writeformat(o, add, cast[uint](p), f)
  304. proc writeformat(o: var Writer; x: SomeFloat; fmt: Format) =
  305. ## Write real number `x` according to format `fmt` using output
  306. ## object `o` and output function `add`.
  307. var fmt = fmt
  308. # handle default format
  309. if fmt.typ == ftDefault:
  310. fmt.typ = ftGen
  311. if fmt.precision < 0: fmt.precision = DefaultPrec
  312. if not (fmt.typ in {ftFix, ftSci, ftGen, ftPercent}):
  313. raise newException(FormatError, "Integer variable must of one of the following types: f,F,e,E,g,G,%")
  314. let positive = x >= 0 and classify(x) != fcNegZero
  315. var len = 0
  316. if fmt.sign != fsMinus or not positive: len.inc
  317. var prec = if fmt.precision < 0: DefaultPrec else: fmt.precision
  318. var y = abs(x)
  319. var exp = 0
  320. var numstr, frstr: array[0..31, char]
  321. var numlen, frbeg, frlen = 0
  322. if fmt.typ == ftPercent: y *= 100
  323. case classify(x):
  324. of fcNan:
  325. numstr[0..2] = ['n', 'a', 'n']
  326. numlen = 3
  327. of fcInf, fcNegInf:
  328. numstr[0..2] = ['f', 'n', 'i']
  329. numlen = 3
  330. of fcZero, fcNegZero:
  331. numstr[0] = '0'
  332. numlen = 1
  333. else: # a usual fractional number
  334. if not (fmt.typ in {ftFix, ftPercent}): # not fixed point
  335. exp = int(floor(log10(y)))
  336. if fmt.typ == ftGen:
  337. if prec == 0: prec = 1
  338. if -4 <= exp and exp < prec:
  339. prec = prec-1-exp
  340. exp = 0
  341. else:
  342. prec = prec - 1
  343. len += 4 # exponent
  344. else:
  345. len += 4 # exponent
  346. # shift y so that 1 <= abs(y) < 2
  347. if exp > 0: y /= pow(10.SomeFloat, abs(exp).SomeFloat)
  348. elif exp < 0: y *= pow(10.SomeFloat, abs(exp).SomeFloat)
  349. elif fmt.typ == ftPercent:
  350. len += 1 # percent sign
  351. # handle rounding by adding +0.5 * LSB
  352. if prec < len(round_nums): y += round_nums[prec]
  353. # split into integer and fractional part
  354. var mult = 1'i64
  355. for i in 1..prec: mult *= 10
  356. var num = y.int64
  357. var fr = ((y - num.SomeFloat) * mult.SomeFloat).int64
  358. # build integer part string
  359. while num != 0:
  360. numstr[numlen] = ('0'.int + (num mod 10)).char
  361. numlen.inc
  362. num = num div 10
  363. if numlen == 0:
  364. numstr[0] = '0'
  365. numlen.inc
  366. # build fractional part string
  367. while fr != 0:
  368. frstr[frlen] = ('0'.int + (fr mod 10)).char
  369. frlen.inc
  370. fr = fr div 10
  371. while frlen < prec:
  372. frstr[frlen] = '0'
  373. frlen.inc
  374. # possible remove trailing 0
  375. if fmt.typ == ftGen:
  376. while frbeg < frlen and frstr[frbeg] == '0': frbeg.inc
  377. # update length of string
  378. len += numlen;
  379. if frbeg < frlen:
  380. len += 1 + frlen - frbeg # decimal point and fractional string
  381. let alg = getalign(fmt, faRight, len)
  382. writefill(o, fmt, alg.left, if positive: 1 else: -1)
  383. for i in (numlen-1).countdown(0): write(o, numstr[i])
  384. if frbeg < frlen:
  385. write(o, '.')
  386. for i in (frlen-1).countdown(frbeg): write(o, frstr[i])
  387. if fmt.typ == ftSci or (fmt.typ == ftGen and exp != 0):
  388. write(o, if fmt.upcase: 'E' else: 'e')
  389. if exp >= 0:
  390. write(o, '+')
  391. else:
  392. write(o, '-')
  393. exp = -exp
  394. if exp < 10:
  395. write(o, '0')
  396. write(o, ('0'.int + exp).char)
  397. else:
  398. var i=0
  399. while exp > 0:
  400. numstr[i] = ('0'.int + exp mod 10).char
  401. i+=1
  402. exp = exp div 10
  403. while i>0:
  404. i-=1
  405. write(o, numstr[i])
  406. if fmt.typ == ftPercent: write(o, '%')
  407. writefill(o, fmt, alg.right)
  408. proc writeformat(o: var Writer; b: bool; fmt: Format) =
  409. ## Write boolean value `b` according to format `fmt` using output
  410. ## object `o`. A boolean may be formatted numerically or as string.
  411. ## In the former case true is written as 1 and false as 0, in the
  412. ## latter the strings "true" and "false" are shown, respectively.
  413. ## The default is string format.
  414. if fmt.typ in {ftStr, ftDefault}:
  415. writeformat(o,
  416. if b: "true"
  417. else: "false",
  418. fmt)
  419. elif fmt.typ in {ftBin, ftOct, ftHex, ftDec}:
  420. writeformat(o,
  421. if b: 1
  422. else: 0,
  423. fmt)
  424. else:
  425. raise newException(FormatError, "Boolean values must of one of the following types: s,b,o,x,X,d,n")
  426. proc writeformat(o: var Writer; ary: openarray[system.any]; fmt: Format) =
  427. ## Write array `ary` according to format `fmt` using output object
  428. ## `o` and output function `add`.
  429. if ary.len == 0: return
  430. var sep: string
  431. var nxtfmt = fmt
  432. if fmt.arysep == nil:
  433. sep = "\t"
  434. elif fmt.arysep.len == 0:
  435. sep = ""
  436. else:
  437. let sepch = fmt.arysep[0]
  438. let nxt = 1 + skipUntil(fmt.arysep, sepch, 1)
  439. if nxt >= 1:
  440. nxtfmt.arysep = fmt.arysep.substr(nxt)
  441. sep = fmt.arysep.substr(1, nxt-1)
  442. else:
  443. nxtfmt.arysep = ""
  444. sep = fmt.arysep.substr(1)
  445. writeformat(o, ary[0], nxtfmt)
  446. for i in 1..ary.len-1:
  447. for c in sep: write(o, c)
  448. writeformat(o, ary[i], nxtfmt)
  449. proc addformat[T](o: var Writer; x: T; fmt: Format = DefaultFmt) {.inline.} =
  450. ## Write `x` formatted with `fmt` to `o`.
  451. writeformat(o, x, fmt)
  452. proc addformat[T](o: var Writer; x: T; fmt: string) {.inline.} =
  453. ## The same as `addformat(o, x, parse(fmt))`.
  454. addformat(o, x, fmt.parse)
  455. proc addformat(s: var string; x: string) {.inline.} =
  456. ## Write `x` to `s`. This is a fast specialized version for
  457. ## appending unformatted strings.
  458. add(s, x)
  459. proc addformat(f: File; x: string) {.inline.} =
  460. ## Write `x` to `f`. This is a fast specialized version for
  461. ## writing unformatted strings to a file.
  462. write(f, x)
  463. proc addformat[T](f: File; x: T; fmt: Format = DefaultFmt) {.inline.} =
  464. ## Write `x` to file `f` using format `fmt`.
  465. var g = f
  466. writeformat(g, x, fmt)
  467. proc addformat[T](f: File; x: T; fmt: string) {.inline.} =
  468. ## Write `x` to file `f` using format string `fmt`. This is the same
  469. ## as `addformat(f, x, parse(fmt))`
  470. addformat(f, x, parse(fmt))
  471. proc addformat(s: Stream; x: string) {.inline.} =
  472. ## Write `x` to `s`. This is a fast specialized version for
  473. ## writing unformatted strings to a stream.
  474. write(s, x)
  475. proc addformat[T](s: Stream; x: T; fmt: Format = DefaultFmt) {.inline.} =
  476. ## Write `x` to stream `s` using format `fmt`.
  477. var g = s
  478. writeformat(g, x, fmt)
  479. proc addformat[T](s: Stream; x: T; fmt: string) {.inline.} =
  480. ## Write `x` to stream `s` using format string `fmt`. This is the same
  481. ## as `addformat(s, x, parse(fmt))`
  482. addformat(s, x, parse(fmt))
  483. proc format[T](x: T; fmt: Format): string =
  484. ## Return `x` formatted as a string according to format `fmt`.
  485. result = ""
  486. addformat(result, x, fmt)
  487. proc format[T](x: T; fmt: string): string =
  488. ## Return `x` formatted as a string according to format string `fmt`.
  489. result = format(x, fmt.parse)
  490. proc format[T](x: T): string {.inline.} =
  491. ## Return `x` formatted as a string according to the default format.
  492. ## The default format corresponds to an empty format string.
  493. var fmt {.global.} : Format = DefaultFmt
  494. result = format(x, fmt)
  495. proc unquoted(s: string): string {.compileTime.} =
  496. ## Return `s` {{ and }} by single { and }, respectively.
  497. result = ""
  498. var pos = 0
  499. while pos < s.len:
  500. let nxt = pos + skipUntil(s, {'{', '}'})
  501. result.add(s.substr(pos, nxt))
  502. pos = nxt + 2
  503. proc splitfmt(s: string): seq[Part] {.compiletime, nosideeffect.} =
  504. ## Split format string `s` into a sequence of "parts".
  505. ##
  506. ## Each part is either a literal string or a format specification. A
  507. ## format specification is a substring of the form
  508. ## "{[arg][:format]}" where `arg` is either empty or a number
  509. ## referring to the arg-th argument and an additional field or array
  510. ## index. The format string is a string accepted by `parse`.
  511. let subpeg = sequence(capture(digits()),
  512. capture(?sequence(charSet({'.'}), *pegs.identStartChars(), *identChars())),
  513. capture(?sequence(charSet({'['}), +digits(), charSet({']'}))),
  514. capture(?sequence(charSet({':'}), *pegs.any())))
  515. result = @[]
  516. var pos = 0
  517. while true:
  518. let oppos = pos + skipUntil(s, {'{', '}'}, pos)
  519. # reached the end
  520. if oppos >= s.len:
  521. if pos < s.len:
  522. result.add(Part(kind: pkStr, str: s.substr(pos).unquoted))
  523. return
  524. # skip double
  525. if oppos + 1 < s.len and s[oppos] == s[oppos+1]:
  526. result.add(Part(kind: pkStr, str: s.substr(pos, oppos)))
  527. pos = oppos + 2
  528. continue
  529. if s[oppos] == '}':
  530. error("Single '}' encountered in format string")
  531. if oppos > pos:
  532. result.add(Part(kind: pkStr, str: s.substr(pos, oppos-1).unquoted))
  533. # find matching closing }
  534. var lvl = 1
  535. var nested = false
  536. pos = oppos
  537. while lvl > 0:
  538. pos.inc
  539. pos = pos + skipUntil(s, {'{', '}'}, pos)
  540. if pos >= s.len:
  541. error("Single '{' encountered in format string")
  542. if s[pos] == '{':
  543. lvl.inc
  544. if lvl == 2:
  545. nested = true
  546. if lvl > 2:
  547. error("Too many nested format levels")
  548. else:
  549. lvl.dec
  550. let clpos = pos
  551. var fmtpart = Part(kind: pkFmt, arg: -1, fmt: s.substr(oppos+1, clpos-1), field: "", index: int.high, nested: nested)
  552. if fmtpart.fmt.len > 0:
  553. var m: array[0..3, string]
  554. if not fmtpart.fmt.match(subpeg, m):
  555. error("invalid format string")
  556. if m[1].len > 0:
  557. fmtpart.field = m[1].substr(1)
  558. if m[2].len > 0:
  559. discard parseInt(m[2].substr(1, m[2].len-2), fmtpart.index)
  560. if m[0].len > 0: discard parseInt(m[0], fmtpart.arg)
  561. if m[3].len == 0:
  562. fmtpart.fmt = ""
  563. elif m[3][0] == ':':
  564. fmtpart.fmt = m[3].substr(1)
  565. else:
  566. fmtpart.fmt = m[3]
  567. result.add(fmtpart)
  568. pos = clpos + 1
  569. proc literal(s: string): NimNode {.compiletime, nosideeffect.} =
  570. ## Return the nim literal of string `s`. This handles the case if
  571. ## `s` is nil.
  572. result = newLit(s)
  573. proc literal(b: bool): NimNode {.compiletime, nosideeffect.} =
  574. ## Return the nim literal of boolean `b`. This is either `true`
  575. ## or `false` symbol.
  576. result = if b: "true".ident else: "false".ident
  577. proc literal[T](x: T): NimNode {.compiletime, nosideeffect.} =
  578. ## Return the nim literal of value `x`.
  579. when type(x) is enum:
  580. result = ($x).ident
  581. else:
  582. result = newLit(x)
  583. proc generatefmt(fmtstr: string;
  584. args: var openarray[tuple[arg:NimNode, cnt:int]];
  585. arg: var int;): seq[tuple[val, fmt:NimNode]] {.compiletime.} =
  586. ## fmtstr
  587. ## the format string
  588. ## args
  589. ## array of expressions for the arguments
  590. ## arg
  591. ## the number of the next argument for automatic parsing
  592. ##
  593. ## If arg is < 0 then the functions assumes that explicit numbering
  594. ## must be used, otherwise automatic numbering is used starting at
  595. ## `arg`. The value of arg is updated according to the number of
  596. ## arguments being used. If arg == 0 then automatic and manual
  597. ## numbering is not decided (because no explicit manual numbering is
  598. ## fixed und no automatically numbered argument has been used so
  599. ## far).
  600. ##
  601. ## The function returns a list of pairs `(val, fmt)` where `val` is
  602. ## an expression to be formatted and `fmt` is the format string (or
  603. ## Format). Therefore, the resulting string can be generated by
  604. ## concatenating expressions `val.format(fmt)`. If `fmt` is `nil`
  605. ## then `val` is a (literal) string expression.
  606. try:
  607. result = @[]
  608. for part in splitfmt(fmtstr):
  609. case part.kind
  610. of pkStr: result.add((newLit(part.str), nil))
  611. of pkFmt:
  612. # first compute the argument expression
  613. # start with the correct index
  614. var argexpr : NimNode
  615. if part.arg >= 0:
  616. if arg > 0:
  617. error("Cannot switch from automatic field numbering to manual field specification")
  618. if part.arg >= args.len:
  619. error("Invalid explicit argument index: " & $part.arg)
  620. argexpr = args[part.arg].arg
  621. args[part.arg].cnt = args[part.arg].cnt + 1
  622. arg = -1
  623. else:
  624. if arg < 0:
  625. error("Cannot switch from manual field specification to automatic field numbering")
  626. if arg >= args.len:
  627. error("Too few arguments for format string")
  628. argexpr = args[arg].arg
  629. args[arg].cnt = args[arg].cnt + 1
  630. arg.inc
  631. # possible field access
  632. if part.field.len > 0:
  633. argexpr = newDotExpr(argexpr, part.field.ident)
  634. # possible array access
  635. if part.index < int.high:
  636. argexpr = newNimNode(nnkBracketExpr).add(argexpr, newLit(part.index))
  637. # now the expression for the format data
  638. var fmtexpr: NimNode
  639. if part.nested:
  640. # nested format string. Compute the format string by
  641. # concatenating the parts of the substring.
  642. for e in generatefmt(part.fmt, args, arg):
  643. var newexpr = if part.fmt.len == 0: e.val else: newCall(bindsym"format", e.val, e.fmt)
  644. if fmtexpr != nil and fmtexpr.kind != nnkNilLit:
  645. fmtexpr = infix(fmtexpr, "&", newexpr)
  646. else:
  647. fmtexpr = newexpr
  648. else:
  649. # literal format string, precompute the format data
  650. fmtexpr = newNimNode(nnkPar)
  651. for field, val in part.fmt.parse.fieldPairs:
  652. fmtexpr.add(newNimNode(nnkExprColonExpr).add(field.ident, literal(val)))
  653. # add argument
  654. result.add((argexpr, fmtexpr))
  655. finally:
  656. discard
  657. proc addfmtfmt(fmtstr: string; args: NimNode; retvar: NimNode): NimNode {.compileTime.} =
  658. var argexprs = newseq[tuple[arg:NimNode; cnt:int]](args.len)
  659. result = newNimNode(nnkStmtListExpr)
  660. # generate let bindings for arguments
  661. for i in 0..args.len-1:
  662. let argsym = gensym(nskLet, "arg" & $i)
  663. result.add(newLetStmt(argsym, args[i]))
  664. argexprs[i].arg = argsym
  665. # add result values
  666. var arg = 0
  667. for e in generatefmt(fmtstr, argexprs, arg):
  668. if e.fmt == nil or e.fmt.kind == nnkNilLit:
  669. result.add(newCall(bindsym"addformat", retvar, e.val))
  670. else:
  671. result.add(newCall(bindsym"addformat", retvar, e.val, e.fmt))
  672. for i, arg in argexprs:
  673. if arg.cnt == 0:
  674. warning("Argument " & $(i+1) & " `" & args[i].repr & "` is not used in format string")
  675. macro addfmt(s: var string, fmtstr: string{lit}, args: varargs[typed]): untyped =
  676. ## The same as `s.add(fmtstr.fmt(args...))` but faster.
  677. result = addfmtfmt($fmtstr, args, s)
  678. var s: string = ""
  679. s.addfmt("a:{}", 42)