I am trying to write a macro which will define a getter and setter for a field. The getter/setter should be called Name. However, when I define the setter the procedure name is 'Name=' and the macro seems to be trying to look for the value of Name=. How do I place 'Name=' in the macro so the whole thing is interpreted as a procedure name?
More specifically with the following code
import macros
type
Test_Type = object of RootObj
Name_HIDDEN: string
macro Name_Macro*(vType: untyped): untyped =
quote do:
proc Name*(self: var `vType`): string =
return self.Name_HIDDEN
proc `Name=`*(self: var `vType`, vIndex: string) =
self.Name_HIDDEN = vIndex
Name_Macro(Test_Type)
var tTest: Test_Type
tTest.Name = "Hello"
echo tTest.Name
I get the following error:
test.nim(14, 15) Error: undeclared identifier: 'Name'
candidates (edit distance, scope distance); see '--spellSuggest':
(1, 4): 'name'
You got most of it right, what you're running into is that quote do is using backticks to identify where to insert nodes, but you also need backticks to define operators (which Name= is).
There's around 3 solutions for what you're facing, here they are in order of recommendation:
Elegantbeef from nim's discord server wants to point out there's also the method of using std/genast which is superior to quote do and has better semantics for situations like this:
import std/[macros, genasts]
type
Test_Type = object of RootObj
Name_HIDDEN: string
macro Name_Macro*(vType: untyped): untyped =
genast(vType):
proc Name*(self: var vType): string =
return self.Name_HIDDEN
proc `Name=`*(self: var vType, vIndex: string) =
self.Name_HIDDEN = vIndex
Name_Macro(Test_Type)
var tTest: Test_Type
tTest.Name = "Hello"
echo tTest.Name
Basically just make a NimNode that contains the operator name ahead of time and insert that.
import macros
type
Test_Type = object of RootObj
Name_HIDDEN: string
macro Name_Macro*(vType: untyped): untyped =
let setterName = nnkAccQuoted.newTree("Name".ident, "=".ident)
quote do:
proc Name*(self: var `vType`): string =
return self.Name_HIDDEN
proc `setterName`*(self: var `vType`, vIndex: string) =
self.Name_HIDDEN = vIndex
Name_Macro(Test_Type)
var tTest: Test_Type
tTest.Name = "Hello"
echo tTest.Name
PMunch from nim's discord server got this to work.
Quote do has an extra parameter that is rarely used. With it you can change how places to insert nodes are identified. That way you can just use backticks:
import macros
type
Test_Type = object of RootObj
Name_HIDDEN: string
macro Name_Macro*(vType: untyped): untyped =
quote("&") do:
proc Name*(self: var `&vType`): string =
return self.Name_HIDDEN
proc `Name=`*(self: var `&vType`, vIndex: string) =
self.Name_HIDDEN = vIndex
Name_Macro(Test_Type)
var tTest: Test_Type
tTest.Name = "Hello"
echo tTest.Name
I would recommend if you're on Nim 2.0 to ditch untyped when you can.
Your macro is outputting something that is valid code, which means you can just omit the output parameter. Not defining an output type is identical to that output type being typed
or untyped
. For the output type both mean the same since the compiler needs to check if its valid code anyway, so the specification is unnecessary.
You also accept input that is valid code, which means you can just change that type to be typed
.
It's useful to have typed
as a general preference, because it enables nnkSym
nodes, which allow you to jump to the implementation of a type and more, it's generally more powerful.
So your macro could be called macro Name_Macro*(vType: typed) =