summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--lib/pure/strformat.nim39
-rw-r--r--tests/stdlib/tstrformat.nim32
2 files changed, 57 insertions, 14 deletions
diff --git a/lib/pure/strformat.nim b/lib/pure/strformat.nim
index 257751088..5dbdf8282 100644
--- a/lib/pure/strformat.nim
+++ b/lib/pure/strformat.nim
@@ -523,10 +523,11 @@ template formatValue(result: var string; value: char; specifier: string) =
 template formatValue(result: var string; value: cstring; specifier: string) =
   result.add value
 
-macro `&`*(pattern: string): untyped =
-  ## For a specification of the ``&`` macro, see the module level documentation.
+proc strformatImpl(pattern: NimNode; openChar, closeChar: char): NimNode =
   if pattern.kind notin {nnkStrLit..nnkTripleStrLit}:
     error "string formatting (fmt(), &) only works with string literals", pattern
+  if openChar == ':' or closeChar == ':':
+    error "openChar and closeChar must not be ':'"
   let f = pattern.strVal
   var i = 0
   let res = genSym(nskVar, "fmtRes")
@@ -539,18 +540,18 @@ macro `&`*(pattern: string): untyped =
                                      newLit(f.len + expectedGrowth)))
   var strlit = ""
   while i < f.len:
-    if f[i] == '{':
+    if f[i] == openChar:
       inc i
-      if f[i] == '{':
+      if f[i] == openChar:
         inc i
-        strlit.add '{'
+        strlit.add openChar
       else:
         if strlit.len > 0:
           result.add newCall(bindSym"add", res, newLit(strlit))
           strlit = ""
 
         var subexpr = ""
-        while i < f.len and f[i] != '}' and f[i] != ':':
+        while i < f.len and f[i] != closeChar and f[i] != ':':
           subexpr.add f[i]
           inc i
         var x: NimNode
@@ -566,17 +567,17 @@ macro `&`*(pattern: string): untyped =
         var options = ""
         if f[i] == ':':
           inc i
-          while i < f.len and f[i] != '}':
+          while i < f.len and f[i] != closeChar:
             options.add f[i]
             inc i
-        if f[i] == '}':
+        if f[i] == closeChar:
           inc i
         else:
           doAssert false, "invalid format string: missing '}'"
         result.add newCall(formatSym, res, x, newLit(options))
-    elif f[i] == '}':
-      if f[i+1] == '}':
-        strlit.add '}'
+    elif f[i] == closeChar:
+      if f[i+1] == closeChar:
+        strlit.add closeChar
         inc i, 2
       else:
         doAssert false, "invalid format string: '}' instead of '}}'"
@@ -590,10 +591,20 @@ macro `&`*(pattern: string): untyped =
   when defined(debugFmtDsl):
     echo repr result
 
-template fmt*(pattern: string): untyped =
+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 ``&``.
-  bind `&`
-  &pattern
+
+macro fmt*(pattern: string; openChar, closeChar: char): untyped =
+  ## Use ``openChar`` instead of '{' and ``closeChar`` instead of '}'
+  runnableExamples:
+    let testInt = 123
+    doAssert "<testInt>".fmt('<', '>') == "123"
+    doAssert """(()"foo" & "bar"())""".fmt(')', '(') == "(foobar)"
+    doAssert """ ""{"123+123"}"" """.fmt('"', '"') == " \"{246}\" "
+  strformatImpl(pattern, openChar.intVal.char, closeChar.intVal.char)
 
 when isMainModule:
   template check(actual, expected: string) =
diff --git a/tests/stdlib/tstrformat.nim b/tests/stdlib/tstrformat.nim
index 732283674..a8dad6084 100644
--- a/tests/stdlib/tstrformat.nim
+++ b/tests/stdlib/tstrformat.nim
@@ -133,3 +133,35 @@ doAssert fmt"{nat:3d}" == " 64"
 doAssert fmt"{nat:3o}" == "100"
 doAssert fmt"{nat:3x}" == " 40"
 doAssert fmt"{nat:3X}" == " 40"
+
+block:
+  template fmt(pattern: string; openCloseChar: char): untyped =
+    fmt(pattern, openCloseChar, openCloseChar)
+
+  let
+    testInt = 123
+    testStr = "foobar"
+    testFlt = 3.141592
+  doAssert ">><<".fmt('<', '>') == "><"
+  doAssert " >> << ".fmt('<', '>') == " > < "
+  doAssert "<<>>".fmt('<', '>') == "<>"
+  doAssert " << >> ".fmt('<', '>') == " < > "
+  doAssert "''".fmt('\'') == "'"
+  doAssert "''''".fmt('\'') == "''"
+  doAssert "'' ''".fmt('\'') == "' '"
+  doAssert "<testInt>".fmt('<', '>') == "123"
+  doAssert "<testInt>".fmt('<', '>') == "123"
+  doAssert "'testFlt:1.2f'".fmt('\'') == "3.14"
+  doAssert "<testInt><testStr>".fmt('<', '>') == "123foobar"
+  doAssert """ ""{"123+123"}"" """.fmt('"') == " \"{246}\" "
+  doAssert "(((testFlt:1.2f)))((111))".fmt('(', ')') == "(3.14)(111)"
+  doAssert """(()"foo" & "bar"())""".fmt(')', '(') == "(foobar)"
+  doAssert "{}abc`testStr' `testFlt:1.2f' `1+1' ``".fmt('`', '\'') == "{}abcfoobar 3.14 2 `"
+  doAssert """x = '"foo" & "bar"'
+              y = '123 + 111'
+              z = '3 in {2..7}'
+           """.fmt('\'') ==
+           """x = foobar
+              y = 234
+              z = true
+           """