# # # Nim's Runtime Library # (c) Copyright 2015 Dominik Picheta # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## This module implements nice syntactic sugar based on Nim's ## macro system. import std/private/since import std/macros proc checkPragma(ex, prag: var NimNode) = since (1, 3): if ex.kind == nnkPragmaExpr: prag = ex[1] ex = ex[0] proc createProcType(p, b: NimNode): NimNode = result = newNimNode(nnkProcTy) var formalParams = newNimNode(nnkFormalParams).add(b) p = p prag = newEmptyNode() checkPragma(p, prag) case p.kind of nnkPar, nnkTupleConstr: for i in 0 ..< p.len: let ident = p[i] var identDefs = newNimNode(nnkIdentDefs) case ident.kind of nnkExprColonExpr: identDefs.add ident[0] identDefs.add ident[1] else: identDefs.add newIdentNode("i" & $i) identDefs.add(ident) identDefs.add newEmptyNode() formalParams.add identDefs else: var identDefs = newNimNode(nnkIdentDefs) identDefs.add newIdentNode("i0") identDefs.add(p) identDefs.add newEmptyNode() formalParams.add identDefs result.add formalParams result.add prag macro `=>`*(p, b: untyped): untyped = ## Syntax sugar for anonymous procedures. It also supports pragmas. 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"] name = newEmptyNode() kind = nnkLambda pragma = newEmptyNode() p = p checkPragma(p, pragma) if p.kind == nnkInfix and p[0].kind == nnkIdent and p[0].eqIdent"->": params[0] = p[2] p = p[1] checkPragma(p, pragma) # check again after -> transform case p.kind of nnkPar, nnkTupleConstr: var untypedBeforeColon = 0 for i, c in p: var identDefs = newNimNode(nnkIdentDefs) case c.kind of nnkExprColonExpr: let t = c[1] since (1, 3): # + 1 here because of return type in params for j in (i - untypedBeforeColon + 1) .. i: params[j][1] = t untypedBeforeColon = 0 identDefs.add(c[0]) identDefs.add(t) identDefs.add(newEmptyNode()) of nnkIdent: identDefs.add(c) identDefs.add(newIdentNode("auto")) identDefs.add(newEmptyNode()) inc untypedBeforeColon of nnkInfix: if c[0].kind == nnkIdent and c[0].eqIdent"->": var procTy = createProcType(c[1], c[2]) params[0] = procTy[0][0] for i in 1 ..< procTy[0].len: params.add(procTy[0][i]) else: error("Expected proc type (->) got (" & c[0].strVal & ").", c) break else: error("Incorrect procedure parameter.", c) params.add(identDefs) of nnkIdent: var identDefs = newNimNode(nnkIdentDefs) identDefs.add(p) identDefs.add(ident"auto") identDefs.add(newEmptyNode()) params.add(identDefs) else: error("Incorrect procedure parameter list.", p) result = newProc(body = b, params = params, pragmas = pragma, name = name, procType = kind) macro `->`*(p, b: untyped): untyped = ## Syntax sugar for procedure types. It also supports pragmas. 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) macro dump*(x: untyped): untyped = ## Dumps the content of an expression, useful for debugging. ## It accepts any expression and prints a textual representation ## of the tree representing the expression - as it would appear in ## source code - together with the value of the expression. ## ## 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 result = quote do: debugEcho `s`, " = ", `x` 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 + nonexistant) 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 = # Replace NimIdent and NimSym by a fresh ident node # see also https://github.com/nim-lang/Nim/pull/8531#issuecomment-410436458 proc inspect(node: NimNode): NimNode = case node.kind: of nnkIdent, nnkSym: result = ident($node) of nnkEmpty, nnkLiterals: result = node else: result = node.kind.newTree() for child in node: result.add inspect(child) result = inspect(ast) 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. 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)) result = newNimNode(nnkCall) result.add(newProc(newEmptyNode(), params, body, nnkLambda)) for arg in locals: result.add(arg) since (1, 1): 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, without modifying its input. ## ## This macro also allows for (otherwise in-place) function chaining. ## ## **Since:** Version 1.2. runnableExamples: import std/algorithm let a = @[1, 2, 3, 4, 5, 6, 7, 8, 9] assert a.dup(sort) == sorted(a) # Chaining: var aCopy = a aCopy.insert(10) assert a.dup(insert(10), sort) == sorted(aCopy) let s1 = "abc" let s2 = "xyz" assert 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") result.add newVarStmt(tmp, arg) underscoredCalls(result, calls, tmp) result.add tmp 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, 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]) = 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]) = 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())]) 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} bracketExpr = newTree(nnkBracketExpr, if init.kind == nnkCall: 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 seqs/sets/tables. ## ## 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 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 = 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. runnableExamples: 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 # avoid `collect` when `sequtils.toSeq` suffices: assert collect(for i in 1..3: i*i) == @[1, 4, 9] # ok in this case assert collect(for i in 1..3: i) == @[1, 2, 3] # overkill in this case from std/sequtils import toSeq assert toSeq(1..3) == @[1, 2, 3] # simpler result = collectImpl(nil, body)