diff options
Diffstat (limited to 'lib/pure/sugar.nim')
-rw-r--r-- | lib/pure/sugar.nim | 225 |
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) |