Nimrod logo

Meta programming

Templates

# from System.nim
template `!=` (a, b: untyped): untyped =
  not (a == b)

assert(5 != 6) # rewritten to: assert(not (5 == 6))

Templates

1
2
3
4
5
6
7
8
template html(name, body) =
  proc name(): string =
    result = "<html>"
    body
    result.add("</html>")

html mainPage:
  echo "colon syntax to pass statements to template"

Templates

Templates already suffice to implement simple DSLs:

1
2
3
4
5
6
7
8
9
10
html mainPage:
  head:
    title "The Nim programming language"
  body:
    ul:
      li "efficient"
      li "expressive"
      li "elegant"

echo mainPage()

Produces:

<html>
  <head><title>The Nim programming language</title></head>
  <body>
    <ul>
      <li>efficient</li>
      <li>expressive</li>
      <li>elegant</li>
    </ul>
  </body>
</html>

Templates

template html(name, body) =
  proc name(): string =
    result = "<html>"
    body
    result.add("</html>")

template head(body) =
  result.add("<head>")
  body
  result.add("</head>")

...

template title(x) =
  result.add("<title>$1</title>" % x)

template li(x) =
  result.add("<li>$1</li>" % x)

Templates

1
2
3
4
5
6
7
8
9
10
11
12
13
proc mainPage(): string =
  result = "<html>"
  result.add("<head>")
  result.add("<title>$1</title>" % "The Nim programming language")
  result.add("</head>")
  result.add("<body>")
  result.add("<ul>")
  result.add("<li>$1</li>" % "efficient")
  result.add("<li>$1</li>" % "expressive")
  result.add("<li>$1</li>" % "elegant")
  result.add("</ul>")
  result.add("</body>")
  result.add("</html>")

Macros

Code coverage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# This is the code our macro will produce!

var
  track = [("line 9", false), ("line 13", 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

1
2
3
4
5
6
7
8
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
## 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)