I'm trying to grok how macros work and I'm a bit confused.Is it possible to evaluate an AST w/in a macro? In other words once I build up an AST, can I evaluate it within the macro so I can apply additional procs to the results of the evaluation before returning the results?
For example, I'm trying to write a macro which will take this syntax and return two tables: 1) one mapping left values to right values and 2) the other mapping right values to left values.
# Desired notation:
let (a,b) = DualMap:
1 <-> "one"
2 <-> "two"
# desired output
a = {1: "one", 2: "two"}
b = {"one":1, "two":2}
Here's what I've got so far which builds up the desired table constructors correctly. But now I want to call tables.toTable on the two constructed tables (=arrays) before returning the results. Is this possible to do w/in a macro?
import macros, table
macro DualMap(body: untyped): untyped =
body.expectKind nnkStmtList
result = newStmtList()
var tbl1 = newTree(nnkTableConstr)
var tbl2 = newTree(nnkTableConstr)
for child in body:
if child.kind == nnkInfix and $child[0] == "<->":
var e1 = newColonExpr(child[1], child[2])
var e2 = newColonExpr(child[2], child[1])
tbl1.add(e1)
tbl2.add(e2)
result.add = newPar(tbl1, tbl2)
# but would really like to do result.add(newPar(tbl1.toTable, tbl2.toTable))
Everything is possible withing a macro. Hold my beer :P.
Note, regarding macro for DSL, you might find the following repo useful:
Thanks for the pointer to the repo. I figured out I can do what I want in a couple of ways, using either ident or bindSym as follows:
result.add newPar(newCall(ident"toTable", tbl1),
newCall(ident"toTable", tbl2))
or
result.add newPar(newCall(bindSym"toTable", tbl1),
newCall(bindSym"toTable", tbl2))
Are these the preferred idioms? What's the practical difference between ident and bindSym? Several notes in the macros manual seem to suggest ident is deprecated. Is that correct?
ident as you use it is not deprecated. It's actually prefered to its alias newIdentNode.
The ident that is deprecated is probably linked to the NimIdent type which has been deprecated.
If you can, use bindSym, it will bind the identifier with what is visible in the module of the macro. Using ident instead will bind the identifier with what is visible at the call site of the macro. Ident is useful if you want to do (compile-time) late-bindings.
Example of when you would want to use bindSym:
# File ex_macro.nim
import macros
proc private_foo() =
echo "I'm foo"
macro wrap_foo_ident*(): untyped =
result = newCall(ident"private_foo") # Error: undeclared identifier: 'private_foo'
macro wrap_foo_bindsym*(): untyped =
result = newCall(bindSym"private_foo")
# file ex_usage.nim
import ./ex_macro
# wrap_foo_ident()
wrap_foo_bindsym()
Example on late bindings coming.
Late bindings example for an interface:
# File ex_macro.nim
import macros
type MyBinaryFunc* = enum
Add
Mul
Equal
# Here "myAdd", "myMul" and "myEqual" are not visible in this scope
# so we need late bindings
let Mapping* {.compileTime.} = [
Add: ident"myAdd",
Mul: ident"myMul",
Equal: ident"myEqual"
]
import ./ex_macro
import macros
proc myAdd(a, b: int): int =
a + b
proc myMul(a, b: int): int =
a * b
proc myEqual(a, b: int): bool =
a == b
macro dispatch(kind: static MyBinaryFunc, a, b: int): untyped =
result = newCall(Mapping[kind], a, b)
echo dispatch(Add, 2, 3)
echo dispatch(Mul, 2, 3)
echo dispatch(Equal, 2, 3)