Building DSLs
29.1 Why Nim Excels at DSLs
Flexible syntax (command calls, do blocks, operator definitions), untyped macro arguments, compile-time code generation, and zero runtime overhead.
29.2 Example: an HTML DSL
import std/macros
macro html(body: untyped): string =
proc build(n: NimNode): NimNode =
case n.kind
of nnkCall, nnkCommand:
let tag = $n[0]
var inner = newLit("")
for i in 1 ..< n.len:
let child = if n[i].kind == nnkStmtList: n[i] else: newStmtList(n[i])
for c in child:
inner = infix(inner, "&", build(c))
result = infix(infix(newLit("<" & tag & ">"), "&", inner), "&", newLit("</" & tag & ">"))
of nnkStrLit: result = n
else: result = prefix(n, "$")
result = newLit("")
for stmt in body: result = infix(result, "&", build(stmt))
echo html:
ul:
li "one"
li "two"
# <ul><li>one</li><li>two</li></ul>29.3 Real-World DSL Examples
Karax (VDOM HTML DSL), Jester routes (routes: get "/": resp "hi"), Nimble tasks, unittest suite/test, std/strscans (scanf), pattern matching (fusion/matching), Arraymancer tensor slicing syntax, npeg (PEG parser generator DSL), cligen (auto-CLI from proc signatures).
29.4 Term-Rewriting Macros and Patterns (Advanced, Rarely Used)
Term-rewriting macros (TRMs) are experimental and discouraged for general code.
template optMul{`*`(a, 2)}(a: int): int = a + a # rewrite x * 2 → x + x