strformat.fmt now supports non-literal const strings (#18274)

Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
This commit is contained in:
Timothee Cour
2021-06-18 08:57:51 -07:00
committed by GitHub
parent 87cd9b24a3
commit 5600a62229
3 changed files with 43 additions and 27 deletions

View File

@@ -93,7 +93,9 @@
## Standard library additions and changes
- Added support for parenthesized expressions in `strformat`
- `strformat`:
added support for parenthesized expressions.
added support for const string's instead of just string literals
- Fixed buffer overflow bugs in `net`

View File

@@ -476,8 +476,9 @@ proc genSym*(kind: NimSymKind = nskLet; ident = ""): NimNode {.
## needs to occur in a declaration context.
proc callsite*(): NimNode {.magic: "NCallSite", benign, deprecated:
"Deprecated since v0.18.1; use varargs[untyped] in the macro prototype instead".}
"Deprecated since v0.18.1; use `varargs[untyped]` in the macro prototype instead".}
## Returns the AST of the invocation expression that invoked this macro.
# see https://github.com/nim-lang/RFCs/issues/387 as candidate replacement.
proc toStrLit*(n: NimNode): NimNode =
## Converts the AST `n` to the concrete Nim code and wraps that

View File

@@ -573,15 +573,12 @@ template formatValue(result: var string; value: char; specifier: string) =
template formatValue(result: var string; value: cstring; specifier: string) =
result.add value
proc strformatImpl(pattern: NimNode; openChar, closeChar: char): NimNode =
if pattern.kind notin {nnkStrLit..nnkTripleStrLit}:
error "string formatting (fmt(), &) only works with string literals", pattern
proc strformatImpl(f: string; openChar, closeChar: char): NimNode =
if openChar == ':' or closeChar == ':':
error "openChar and closeChar must not be ':'"
let f = pattern.strVal
var i = 0
let res = genSym(nskVar, "fmtRes")
result = newNimNode(nnkStmtListExpr, lineInfoFrom = pattern)
result = newNimNode(nnkStmtListExpr)
# XXX: https://github.com/nim-lang/Nim/issues/8405
# When compiling with -d:useNimRtl, certain procs such as `count` from the strutils
# module are not accessible at compile-time:
@@ -633,12 +630,8 @@ proc strformatImpl(pattern: NimNode; openChar, closeChar: char): NimNode =
var x: NimNode
try:
x = parseExpr(subexpr)
except ValueError:
when declared(getCurrentExceptionMsg):
let msg = getCurrentExceptionMsg()
error("could not parse `" & subexpr & "`.\n" & msg, pattern)
else:
error("could not parse `" & subexpr & "`.\n", pattern)
except ValueError as e:
error("could not parse `$#` in `$#`.\n$#" % [subexpr, f, e.msg])
let formatSym = bindSym("formatValue", brOpen)
var options = ""
if f[i] == ':':
@@ -667,19 +660,39 @@ proc strformatImpl(pattern: NimNode; openChar, closeChar: char): NimNode =
when defined(debugFmtDsl):
echo repr result
macro `&`*(pattern: string): untyped = strformatImpl(pattern, '{', '}')
## For a specification of the `&` macro, see the module level documentation.
macro fmt*(pattern: string): untyped = strformatImpl(pattern, '{', '}')
## An alias for `& <#&.m,string>`_.
macro fmt*(pattern: string; openChar, closeChar: char): untyped =
## The same as `fmt <#fmt.m,string>`_, but uses `openChar` instead of `'{'`
## and `closeChar` instead of `'}'`.
macro fmt*(pattern: static string; openChar: static char, closeChar: static char): string =
## Interpolates `pattern` using symbols in scope.
runnableExamples:
let testInt = 123
assert "<testInt>".fmt('<', '>') == "123"
assert """(()"foo" & "bar"())""".fmt(')', '(') == "(foobar)"
assert """ ""{"123+123"}"" """.fmt('"', '"') == " \"{246}\" "
let x = 7
assert "var is {x * 2}".fmt == "var is 14"
assert "var is {{x}}".fmt == "var is {x}" # escape via doubling
const s = "foo: {x}"
assert s.fmt == "foo: 7" # also works with const strings
strformatImpl(pattern, openChar.intVal.char, closeChar.intVal.char)
assert fmt"\n" == r"\n" # raw string literal
assert "\n".fmt == "\n" # regular literal (likewise with `fmt("\n")` or `fmt "\n"`)
runnableExamples:
# custom `openChar`, `closeChar`
let x = 7
assert "<x>".fmt('<', '>') == "7"
assert "<<<x>>>".fmt('<', '>') == "<7>"
assert "`x`".fmt('`', '`') == "7"
strformatImpl(pattern, openChar, closeChar)
template fmt*(pattern: static string): untyped =
## Alias for `fmt(pattern, '{', '}')`.
fmt(pattern, '{', '}')
macro `&`*(pattern: string{lit}): string =
## `&pattern` is the same as `pattern.fmt`.
## For a specification of the `&` macro, see the module level documentation.
# pending bug #18275, bug #18278, use `pattern: static string`
# consider deprecating this, it's redundant with `fmt` and `fmt` is strictly
# more flexible, readable (no confusion with the binary `&`), self-documenting,
# not to mention #18275, bug #18278.
runnableExamples:
let x = 7
assert &"{x}\n" == "7\n" # regular string literal
assert &"{x}\n" == "7\n".fmt # `fmt` can be used instead
assert &"{x}\n" != fmt"7\n" # see `fmt` docs, this would use a raw string literal
strformatImpl(pattern.strVal, '{', '}')