I wanted to share my "first big macro" in Nim (and it actually works, but still cleaning some things ups); has been an interesting experience:
Some helpful things I learned, in case you need/want to write your own Nim macros:
When you've got a macro where you're passing in a body: untyped
(usually the last param, and not necessarily called "body" but that's common), you can print it to the screen in the compile-time output with e.g.
echo()
echo(treeRepr body)
echo()
That gives you a prettified version of the AST.
Also, inside a macro definition, sometimes it's the easiest thing to build up an AST by doing something like:
var slist = newStmtList()
slist.add quote do:
var blah = "stuff"
echo "foo"
myProc(blah)
Inside the quote do:
block you write normal looking Nim code and you can use backticks to splice in NimNode's that you created in more procedural ways, like let barId = ident("bar")
and then in the quote block you write `barId`
and it turns into the symbol bar
.
Those ASTs you build with quote do:
can also be printed to screen during compile-time output e.g. echo(treeRepr slist)
.
So now armed with concrete ASTs printed to your screen, you can start to make more sense of https://nim-lang.org/docs/macros.html.
It starts to get clearer how to build up complex statements and expressions if you hit the limits of quote do:
.
Sometimes there's a shortcut like newStmtList()
, which is shorthand for newNimNode(nnkStmtList)
. And once you have a new node you then .add
more NimNodes to it, according to what's shown in the manual or what you're learning from looking at the ASTs you're printing during compile-time.
Finally, at the bottom of your macro you can put:
echo toStrLit(result)
And that will print to the screen during compile-time all the Nim source code (not the AST) that your macro generated.
Maybe there's even better techniques, but this is what worked for me.
This code uses the task
macro defined in the .nim file linked above as a pragma:
proc hello*[T, U](someName: string, otherStuff: int) {.task(kind=no_rts, stoppable=true).} =
echo "Hello, " & someName & $otherStuff & "!"
This is the source code generated by the macro (more or less, I think I tweaked the macro since I made the following gist): https://gist.github.com/michaelsbradleyjr/89870e55fa46745bb0e3302555360420.
In the proc hello
shown above that uses the task pragma, when you echo(treeRepr body)
you get this AST printed during the compile-time output: https://gist.github.com/michaelsbradleyjr/c9c9527a269d76e5a31272ed72036b25.
It was after looking over that and comparing with https://nim-lang.org/docs/macros.html#statements-procedure-declaration, that's when things started to click for me.