diff options
Diffstat (limited to 'lib/pure/sugar.nim')
-rw-r--r-- | lib/pure/sugar.nim | 377 |
1 files changed, 205 insertions, 172 deletions
diff --git a/lib/pure/sugar.nim b/lib/pure/sugar.nim index d2bdb0fdc..90ba20c13 100644 --- a/lib/pure/sugar.nim +++ b/lib/pure/sugar.nim @@ -11,18 +11,15 @@ ## macro system. import std/private/since -import macros, typetraits +import std/macros proc checkPragma(ex, prag: var NimNode) = since (1, 3): if ex.kind == nnkPragmaExpr: prag = ex[1] - if ex[0].kind == nnkPar and ex[0].len == 1: - ex = ex[0][0] - else: - ex = ex[0] + 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) @@ -56,14 +53,25 @@ proc createProcType(p, b: NimNode): NimNode {.compileTime.} = result.add prag macro `=>`*(p, b: untyped): untyped = - ## Syntax sugar for anonymous procedures. - ## - ## .. code-block:: nim - ## - ## proc passTwoAndTwo(f: (int, int) -> int): int = - ## f(2, 2) + ## Syntax sugar for anonymous procedures. It also supports pragmas. ## - ## passTwoAndTwo((x, y) => x + y) # 4 + ## .. warning:: Semicolons can not be used to separate procedure arguments. + runnableExamples: + proc passTwoAndTwo(f: (int, int) -> int): int = f(2, 2) + + assert passTwoAndTwo((x, y) => x + y) == 4 + + type + Bot = object + call: (string {.noSideEffect.} -> string) + + var myBot = Bot() + + myBot.call = (name: string) {.noSideEffect.} => "Hello " & name & ", 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"] @@ -80,16 +88,6 @@ macro `=>`*(p, b: untyped): untyped = checkPragma(p, pragma) # check again after -> transform - since (1, 3): - if p.kind == nnkCall: - # 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 @@ -121,11 +119,11 @@ macro `=>`*(p, b: untyped): untyped = error("Expected proc type (->) got (" & c[0].strVal & ").", c) break else: - error("Incorrect procedure parameter list.", c) + 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) @@ -136,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) @@ -156,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 = @@ -176,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 @@ -186,57 +213,83 @@ proc freshIdentNodes(ast: NimNode): NimNode = result.add inspect(child) result = inspect(ast) -template distinctBase*(T: typedesc): typedesc {.deprecated: "use distinctBase from typetraits instead".} = - ## reverses ``type T = distinct A``; works recursively. - typetraits.distinctBase(T) - 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: - 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 - ## a copy and returns this copy. - ## **Since**: Version 1.2. + ## a copy and returns this copy, without modifying its input. + ## + ## This macro also allows for (otherwise in-place) function chaining. + ## + ## **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]) + + let c = "xyz" + + # chaining: + let d = dup c: + makePalindrome # xyzyx + sort(_, SortOrder.Descending) # zyyxx + makePalindrome # zyyxxxyyz + assert d == "zyyxxxyyz" result = newNimNode(nnkStmtListExpr, arg) let tmp = genSym(nskVar, "dupResult") @@ -244,153 +297,133 @@ since (1, 1): underscoredCalls(result, calls, tmp) result.add tmp - -proc transLastStmt(n, res, bracketExpr: NimNode): (NimNode, NimNode, NimNode) {.since: (1, 1).} = +proc trans(n, res, bracketExpr: NimNode): (NimNode, NimNode, NimNode) {.since: (1, 1).} = # Looks for the last statement of the last statement, etc... case n.kind - of nnkIfExpr, nnkIfStmt, nnkTryStmt, nnkCaseStmt: + of nnkIfExpr, nnkIfStmt, nnkTryStmt, nnkCaseStmt, nnkWhenStmt: result[0] = copyNimTree(n) result[1] = copyNimTree(n) result[2] = copyNimTree(n) - for i in ord(n.kind == nnkCaseStmt)..<n.len: - (result[0][i], result[1][^1], result[2][^1]) = transLastStmt(n[i], res, bracketExpr) + for i in ord(n.kind == nnkCaseStmt) ..< n.len: + (result[0][i], result[1][^1], result[2][^1]) = trans(n[i], res, bracketExpr) of nnkStmtList, nnkStmtListExpr, nnkBlockStmt, nnkBlockExpr, nnkWhileStmt, nnkForStmt, nnkElifBranch, nnkElse, nnkElifExpr, nnkOfBranch, nnkExceptBranch: result[0] = copyNimTree(n) result[1] = copyNimTree(n) result[2] = copyNimTree(n) if n.len >= 1: - (result[0][^1], result[1][^1], result[2][^1]) = transLastStmt(n[^1], res, bracketExpr) + (result[0][^1], result[1][^1], result[2][^1]) = trans(n[^1], + res, bracketExpr) of nnkTableConstr: result[1] = n[0][0] result[2] = n[0][1] + if bracketExpr.len == 0: + bracketExpr.add(ident"initTable") # don't import tables if bracketExpr.len == 1: - bracketExpr.add([newCall(bindSym"typeof", newEmptyNode()), newCall( - bindSym"typeof", newEmptyNode())]) + bracketExpr.add([newCall(bindSym"typeof", + newEmptyNode()), newCall(bindSym"typeof", newEmptyNode())]) template adder(res, k, v) = res[k] = v result[0] = getAst(adder(res, n[0][0], n[0][1])) of nnkCurly: result[2] = n[0] + if bracketExpr.len == 0: + bracketExpr.add(ident"initHashSet") if bracketExpr.len == 1: bracketExpr.add(newCall(bindSym"typeof", newEmptyNode())) template adder(res, v) = res.incl(v) result[0] = getAst(adder(res, n[0])) else: result[2] = n + if bracketExpr.len == 0: + bracketExpr.add(bindSym"newSeq") if bracketExpr.len == 1: bracketExpr.add(newCall(bindSym"typeof", newEmptyNode())) template adder(res, v) = res.add(v) result[0] = getAst(adder(res, n)) +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, nnkClosedSymChoice, nnkOpenSymChoice, nnkOpenSym} + bracketExpr = newTree(nnkBracketExpr, + 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) + if bracketExpr.len == 3: + bracketExpr[1][1] = keyType + bracketExpr[2][1] = valueType + else: + bracketExpr[1][1] = valueType + let call = newTree(nnkCall, bracketExpr) + if init != nil and init.kind == nnkCall: + for i in 1 ..< init.len: + call.add init[i] + result = newTree(nnkStmtListExpr, newVarStmt(res, call), resBody, res) + macro collect*(init, body: untyped): untyped {.since: (1, 1).} = - ## Comprehension for seq/set/table collections. ``init`` is - ## the init call, and so custom collections are supported. - ## - ## The last statement of ``body`` has special syntax that specifies - ## the collection's add operation. Use ``{e}`` for set's ``incl``, - ## ``{k: v}`` for table's ``[]=`` and ``e`` for seq's ``add``. + ## Comprehension for seqs/sets/tables. ## - ## The ``init`` proc can be called with any number of arguments, - ## i.e. ``initTable(initialSize)``. + ## The last expression of `body` has special syntax that specifies + ## the collection's add operation. Use `{e}` for set's `incl`, + ## `{k: v}` for table's `[]=` and `e` for seq's `add`. + # 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 - # analyse the body, find the deepest expression 'it' and replace it via - # 'result.add it' - let res = genSym(nskVar, "collectResult") - expectKind init, {nnkCall, nnkIdent, nnkSym} - let bracketExpr = newTree(nnkBracketExpr, - if init.kind == nnkCall: init[0] else: init) - let (resBody, keyType, valueType) = transLastStmt(body, res, bracketExpr) - if bracketExpr.len == 3: - bracketExpr[1][1] = keyType - bracketExpr[2][1] = valueType - else: - bracketExpr[1][1] = valueType - let call = newTree(nnkCall, bracketExpr) - if init.kind == nnkCall: - for i in 1 ..< init.len: - call.add init[i] - result = newTree(nnkStmtListExpr, newVarStmt(res, call), resBody, res) - -when isMainModule: - since (1, 1): - import algorithm - - var a = @[1, 2, 3, 4, 5, 6, 7, 8, 9] - doAssert dup(a, sort(_)) == sorted(a) - doAssert a.dup(sort) == sorted(a) - #Chaining: - var aCopy = a - aCopy.insert(10) - doAssert a.dup(insert(10)).dup(sort()) == sorted(aCopy) - import random + result = collectImpl(init, body) - const b = @[0, 1, 2] - let c = b.dup shuffle() - doAssert c[0] == 1 - doAssert c[1] == 0 +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 std/[sets, tables] + let data = @["bird", "word"] - #test collect - import sets, tables + # seq: + let k = collect: + for i, d in data.pairs: + if i mod 2 == 0: d + assert k == @["bird"] - let data = @["bird", "word"] # if this gets stuck in your head, its not my fault - assert collect(newSeq, for (i, d) in data.pairs: (if i mod 2 == 0: d)) == @["bird"] - assert collect(initTable(2), for (i, d) in data.pairs: {i: d}) == {0: "bird", - 1: "word"}.toTable - assert initHashSet.collect(for d in data.items: {d}) == data.toHashSet + ## HashSet: + let n = collect: + for d in data.items: {d} + assert n == data.toHashSet - let x = collect(newSeqOfCap(4)): - for (i, d) in data.pairs: - if i mod 2 == 0: d - assert x == @["bird"] + ## Table: + let m = collect: + for i, d in data.pairs: {i: d} + assert m == {0: "bird", 1: "word"}.toTable - # bug #12874 - - let bug1 = collect( - newSeq, - for (i, d) in data.pairs:( - block: - if i mod 2 == 0: - d - else: - d & d - ) - ) - assert bug1 == @["bird", "wordword"] - - import strutils - let y = collect(newSeq): - for (i, d) in data.pairs: - try: parseInt(d) except: 0 - assert y == @[0, 0] - - let z = collect(newSeq): - for (i, d) in data.pairs: - case d - of "bird": "word" - else: d - assert z == @["word", "word"] + result = collectImpl(nil, body) |