Skip to content

Instantly share code, notes, and snippets.

@michaelsbradleyjr
Last active June 11, 2021 00:50
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save michaelsbradleyjr/7d8bbc2d84d439486d36194ee5464106 to your computer and use it in GitHub Desktop.
Save michaelsbradleyjr/7d8bbc2d84d439486d36194ee5464106 to your computer and use it in GitHub Desktop.

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:

https://github.com/status-im/nim-status/blob/feat/examples/client/examples/chat/task_runner/macros.nim

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment