strformat.nim 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2017 Nim contributors
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ##[
  10. String `interpolation`:idx: / `format`:idx: inspired by
  11. Python's ``f``-strings.
  12. ``fmt`` vs. ``&``
  13. =================
  14. You can use either ``fmt`` or the unary ``&`` operator for formatting. The
  15. difference between them is subtle but important.
  16. The ``fmt"{expr}"`` syntax is more aesthetically pleasing, but it hides a small
  17. gotcha. The string is a
  18. `generalized raw string literal <manual.html#lexical-analysis-generalized-raw-string-literals>`_.
  19. This has some surprising effects:
  20. .. code-block:: nim
  21. import strformat
  22. let msg = "hello"
  23. doAssert fmt"{msg}\n" == "hello\\n"
  24. Because the literal is a raw string literal, the ``\n`` is not interpreted as
  25. an escape sequence.
  26. There are multiple ways to get around this, including the use of the ``&``
  27. operator:
  28. .. code-block:: nim
  29. import strformat
  30. let msg = "hello"
  31. doAssert &"{msg}\n" == "hello\n"
  32. doAssert fmt"{msg}{'\n'}" == "hello\n"
  33. doAssert fmt("{msg}\n") == "hello\n"
  34. doAssert "{msg}\n".fmt == "hello\n"
  35. The choice of style is up to you.
  36. Formatting strings
  37. ==================
  38. .. code-block:: nim
  39. import strformat
  40. doAssert &"""{"abc":>4}""" == " abc"
  41. doAssert &"""{"abc":<4}""" == "abc "
  42. Formatting floats
  43. =================
  44. .. code-block:: nim
  45. import strformat
  46. doAssert fmt"{-12345:08}" == "-0012345"
  47. doAssert fmt"{-1:3}" == " -1"
  48. doAssert fmt"{-1:03}" == "-01"
  49. doAssert fmt"{16:#X}" == "0x10"
  50. doAssert fmt"{123.456}" == "123.456"
  51. doAssert fmt"{123.456:>9.3f}" == " 123.456"
  52. doAssert fmt"{123.456:9.3f}" == " 123.456"
  53. doAssert fmt"{123.456:9.4f}" == " 123.4560"
  54. doAssert fmt"{123.456:>9.0f}" == " 123."
  55. doAssert fmt"{123.456:<9.4f}" == "123.4560 "
  56. doAssert fmt"{123.456:e}" == "1.234560e+02"
  57. doAssert fmt"{123.456:>13e}" == " 1.234560e+02"
  58. doAssert fmt"{123.456:13e}" == " 1.234560e+02"
  59. Implementation details
  60. ======================
  61. An expression like ``&"{key} is {value:arg} {{z}}"`` is transformed into:
  62. .. code-block:: nim
  63. var temp = newStringOfCap(educatedCapGuess)
  64. temp.formatValue key, ""
  65. temp.add " is "
  66. temp.formatValue value, arg
  67. temp.add " {z}"
  68. temp
  69. Parts of the string that are enclosed in the curly braces are interpreted
  70. as Nim code, to escape an ``{`` or ``}`` double it.
  71. ``&`` delegates most of the work to an open overloaded set
  72. of ``formatValue`` procs. The required signature for a type ``T`` that supports
  73. formatting is usually ``proc formatValue(result: var string; x: T; specifier: string)``.
  74. The subexpression after the colon
  75. (``arg`` in ``&"{key} is {value:arg} {{z}}"``) is optional. It will be passed as
  76. the last argument to ``formatValue``. When the colon with the subexpression it is
  77. left out, an empty string will be taken instead.
  78. For strings and numeric types the optional argument is a so-called
  79. "standard format specifier".
  80. Standard format specifier for strings, integers and floats
  81. ==========================================================
  82. The general form of a standard format specifier is::
  83. [[fill]align][sign][#][0][minimumwidth][.precision][type]
  84. The square brackets ``[]`` indicate an optional element.
  85. The optional align flag can be one of the following:
  86. '<'
  87. Forces the field to be left-aligned within the available
  88. space. (This is the default for strings.)
  89. '>'
  90. Forces the field to be right-aligned within the available space.
  91. (This is the default for numbers.)
  92. '^'
  93. Forces the field to be centered within the available space.
  94. Note that unless a minimum field width is defined, the field width
  95. will always be the same size as the data to fill it, so that the alignment
  96. option has no meaning in this case.
  97. The optional 'fill' character defines the character to be used to pad
  98. the field to the minimum width. The fill character, if present, must be
  99. followed by an alignment flag.
  100. The 'sign' option is only valid for numeric types, and can be one of the following:
  101. ================= ====================================================
  102. Sign Meaning
  103. ================= ====================================================
  104. ``+`` Indicates that a sign should be used for both
  105. positive as well as negative numbers.
  106. ``-`` Indicates that a sign should be used only for
  107. negative numbers (this is the default behavior).
  108. (space) Indicates that a leading space should be used on
  109. positive numbers.
  110. ================= ====================================================
  111. If the '#' character is present, integers use the 'alternate form' for formatting.
  112. This means that binary, octal, and hexadecimal output will be prefixed
  113. with '0b', '0o', and '0x', respectively.
  114. 'width' is a decimal integer defining the minimum field width. If not specified,
  115. then the field width will be determined by the content.
  116. If the width field is preceded by a zero ('0') character, this enables
  117. zero-padding.
  118. The 'precision' is a decimal number indicating how many digits should be displayed
  119. after the decimal point in a floating point conversion. For non-numeric types the
  120. field indicates the maximum field size - in other words, how many characters will
  121. be used from the field content. The precision is ignored for integer conversions.
  122. Finally, the 'type' determines how the data should be presented.
  123. The available integer presentation types are:
  124. ================= ====================================================
  125. Type Result
  126. ================= ====================================================
  127. ``b`` Binary. Outputs the number in base 2.
  128. ``d`` Decimal Integer. Outputs the number in base 10.
  129. ``o`` Octal format. Outputs the number in base 8.
  130. ``x`` Hex format. Outputs the number in base 16, using
  131. lower-case letters for the digits above 9.
  132. ``X`` Hex format. Outputs the number in base 16, using
  133. uppercase letters for the digits above 9.
  134. (None) the same as 'd'
  135. ================= ====================================================
  136. The available floating point presentation types are:
  137. ================= ====================================================
  138. Type Result
  139. ================= ====================================================
  140. ``e`` Exponent notation. Prints the number in scientific
  141. notation using the letter 'e' to indicate the
  142. exponent.
  143. ``E`` Exponent notation. Same as 'e' except it converts
  144. the number to uppercase.
  145. ``f`` Fixed point. Displays the number as a fixed-point
  146. number.
  147. ``F`` Fixed point. Same as 'f' except it converts the
  148. number to uppercase.
  149. ``g`` General format. This prints the number as a
  150. fixed-point number, unless the number is too
  151. large, in which case it switches to 'e'
  152. exponent notation.
  153. ``G`` General format. Same as 'g' except switches to 'E'
  154. if the number gets to large.
  155. (None) similar to 'g', except that it prints at least one
  156. digit after the decimal point.
  157. ================= ====================================================
  158. Limitations
  159. ===========
  160. Because of the well defined order how templates and macros are
  161. expanded, strformat cannot expand template arguments:
  162. .. code-block:: nim
  163. template myTemplate(arg: untyped): untyped =
  164. echo "arg is: ", arg
  165. echo &"--- {arg} ---"
  166. let x = "abc"
  167. myTemplate(x)
  168. First the template ``myTemplate`` is expanded, where every identifier
  169. ``arg`` is substituted with its argument. The ``arg`` inside the
  170. format string is not seen by this process, because it is part of a
  171. quoted string literal. It is not an identifier yet. Then the strformat
  172. macro creates the ``arg`` identifier from the string literal. An
  173. identifier that cannot be resolved anymore.
  174. The workaround for this is to bind the template argument to a new local variable.
  175. .. code-block:: nim
  176. template myTemplate(arg: untyped): untyped =
  177. block:
  178. let arg1 {.inject.} = arg
  179. echo "arg is: ", arg1
  180. echo &"--- {arg1} ---"
  181. The use of ``{.inject.}`` here is necessary again because of template
  182. expansion order and hygienic templates. But since we generally want to
  183. keep the hygienicness of ``myTemplate``, and we do not want ``arg1``
  184. to be injected into the context where ``myTemplate`` is expanded,
  185. everything is wrapped in a ``block``.
  186. Future directions
  187. =================
  188. A curly expression with commas in it like ``{x, argA, argB}`` could be
  189. transformed to ``formatValue(result, x, argA, argB)`` in order to support
  190. formatters that do not need to parse a custom language within a custom
  191. language but instead prefer to use Nim's existing syntax. This also
  192. helps in readability since there is only so much you can cram into
  193. single letter DSLs.
  194. ]##
  195. import macros, parseutils, unicode
  196. import strutils except format
  197. proc mkDigit(v: int, typ: char): string {.inline.} =
  198. assert(v < 26)
  199. if v < 10:
  200. result = $chr(ord('0') + v)
  201. else:
  202. result = $chr(ord(if typ == 'x': 'a' else: 'A') + v - 10)
  203. proc alignString*(s: string, minimumWidth: int; align = '\0';
  204. fill = ' '): string =
  205. ## Aligns ``s`` using ``fill`` char.
  206. ## This is only of interest if you want to write a custom ``format`` proc that
  207. ## should support the standard format specifiers.
  208. if minimumWidth == 0:
  209. result = s
  210. else:
  211. let sRuneLen = if s.validateUtf8 == -1: s.runeLen else: s.len
  212. let toFill = minimumWidth - sRuneLen
  213. if toFill <= 0:
  214. result = s
  215. elif align == '<' or align == '\0':
  216. result = s & repeat(fill, toFill)
  217. elif align == '^':
  218. let half = toFill div 2
  219. result = repeat(fill, half) & s & repeat(fill, toFill - half)
  220. else:
  221. result = repeat(fill, toFill) & s
  222. type
  223. StandardFormatSpecifier* = object ## Type that describes "standard format specifiers".
  224. fill*, align*: char ## Desired fill and alignment.
  225. sign*: char ## Desired sign.
  226. alternateForm*: bool ## Whether to prefix binary, octal and hex numbers
  227. ## with ``0b``, ``0o``, ``0x``.
  228. padWithZero*: bool ## Whether to pad with zeros rather than spaces.
  229. minimumWidth*, precision*: int ## Desired minimum width and precision.
  230. typ*: char ## Type like 'f', 'g' or 'd'.
  231. endPosition*: int ## End position in the format specifier after
  232. ## ``parseStandardFormatSpecifier`` returned.
  233. proc formatInt(n: SomeNumber; radix: int;
  234. spec: StandardFormatSpecifier): string =
  235. ## Converts ``n`` to string. If ``n`` is `SomeFloat`, it casts to `int64`.
  236. ## Conversion is done using ``radix``. If result's length is lesser than
  237. ## ``minimumWidth``, it aligns result to the right or left (depending on ``a``)
  238. ## with ``fill`` char.
  239. when n is SomeUnsignedInt:
  240. var v = n.uint64
  241. let negative = false
  242. else:
  243. var v = n.int64
  244. let negative = v.int64 < 0
  245. if negative:
  246. # FIXME: overflow error for low(int64)
  247. v = v * -1
  248. var xx = ""
  249. if spec.alternateForm:
  250. case spec.typ
  251. of 'X': xx = "0x"
  252. of 'x': xx = "0x"
  253. of 'b': xx = "0b"
  254. of 'o': xx = "0o"
  255. else: discard
  256. if v == 0:
  257. result = "0"
  258. else:
  259. result = ""
  260. while v > type(v)(0):
  261. let d = v mod type(v)(radix)
  262. v = v div type(v)(radix)
  263. result.add(mkDigit(d.int, spec.typ))
  264. for idx in 0..<(result.len div 2):
  265. swap result[idx], result[result.len - idx - 1]
  266. if spec.padWithZero:
  267. let sign = negative or spec.sign != '-'
  268. let toFill = spec.minimumWidth - result.len - xx.len - ord(sign)
  269. if toFill > 0:
  270. result = repeat('0', toFill) & result
  271. if negative:
  272. result = "-" & xx & result
  273. elif spec.sign != '-':
  274. result = spec.sign & xx & result
  275. else:
  276. result = xx & result
  277. if spec.align == '<':
  278. for i in result.len..<spec.minimumWidth:
  279. result.add(spec.fill)
  280. else:
  281. let toFill = spec.minimumWidth - result.len
  282. if spec.align == '^':
  283. let half = toFill div 2
  284. result = repeat(spec.fill, half) & result & repeat(spec.fill, toFill - half)
  285. else:
  286. if toFill > 0:
  287. result = repeat(spec.fill, toFill) & result
  288. proc parseStandardFormatSpecifier*(s: string; start = 0;
  289. ignoreUnknownSuffix = false): StandardFormatSpecifier =
  290. ## An exported helper proc that parses the "standard format specifiers",
  291. ## as specified by the grammar::
  292. ##
  293. ## [[fill]align][sign][#][0][minimumwidth][.precision][type]
  294. ##
  295. ## This is only of interest if you want to write a custom ``format`` proc that
  296. ## should support the standard format specifiers. If ``ignoreUnknownSuffix`` is true,
  297. ## an unknown suffix after the ``type`` field is not an error.
  298. const alignChars = {'<', '>', '^'}
  299. result.fill = ' '
  300. result.align = '\0'
  301. result.sign = '-'
  302. var i = start
  303. if i + 1 < s.len and s[i+1] in alignChars:
  304. result.fill = s[i]
  305. result.align = s[i+1]
  306. inc i, 2
  307. elif i < s.len and s[i] in alignChars:
  308. result.align = s[i]
  309. inc i
  310. if i < s.len and s[i] in {'-', '+', ' '}:
  311. result.sign = s[i]
  312. inc i
  313. if i < s.len and s[i] == '#':
  314. result.alternateForm = true
  315. inc i
  316. if i+1 < s.len and s[i] == '0' and s[i+1] in {'0'..'9'}:
  317. result.padWithZero = true
  318. inc i
  319. let parsedLength = parseSaturatedNatural(s, result.minimumWidth, i)
  320. inc i, parsedLength
  321. if i < s.len and s[i] == '.':
  322. inc i
  323. let parsedLengthB = parseSaturatedNatural(s, result.precision, i)
  324. inc i, parsedLengthB
  325. else:
  326. result.precision = -1
  327. if i < s.len and s[i] in {'A'..'Z', 'a'..'z'}:
  328. result.typ = s[i]
  329. inc i
  330. result.endPosition = i
  331. if i != s.len and not ignoreUnknownSuffix:
  332. raise newException(ValueError,
  333. "invalid format string, cannot parse: " & s[i..^1])
  334. proc formatValue*[T: SomeInteger](result: var string; value: T;
  335. specifier: string) =
  336. ## Standard format implementation for ``SomeInteger``. It makes little
  337. ## sense to call this directly, but it is required to exist
  338. ## by the ``&`` macro.
  339. if specifier.len == 0:
  340. result.add $value
  341. return
  342. let spec = parseStandardFormatSpecifier(specifier)
  343. var radix = 10
  344. case spec.typ
  345. of 'x', 'X': radix = 16
  346. of 'd', '\0': discard
  347. of 'b': radix = 2
  348. of 'o': radix = 8
  349. else:
  350. raise newException(ValueError,
  351. "invalid type in format string for number, expected one " &
  352. " of 'x', 'X', 'b', 'd', 'o' but got: " & spec.typ)
  353. result.add formatInt(value, radix, spec)
  354. proc formatValue*(result: var string; value: SomeFloat; specifier: string) =
  355. ## Standard format implementation for ``SomeFloat``. It makes little
  356. ## sense to call this directly, but it is required to exist
  357. ## by the ``&`` macro.
  358. if specifier.len == 0:
  359. result.add $value
  360. return
  361. let spec = parseStandardFormatSpecifier(specifier)
  362. var fmode = ffDefault
  363. case spec.typ
  364. of 'e', 'E':
  365. fmode = ffScientific
  366. of 'f', 'F':
  367. fmode = ffDecimal
  368. of 'g', 'G':
  369. fmode = ffDefault
  370. of '\0': discard
  371. else:
  372. raise newException(ValueError,
  373. "invalid type in format string for number, expected one " &
  374. " of 'e', 'E', 'f', 'F', 'g', 'G' but got: " & spec.typ)
  375. var f = formatBiggestFloat(value, fmode, spec.precision)
  376. var sign = false
  377. if value >= 0.0:
  378. if spec.sign != '-':
  379. sign = true
  380. if value == 0.0:
  381. if 1.0 / value == Inf:
  382. # only insert the sign if value != negZero
  383. f.insert($spec.sign, 0)
  384. else:
  385. f.insert($spec.sign, 0)
  386. else:
  387. sign = true
  388. if spec.padWithZero:
  389. var signStr = ""
  390. if sign:
  391. signStr = $f[0]
  392. f = f[1..^1]
  393. let toFill = spec.minimumWidth - f.len - ord(sign)
  394. if toFill > 0:
  395. f = repeat('0', toFill) & f
  396. if sign:
  397. f = signStr & f
  398. # the default for numbers is right-alignment:
  399. let align = if spec.align == '\0': '>' else: spec.align
  400. let res = alignString(f, spec.minimumWidth, align, spec.fill)
  401. if spec.typ in {'A'..'Z'}:
  402. result.add toUpperAscii(res)
  403. else:
  404. result.add res
  405. proc formatValue*(result: var string; value: string; specifier: string) =
  406. ## Standard format implementation for ``string``. It makes little
  407. ## sense to call this directly, but it is required to exist
  408. ## by the ``&`` macro.
  409. let spec = parseStandardFormatSpecifier(specifier)
  410. var value = value
  411. case spec.typ
  412. of 's', '\0': discard
  413. else:
  414. raise newException(ValueError,
  415. "invalid type in format string for string, expected 's', but got " &
  416. spec.typ)
  417. if spec.precision != -1:
  418. if spec.precision < runeLen(value):
  419. setLen(value, runeOffset(value, spec.precision))
  420. result.add alignString(value, spec.minimumWidth, spec.align, spec.fill)
  421. proc formatValue[T: not SomeInteger](result: var string; value: T;
  422. specifier: string) =
  423. mixin `$`
  424. formatValue(result, $value, specifier)
  425. template formatValue(result: var string; value: char; specifier: string) =
  426. result.add value
  427. template formatValue(result: var string; value: cstring; specifier: string) =
  428. result.add value
  429. macro `&`*(pattern: string): untyped =
  430. ## For a specification of the ``&`` macro, see the module level documentation.
  431. if pattern.kind notin {nnkStrLit..nnkTripleStrLit}:
  432. error "string formatting (fmt(), &) only works with string literals", pattern
  433. let f = pattern.strVal
  434. var i = 0
  435. let res = genSym(nskVar, "fmtRes")
  436. result = newNimNode(nnkStmtListExpr, lineInfoFrom = pattern)
  437. # XXX: https://github.com/nim-lang/Nim/issues/8405
  438. # When compiling with -d:useNimRtl, certain procs such as `count` from the strutils
  439. # module are not accessible at compile-time:
  440. let expectedGrowth = when defined(useNimRtl): 0 else: count(f, '{') * 10
  441. result.add newVarStmt(res, newCall(bindSym"newStringOfCap",
  442. newLit(f.len + expectedGrowth)))
  443. var strlit = ""
  444. while i < f.len:
  445. if f[i] == '{':
  446. inc i
  447. if f[i] == '{':
  448. inc i
  449. strlit.add '{'
  450. else:
  451. if strlit.len > 0:
  452. result.add newCall(bindSym"add", res, newLit(strlit))
  453. strlit = ""
  454. var subexpr = ""
  455. while i < f.len and f[i] != '}' and f[i] != ':':
  456. subexpr.add f[i]
  457. inc i
  458. var x: NimNode
  459. try:
  460. x = parseExpr(subexpr)
  461. except ValueError:
  462. when declared(getCurrentExceptionMsg):
  463. let msg = getCurrentExceptionMsg()
  464. error("could not parse `" & subexpr & "`.\n" & msg, pattern)
  465. else:
  466. error("could not parse `" & subexpr & "`.\n", pattern)
  467. let formatSym = bindSym("formatValue", brOpen)
  468. var options = ""
  469. if f[i] == ':':
  470. inc i
  471. while i < f.len and f[i] != '}':
  472. options.add f[i]
  473. inc i
  474. if f[i] == '}':
  475. inc i
  476. else:
  477. doAssert false, "invalid format string: missing '}'"
  478. result.add newCall(formatSym, res, x, newLit(options))
  479. elif f[i] == '}':
  480. if f[i+1] == '}':
  481. strlit.add '}'
  482. inc i, 2
  483. else:
  484. doAssert false, "invalid format string: '}' instead of '}}'"
  485. inc i
  486. else:
  487. strlit.add f[i]
  488. inc i
  489. if strlit.len > 0:
  490. result.add newCall(bindSym"add", res, newLit(strlit))
  491. result.add res
  492. when defined(debugFmtDsl):
  493. echo repr result
  494. template fmt*(pattern: string): untyped =
  495. ## An alias for ``&``.
  496. bind `&`
  497. &pattern
  498. when isMainModule:
  499. template check(actual, expected: string) =
  500. doAssert actual == expected
  501. from strutils import toUpperAscii, repeat
  502. # Basic tests
  503. let s = "string"
  504. check &"{0} {s}", "0 string"
  505. check &"{s[0..2].toUpperAscii}", "STR"
  506. check &"{-10:04}", "-010"
  507. check &"{-10:<04}", "-010"
  508. check &"{-10:>04}", "-010"
  509. check &"0x{10:02X}", "0x0A"
  510. check &"{10:#04X}", "0x0A"
  511. check &"""{"test":#>5}""", "#test"
  512. check &"""{"test":>5}""", " test"
  513. check &"""{"test":#^7}""", "#test##"
  514. check &"""{"test": <5}""", "test "
  515. check &"""{"test":<5}""", "test "
  516. check &"{1f:.3f}", "1.000"
  517. check &"Hello, {s}!", "Hello, string!"
  518. # Tests for identifiers without parenthesis
  519. check &"{s} works{s}", "string worksstring"
  520. check &"{s:>7}", " string"
  521. doAssert(not compiles(&"{s_works}")) # parsed as identifier `s_works`
  522. # Misc general tests
  523. check &"{{}}", "{}"
  524. check &"{0}%", "0%"
  525. check &"{0}%asdf", "0%asdf"
  526. check &("\n{\"\\n\"}\n"), "\n\n\n"
  527. check &"""{"abc"}s""", "abcs"
  528. # String tests
  529. check &"""{"abc"}""", "abc"
  530. check &"""{"abc":>4}""", " abc"
  531. check &"""{"abc":<4}""", "abc "
  532. check &"""{"":>4}""", " "
  533. check &"""{"":<4}""", " "
  534. # Int tests
  535. check &"{12345}", "12345"
  536. check &"{ - 12345}", "-12345"
  537. check &"{12345:6}", " 12345"
  538. check &"{12345:>6}", " 12345"
  539. check &"{12345:4}", "12345"
  540. check &"{12345:08}", "00012345"
  541. check &"{-12345:08}", "-0012345"
  542. check &"{0:0}", "0"
  543. check &"{0:02}", "00"
  544. check &"{-1:3}", " -1"
  545. check &"{-1:03}", "-01"
  546. check &"{10}", "10"
  547. check &"{16:#X}", "0x10"
  548. check &"{16:^#7X}", " 0x10 "
  549. check &"{16:^+#7X}", " +0x10 "
  550. # Hex tests
  551. check &"{0:x}", "0"
  552. check &"{-0:x}", "0"
  553. check &"{255:x}", "ff"
  554. check &"{255:X}", "FF"
  555. check &"{-255:x}", "-ff"
  556. check &"{-255:X}", "-FF"
  557. check &"{255:x} uNaffeCteD CaSe", "ff uNaffeCteD CaSe"
  558. check &"{255:X} uNaffeCteD CaSe", "FF uNaffeCteD CaSe"
  559. check &"{255:4x}", " ff"
  560. check &"{255:04x}", "00ff"
  561. check &"{-255:4x}", " -ff"
  562. check &"{-255:04x}", "-0ff"
  563. # Float tests
  564. check &"{123.456}", "123.456"
  565. check &"{-123.456}", "-123.456"
  566. check &"{123.456:.3f}", "123.456"
  567. check &"{123.456:+.3f}", "+123.456"
  568. check &"{-123.456:+.3f}", "-123.456"
  569. check &"{-123.456:.3f}", "-123.456"
  570. check &"{123.456:1g}", "123.456"
  571. check &"{123.456:.1f}", "123.5"
  572. check &"{123.456:.0f}", "123."
  573. #check &"{123.456:.0f}", "123."
  574. check &"{123.456:>9.3f}", " 123.456"
  575. check &"{123.456:9.3f}", " 123.456"
  576. check &"{123.456:>9.4f}", " 123.4560"
  577. check &"{123.456:>9.0f}", " 123."
  578. check &"{123.456:<9.4f}", "123.4560 "
  579. # Float (scientific) tests
  580. check &"{123.456:e}", "1.234560e+02"
  581. check &"{123.456:>13e}", " 1.234560e+02"
  582. check &"{123.456:<13e}", "1.234560e+02 "
  583. check &"{123.456:.1e}", "1.2e+02"
  584. check &"{123.456:.2e}", "1.23e+02"
  585. check &"{123.456:.3e}", "1.235e+02"
  586. # Note: times.format adheres to the format protocol. Test that this
  587. # works:
  588. import times
  589. var dt = initDateTime(01, mJan, 2000, 00, 00, 00)
  590. check &"{dt:yyyy-MM-dd}", "2000-01-01"
  591. var tm = fromUnix(0)
  592. discard &"{tm}"
  593. var noww = now()
  594. check &"{noww}", $noww
  595. # Unicode string tests
  596. check &"""{"αβγ"}""", "αβγ"
  597. check &"""{"αβγ":>5}""", " αβγ"
  598. check &"""{"αβγ":<5}""", "αβγ "
  599. check &"""a{"a"}α{"α"}€{"€"}𐍈{"𐍈"}""", "aaαα€€𐍈𐍈"
  600. check &"""a{"a":2}α{"α":2}€{"€":2}𐍈{"𐍈":2}""", "aa αα €€ 𐍈𐍈 "
  601. # Invalid unicode sequences should be handled as plain strings.
  602. # Invalid examples taken from: https://stackoverflow.com/a/3886015/1804173
  603. let invalidUtf8 = [
  604. "\xc3\x28", "\xa0\xa1",
  605. "\xe2\x28\xa1", "\xe2\x82\x28",
  606. "\xf0\x28\x8c\xbc", "\xf0\x90\x28\xbc", "\xf0\x28\x8c\x28"
  607. ]
  608. for s in invalidUtf8:
  609. check &"{s:>5}", repeat(" ", 5-s.len) & s
  610. # bug #11089
  611. let flfoo: float = 1.0
  612. check &"{flfoo}", "1.0"
  613. # bug #11092
  614. check &"{high(int64)}", "9223372036854775807"
  615. check &"{low(int64)}", "-9223372036854775808"
  616. import json
  617. doAssert fmt"{'a'} {'b'}" == "a b"
  618. echo("All tests ok")