In nim I have a macro that takes in a proc definition and generates a proc based on that and some statements. The syntax looks like this:
type A = object
name: string
type B = object
name: string
id: int
macro generateMapper(body: untyped): untyped =
let procDefNode: NimNode = body[0]
... macro code ...
generateMapper():
proc myMapProc(source: A, target: B): string =
target.name = source.name
I can fetch the proc definition out of the body into procDefNode
as shown. The procDefNode.treeRepr
is:
ProcDef
Ident "myMapProc"
Empty
Empty
FormalParams
Ident "string"
IdentDefs
Ident "source"
Ident "A"
Empty
IdentDefs
Ident "target"
Ident "B"
Empty
Empty
Empty
StmtList
... some body statements that don't matter right now...
This shows that I can access the nodes of my parameter types, which are A
and B
.
What I want now is to generate a list from the node for B
of all the fields that type has. In this example that would be @["name", "id"]
.
But how do I do that?
I've skimmed through std/macros and getImpl
seems like the thing I want, but I can't figure out how to make it work.
This:
macro generateMapper(body: untyped): untyped =
let procDefNode: NimNode = body[0]
let typeNode = procDefNode[3][2][1] # 3 is the node for FormalParams, 2 is the Node of the second parameter and 1 there is the type-Node of the second parameter
echo getImpl(typeNode)
Just errors out with Error: node is not a symbol
.
So how do I get from the node ident "B"
aka typeNode
to all of its fields in the shape of @["name", "id"]
?
Thanks to hugagranstrom over on the nim forum I found the answer.
The clue is as he stated to have the NimNode
that the macro accepts for the proc-definition be of type typed
instead of untyped
.
Making that NimNode
typed applies some restrictions to what statements you can write, as the semantics are checked to be consistent. So e.g. a lambda definition must have all necessary parts of a lambda definition.
However, it does allow the compiler to not just view variables as simple "identifiers" but as "Symbols" (Aka NimNodes of kind nnkSym
) whose definition you can fetch using getImpl
.
An Untyped NimNode parameter does not have those restrictions but at the same time can not provide Symbols the way Typed does.
So this works:
type A = object
name: string
type B = object
name: string
id: int
macro generateMapper(procDef: typed, specialMappings: untyped): untyped =
echo procDef[0][0].getImpl().treeRepr ## [0][0] is the Symbol of the *resultType*
let mapAToB = generateMapper(proc(source: A, source2: int): B):
result.id = source2
For reference, this is the node representation of procDef
:
ProcTy # This is what we have in `procDef`
FormalParams
Sym "B" # Symbol of Result Type
IdentDefs # Box representing the first Parameter
Sym "source"
Sym "A"
Empty
IdentDefs # Box representing the second Parameter
Sym "source2"
Sym "int"
Empty
Empty
So procDef[0]
is the node FormalParams
and FormalParams[0]
is the node Sym "B"
, which is the result-type of the proc definition.
With getImpl
as stated we can get the type, whose treeRepr in this case looks like this:
# from `echo procDef[0][0].getImpl().treeRepr`
TypeDef
Sym "B"
Empty
ObjectTy
Empty
Empty
RecList # Box for list of Fields of type called "B"
IdentDefs # Box for first Field
Ident "name"
Sym "string"
Empty
IdentDefs # Box for second Field
Ident "id"
Sym "int"
Empty