summary refs log tree commit diff stats
path: root/lib/pure/sugar.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure/sugar.nim')
-rw-r--r--lib/pure/sugar.nim225
1 files changed, 130 insertions, 95 deletions
diff --git a/lib/pure/sugar.nim b/lib/pure/sugar.nim
index 686729515..90ba20c13 100644
--- a/lib/pure/sugar.nim
+++ b/lib/pure/sugar.nim
@@ -11,7 +11,7 @@
 ## macro system.
 
 import std/private/since
-import macros, typetraits
+import std/macros
 
 proc checkPragma(ex, prag: var NimNode) =
   since (1, 3):
@@ -19,7 +19,7 @@ proc checkPragma(ex, prag: var NimNode) =
       prag = ex[1]
       ex = ex[0]
 
-proc createProcType(p, b: NimNode): NimNode {.compileTime.} =
+proc createProcType(p, b: NimNode): NimNode =
   result = newNimNode(nnkProcTy)
   var
     formalParams = newNimNode(nnkFormalParams).add(b)
@@ -53,22 +53,25 @@ proc createProcType(p, b: NimNode): NimNode {.compileTime.} =
   result.add prag
 
 macro `=>`*(p, b: untyped): untyped =
-  ## Syntax sugar for anonymous procedures.
-  ## It also supports pragmas.
+  ## Syntax sugar for anonymous procedures. It also supports pragmas.
+  ##
+  ## .. warning:: Semicolons can not be used to separate procedure arguments.
   runnableExamples:
-    proc passTwoAndTwo(f: (int, int) -> int): int =
-      f(2, 2)
+    proc passTwoAndTwo(f: (int, int) -> int): int = f(2, 2)
 
-    doAssert passTwoAndTwo((x, y) => x + y) == 4
+    assert passTwoAndTwo((x, y) => x + y) == 4
 
     type
       Bot = object
-        call: proc (name: string): string {.noSideEffect.}
+        call: (string {.noSideEffect.} -> string)
 
     var myBot = Bot()
 
     myBot.call = (name: string) {.noSideEffect.} => "Hello " & name & ", I'm a bot."
-    doAssert myBot.call("John") == "Hello John, I'm a bot."
+    assert myBot.call("John") == "Hello John, I'm a bot."
+
+    let f = () => (discard) # simplest proc that returns void
+    f()
 
   var
     params = @[ident"auto"]
@@ -85,16 +88,6 @@ macro `=>`*(p, b: untyped): untyped =
 
   checkPragma(p, pragma) # check again after -> transform
 
-  since (1, 3):
-    if p.kind in {nnkCall, nnkObjConstr}:
-      # foo(x, y) => x + y
-      kind = nnkProcDef
-      name = p[0]
-      let newP = newNimNode(nnkPar)
-      for i in 1..<p.len:
-        newP.add(p[i])
-      p = newP
-
   case p.kind
   of nnkPar, nnkTupleConstr:
     var untypedBeforeColon = 0
@@ -128,9 +121,9 @@ macro `=>`*(p, b: untyped): untyped =
       else:
         error("Incorrect procedure parameter.", c)
       params.add(identDefs)
-  of nnkIdent:
+  of nnkIdent, nnkOpenSymChoice, nnkClosedSymChoice, nnkSym:
     var identDefs = newNimNode(nnkIdentDefs)
-    identDefs.add(p)
+    identDefs.add(ident $p)
     identDefs.add(ident"auto")
     identDefs.add(newEmptyNode())
     params.add(identDefs)
@@ -141,17 +134,21 @@ macro `=>`*(p, b: untyped): untyped =
                    procType = kind)
 
 macro `->`*(p, b: untyped): untyped =
-  ## Syntax sugar for procedure types.
-  ##
-  ## .. code-block:: nim
-  ##
-  ##   proc pass2(f: (float, float) -> float): float =
-  ##     f(2, 2)
+  ## Syntax sugar for procedure types. It also supports pragmas.
   ##
-  ##   # is the same as:
-  ##
-  ##   proc pass2(f: proc (x, y: float): float): float =
-  ##     f(2, 2)
+  ## .. warning:: Semicolons can not be used to separate procedure arguments.
+  runnableExamples:
+    proc passTwoAndTwo(f: (int, int) -> int): int = f(2, 2)
+    # is the same as:
+    # proc passTwoAndTwo(f: proc (x, y: int): int): int = f(2, 2)
+
+    assert passTwoAndTwo((x, y) => x + y) == 4
+
+    proc passOne(f: (int {.noSideEffect.} -> int)): int = f(1)
+    # is the same as:
+    # proc passOne(f: proc (x: int): int {.noSideEffect.}): int = f(1)
+
+    assert passOne(x {.noSideEffect.} => x + 1) == 2
 
   result = createProcType(p, b)
 
@@ -161,19 +158,44 @@ macro dump*(x: untyped): untyped =
   ## of the tree representing the expression - as it would appear in
   ## source code - together with the value of the expression.
   ##
-  ## As an example,
-  ##
-  ## .. code-block:: nim
-  ##   let
-  ##     x = 10
-  ##     y = 20
-  ##   dump(x + y)
-  ##
-  ## will print ``x + y = 30``.
+  ## See also: `dumpToString` which is more convenient and useful since
+  ## it expands intermediate templates/macros, returns a string instead of
+  ## calling `echo`, and works with statements and expressions.
+  runnableExamples("-r:off"):
+    let
+      x = 10
+      y = 20
+    dump(x + y) # prints: `x + y = 30`
+
   let s = x.toStrLit
-  let r = quote do:
+  result = quote do:
     debugEcho `s`, " = ", `x`
-  return r
+
+macro dumpToStringImpl(s: static string, x: typed): string =
+  let s2 = x.toStrLit
+  if x.typeKind == ntyVoid:
+    result = quote do:
+      `s` & ": " & `s2`
+  else:
+    result = quote do:
+      `s` & ": " & `s2` & " = " & $`x`
+
+macro dumpToString*(x: untyped): string =
+  ## Returns the content of a statement or expression `x` after semantic analysis,
+  ## useful for debugging.
+  runnableExamples:
+    const a = 1
+    let x = 10
+    assert dumpToString(a + 2) == "a + 2: 3 = 3"
+    assert dumpToString(a + x) == "a + x: 1 + x = 11"
+    template square(x): untyped = x * x
+    assert dumpToString(square(x)) == "square(x): x * x = 100"
+    assert not compiles dumpToString(1 + nonexistent)
+    import std/strutils
+    assert "failedAssertImpl" in dumpToString(assert true) # example with a statement
+  result = newCall(bindSym"dumpToStringImpl")
+  result.add newLit repr(x)
+  result.add x
 
 # TODO: consider exporting this in macros.nim
 proc freshIdentNodes(ast: NimNode): NimNode =
@@ -181,7 +203,7 @@ proc freshIdentNodes(ast: NimNode): NimNode =
   # see also https://github.com/nim-lang/Nim/pull/8531#issuecomment-410436458
   proc inspect(node: NimNode): NimNode =
     case node.kind:
-    of nnkIdent, nnkSym:
+    of nnkIdent, nnkSym, nnkOpenSymChoice, nnkClosedSymChoice, nnkOpenSym:
       result = ident($node)
     of nnkEmpty, nnkLiterals:
       result = node
@@ -193,34 +215,41 @@ proc freshIdentNodes(ast: NimNode): NimNode =
 
 macro capture*(locals: varargs[typed], body: untyped): untyped {.since: (1, 1).} =
   ## Useful when creating a closure in a loop to capture some local loop variables
-  ## by their current iteration values. Example:
-  ##
-  ## .. code-block:: Nim
-  ##   import strformat, sequtils, sugar
-  ##   var myClosure : proc()
-  ##   for i in 5..7:
-  ##     for j in 7..9:
-  ##       if i * j == 42:
-  ##         capture i, j:
-  ##           myClosure = proc () = echo fmt"{i} * {j} = 42"
-  ##   myClosure() # output: 6 * 7 == 42
-  ##   let m = @[proc (s: string): string = "to " & s, proc (s: string): string = "not to " & s]
-  ##   var l = m.mapIt(capture(it, proc (s: string): string = it(s)))
-  ##   let r = l.mapIt(it("be"))
-  ##   echo r[0] & ", or " & r[1] # output: to be, or not to be
+  ## by their current iteration values.
+  runnableExamples:
+    import std/strformat
+
+    var myClosure: () -> string
+    for i in 5..7:
+      for j in 7..9:
+        if i * j == 42:
+          capture i, j:
+            myClosure = () => fmt"{i} * {j} = 42"
+    assert myClosure() == "6 * 7 = 42"
+
   var params = @[newIdentNode("auto")]
   let locals = if locals.len == 1 and locals[0].kind == nnkBracket: locals[0]
                else: locals
   for arg in locals:
-    if arg.strVal == "result":
-      error("The variable name cannot be `result`!", arg)
-    params.add(newIdentDefs(ident(arg.strVal), freshIdentNodes getTypeInst arg))
+    proc getIdent(n: NimNode): NimNode =
+      case n.kind
+      of nnkIdent, nnkSym:
+        let nStr = n.strVal
+        if nStr == "result":
+          error("The variable name cannot be `result`!", n)
+        result = ident(nStr)
+      of nnkHiddenDeref: result = n[0].getIdent()
+      else:
+        error("The argument to be captured `" & n.repr & "` is not a pure identifier. " &
+          "It is an unsupported `" & $n.kind & "` node.", n)
+    let argName = getIdent(arg)
+    params.add(newIdentDefs(argName, freshIdentNodes getTypeInst arg))
   result = newNimNode(nnkCall)
-  result.add(newProc(newEmptyNode(), params, body, nnkProcDef))
+  result.add(newProc(newEmptyNode(), params, body, nnkLambda))
   for arg in locals: result.add(arg)
 
 since (1, 1):
-  import std / private / underscored_calls
+  import std/private/underscored_calls
 
   macro dup*[T](arg: T, calls: varargs[untyped]): T =
     ## Turns an `in-place`:idx: algorithm into one that works on
@@ -228,41 +257,39 @@ since (1, 1):
     ##
     ## This macro also allows for (otherwise in-place) function chaining.
     ##
-    ## **Since**: Version 1.2.
+    ## **Since:** Version 1.2.
     runnableExamples:
-      import algorithm
+      import std/algorithm
+
+      let a = @[1, 2, 3, 4, 5, 6, 7, 8, 9]
+      assert a.dup(sort) == sorted(a)
 
-      var a = @[1, 2, 3, 4, 5, 6, 7, 8, 9]
-      doAssert a.dup(sort) == sorted(a)
       # Chaining:
       var aCopy = a
       aCopy.insert(10)
+      assert a.dup(insert(10), sort) == sorted(aCopy)
 
-      doAssert a.dup(insert(10), sort) == sorted(aCopy)
+      let s1 = "abc"
+      let s2 = "xyz"
+      assert s1 & s2 == s1.dup(&= s2)
 
-      var s1 = "abc"
-      var s2 = "xyz"
-      doAssert s1 & s2 == s1.dup(&= s2)
+      # An underscore (_) can be used to denote the place of the argument you're passing:
+      assert "".dup(addQuoted(_, "foo")) == "\"foo\""
+      # but `_` is optional here since the substitution is in 1st position:
+      assert "".dup(addQuoted("foo")) == "\"foo\""
 
       proc makePalindrome(s: var string) =
         for i in countdown(s.len-2, 0):
           s.add(s[i])
 
-      var c = "xyz"
-
-      # An underscore (_) can be used to denote the place of the argument you're passing:
-      doAssert "".dup(addQuoted(_, "foo")) == "\"foo\""
-      # but `_` is optional here since the substitution is in 1st position:
-      doAssert "".dup(addQuoted("foo")) == "\"foo\""
+      let c = "xyz"
 
       # chaining:
-      # b = "xyz"
-      var d = dup c:
+      let d = dup c:
         makePalindrome # xyzyx
         sort(_, SortOrder.Descending) # zyyxx
         makePalindrome # zyyxxxyyz
-
-      doAssert d == "zyyxxxyyz"
+      assert d == "zyyxxxyyz"
 
     result = newNimNode(nnkStmtListExpr, arg)
     let tmp = genSym(nskVar, "dupResult")
@@ -318,9 +345,10 @@ proc collectImpl(init, body: NimNode): NimNode {.since: (1, 1).} =
   let res = genSym(nskVar, "collectResult")
   var bracketExpr: NimNode
   if init != nil:
-    expectKind init, {nnkCall, nnkIdent, nnkSym}
+    expectKind init, {nnkCall, nnkIdent, nnkSym, nnkClosedSymChoice, nnkOpenSymChoice, nnkOpenSym}
     bracketExpr = newTree(nnkBracketExpr,
-      if init.kind == nnkCall: freshIdentNodes(init[0]) else: freshIdentNodes(init))
+      if init.kind in {nnkCall, nnkClosedSymChoice, nnkOpenSymChoice, nnkOpenSym}:
+        freshIdentNodes(init[0]) else: freshIdentNodes(init))
   else:
     bracketExpr = newTree(nnkBracketExpr)
   let (resBody, keyType, valueType) = trans(body, res, bracketExpr)
@@ -344,51 +372,58 @@ macro collect*(init, body: untyped): untyped {.since: (1, 1).} =
   # analyse the body, find the deepest expression 'it' and replace it via
   # 'result.add it'
   runnableExamples:
-    import sets, tables
+    import std/[sets, tables]
+
     let data = @["bird", "word"]
+
     ## seq:
     let k = collect(newSeq):
       for i, d in data.pairs:
         if i mod 2 == 0: d
-
     assert k == @["bird"]
+
     ## seq with initialSize:
     let x = collect(newSeqOfCap(4)):
       for i, d in data.pairs:
         if i mod 2 == 0: d
-
     assert x == @["bird"]
+
     ## HashSet:
-    let y = initHashSet.collect:
+    let y = collect(initHashSet()):
       for d in data.items: {d}
-
     assert y == data.toHashSet
+
     ## Table:
     let z = collect(initTable(2)):
       for i, d in data.pairs: {i: d}
-
     assert z == {0: "bird", 1: "word"}.toTable
+
   result = collectImpl(init, body)
 
 macro collect*(body: untyped): untyped {.since: (1, 5).} =
   ## Same as `collect` but without an `init` parameter.
+  ##
+  ## **See also:**
+  ## * `sequtils.toSeq proc<sequtils.html#toSeq.t%2Cuntyped>`_
+  ## * `sequtils.mapIt template<sequtils.html#mapIt.t%2Ctyped%2Cuntyped>`_
   runnableExamples:
-    import sets, tables
-    # Seq:
+    import std/[sets, tables]
     let data = @["bird", "word"]
+
+    # seq:
     let k = collect:
       for i, d in data.pairs:
         if i mod 2 == 0: d
-
     assert k == @["bird"]
+
     ## HashSet:
     let n = collect:
       for d in data.items: {d}
-
     assert n == data.toHashSet
+
     ## Table:
     let m = collect:
       for i, d in data.pairs: {i: d}
-
     assert m == {0: "bird", 1: "word"}.toTable
-  result = collectImpl(nil, body)
\ No newline at end of file
+
+  result = collectImpl(nil, body)