summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorTimothee Cour <timothee.cour2@gmail.com>2021-06-18 08:57:51 -0700
committerGitHub <noreply@github.com>2021-06-18 08:57:51 -0700
commit5600a622297a9b06b2f47039e0397cded1bd425c (patch)
tree99301aef019d3962f605bbad142f496f6b02f2ec
parent87cd9b24a3df66dcde96bbdec2bd7620015ddeba (diff)
downloadNim-5600a622297a9b06b2f47039e0397cded1bd425c.tar.gz
strformat.fmt now supports non-literal const strings (#18274)
Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
-rw-r--r--changelog.md4
-rw-r--r--lib/core/macros.nim3
-rw-r--r--lib/pure/strformat.nim63
3 files changed, 43 insertions, 27 deletions
diff --git a/changelog.md b/changelog.md
index 26048eedd..4fd1ef355 100644
--- a/changelog.md
+++ b/changelog.md
@@ -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`
 
diff --git a/lib/core/macros.nim b/lib/core/macros.nim
index 37f855d94..c09fae6b3 100644
--- a/lib/core/macros.nim
+++ b/lib/core/macros.nim
@@ -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
diff --git a/lib/pure/strformat.nim b/lib/pure/strformat.nim
index 52731e970..872141383 100644
--- a/lib/pure/strformat.nim
+++ b/lib/pure/strformat.nim
@@ -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, '{', '}')
+macro fmt*(pattern: static string; openChar: static char, closeChar: static char): string =
+  ## Interpolates `pattern` using symbols in scope.
+  runnableExamples:
+    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
+
+    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.
-
-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 `'}'`.
+  # 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 testInt = 123
-    assert "<testInt>".fmt('<', '>') == "123"
-    assert """(()"foo" & "bar"())""".fmt(')', '(') == "(foobar)"
-    assert """ ""{"123+123"}"" """.fmt('"', '"') == " \"{246}\" "
-
-  strformatImpl(pattern, openChar.intVal.char, closeChar.intVal.char)
+    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, '{', '}')