================================================================= Meta programming ================================================================= Templates ========= * templates are declarative, macros imperative .. code-block:: nim # from System.nim template `!=` (a, b: untyped): untyped = not (a == b) assert(5 != 6) # rewritten to: assert(not (5 == 6)) * more transformations - ``a > b`` is rewritten to ``b < a``. - ``a in b`` is rewritten to ``contains(b, a)``. - ``a notin b`` is rewritten to ``not (a in b)``. - ``a isnot b`` is rewritten to ``not (a is b)``. Templates ========= .. code-block:: nim :number-lines: template html(name, body) = proc name(): string = result = "" body result.add("") html mainPage: echo "colon syntax to pass statements to template" Templates ========= Templates already suffice to implement simple DSLs: .. code-block:: nim :number-lines: html mainPage: head: title "The Nim programming language" body: ul: li "efficient" li "expressive" li "elegant" echo mainPage() Produces:: The Nim programming language Templates ========= .. code-block:: nim template html(name, body) = proc name(): string = result = "" body result.add("") template head(body) = result.add("") body result.add("") ... template title(x) = result.add("$1" % x) template li(x) = result.add("
  • $1
  • " % x) Templates ========= .. code-block:: nim :number-lines: proc mainPage(): string = result = "" result.add("") result.add("$1" % "The Nim programming language") result.add("") result.add("") result.add("") result.add("") result.add("") Macros ====== * imperative AST to AST transformations * Turing complete * ``macros`` module provides an API for dealing with Nim ASTs Code coverage ============= .. code-block:: nim :number-lines: proc toTest(x, y: int) = try: case x of 8: if y > 9: echo "8.1" else: echo "8.2" of 9: echo "9" else: echo "else" echo "no exception" except IoError: echo "IoError" toTest(8, 10) toTest(10, 10) Code coverage ============= .. code-block:: nim :number-lines: proc toTest(x, y: int) = try: case x of 8: if y > 9: echo "8.1" else: ***echo "8.2"*** of 9: ***echo "9"*** else: echo "else" echo "no exception" except IoError: ***echo "IoError"*** toTest(8, 10) toTest(10, 10) Code coverage ============= .. code-block:: nim :number-lines: # This is the code our macro will produce! var track = [("line 11", false), ("line 15", false), ...] proc toTest(x, y: int) = try: case x of 8: if y > 9: track[0][1] = true echo "8.1" else: track[1][1] = true echo "8.2" of 9: track[2][1] = true echo "9" else: track[3][1] = true echo "foo" echo "no exception" except IoError: track[4][1] = true echo "IoError" Code coverage ============= .. code-block:: nim :number-lines: toTest(8, 10) toTest(1, 2) proc listCoverage(s: openArray[(string, bool)]) = for x in s: if not x[1]: echo "NOT COVERED ", x[0] listCoverage(track) Code coverage ============= .. code-block:: nim :number-lines: import macros macro cov(n: untyped): untyped = result = n echo treeRepr n cov: proc toTest(x, y: int) = try: case x of 8: if y > 9: echo "8.1" else: echo "8.2" of 9: echo "9" else: echo "foo" echo "no exception" except IoError: echo "IoError" toTest(8, 10) toTest(10, 10) Code coverage ============= :: ... TryStmt StmtList CaseStmt Ident !"x" OfBranch IntLit 8 StmtList IfStmt ElifBranch Infix Ident !">" Ident !"y" IntLit 9 StmtList [...] Else StmtList [...] OfBranch IntLit 9 StmtList Command Ident !"echo" StrLit 9 Else StmtList Command Ident !"echo" StrLit foo Command [...] ExceptBranch [...] Code coverage ============= .. code-block:: nim :number-lines: ## Code coverage macro import macros proc transform(n, track, list: NimNode): NimNode {.compileTime.} = ... macro cov(body: untyped): untyped = var list = newNimNode(nnkBracket) let track = genSym(nskVar, "track") result = transform(body, track, list) result = newStmtList(newVarStmt(track, list), result, newCall(bindSym"listCoverage", track)) echo result.toStrLit cov: proc toTest(x, y: int) = ... toTest(8, 10) toTest(10, 10) Macros ====== .. code-block:: nim :number-lines: proc transform(n, track, list: NimNode): NimNode {.compileTime.} = # recurse: result = copyNimNode(n) for c in n.children: result.add c.transform(track, list) if n.kind in {nnkElifBranch, nnkOfBranch, nnkExceptBranch, nnkElse}: let lineinfo = result[^1].lineinfo template trackStmt(track, i) = track[i][1] = true result[^1] = newStmtList(getAst trackStmt(track, list.len), result[^1]) template tup(lineinfo) = (lineinfo, false) list.add(getAst tup(lineinfo)) Macros ====== Result:: 8.1 no exception else no exception NOT COVERED coverage.nim(42,14) NOT COVERED coverage.nim(43,12) NOT COVERED coverage.nim(47,6) Macros ====== .. code-block:: nim :number-lines: proc toTest(x, y: int) = try: case x of 8: if y > 9: echo "8.1" else: ***echo "8.2"*** of 9: ***echo "9"*** else: echo "else" echo "no exception" except IoError: ***echo "IoError"*** toTest(8, 10) toTest(10, 10)