Macros and AST Manipulation
28.1 What Macros Are
Procedures that run at compile time, receive the AST (NimNode) of their arguments, and return a new AST.
import std/macros.
28.2 Inspecting ASTs
import std/macros
dumpTree:
let x = 1 + 2
# StmtList
# LetSection
# IdentDefs
# Ident "x"
# Empty
# Infix
# Ident "+"
# IntLit 1
# IntLit 2
dumpAstGen: echo "hi" # prints the code that constructs this AST
macro show(x: untyped): untyped =
echo x.treeRepr # print at compile time
echo x.repr # back to source
result = x28.3 Building ASTs
macro debugPrint(args: varargs[untyped]): untyped =
result = newStmtList()
for a in args:
result.add newCall("echo", newLit(a.repr & " = "), a)
let x = 5; let y = "hi"
debugPrint(x, y, x * 2)
# x = 5
# y = hi
# x * 2 = 1028.4 quote do and genAst
macro repeat(n: static int, body: untyped): untyped =
result = quote do:
for i in 0 ..< `n`: # backticks interpolate NimNodes / values
`body`
import std/genasts
macro assertPositive(x: untyped): untyped =
let s = x.repr
result = genAst(x, s):
if x <= 0: raise newException(ValueError, s & " must be positive")28.5 Common NimNode API
kind, len, [], add, insert, del Node navigation and editing
newIdentNode, ident("x"), newLit(v), newCall, newStmtList, newProc, newTree(nnkX, ...)
expectKind, expectLen, error("msg", node) Validation and errors strVal, intVal, floatVal
getType, getTypeImpl, getTypeInst, getImpl Type information (typed macros)
treeRepr, lispRepr, repr, astGenRepr bindSym, genSym parseStmt, parseExpr Parse a string into an AST copyNimTree, body, params, name
28.6 Macro Examples
# 1. Generate getters for all fields of an object
macro genGetters(T: typedesc): untyped =
result = newStmtList()
let impl = T.getImpl
let recList = impl[2][2]
for field in recList:
let fname = field[0]
let ftype = field[1]
let getter = ident("get" & capitalizeAscii($fname))
result.add quote do:
proc `getter`*(o: `T`): `ftype` = o.`fname`
# 2. Macro pragma: time a proc
macro timed(p: untyped): untyped =
p.expectKind nnkProcDef
let name = $p.name
let body = p.body
p.body = quote do:
let t0 = cpuTime()
defer: echo `name`, " took ", cpuTime() - t0
`body`
result = p
proc slowWork() {.timed.} = sleep(100)
# 3. Typed macro using type info
macro fieldCount(T: typedesc): int =
newLit(T.getTypeImpl[1].getTypeImpl[2].len)28.7 Macro Best Practices
Prefer procs → generics → templates → macros (least power first).
Validate input and report errors with error("...", node) so users see good messages.
Use quote/genAst for readability. Test macros with expandMacros and dumpTree.