I basically want this https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/nameof
I thought It would be simple (btw I have almost no knowledge of macro)
macro nameof(x: typed): string =
if x.len() > 1:
x.last().toStrLit()
else:
x.toStrLit()
This works for
but it doesn't work as expected when passing a template
proc wow() = echo "wow!"
template hey() = wow()
echo nameof(hey) # actual output: wow()
# expected output: hey
Also how can I disallow certain things to be passed like literals and proc calls
but still allowing module, type, etc... ?
Also how can I disallow certain things...
Standard library has https://nim-lang.org/docs/macros.html#expectKind%2CNimNode%2CNimNodeKind but not expect-not or expect-in.
import std/macros
proc expectKind*(n: NimNode, k: openArray[NimNodeKind]) {.compileTime.} =
if not k.contains(n.kind):
error("Expected a node of kind " & $k & ", got " & $n.kind, n)
proc expectNotKind*(n: NimNode, k: NimNodeKind) {.compileTime.} =
if n.kind == k:
error("Expected a node not of kind " & $k & ", got " & $n.kind, n)
macro nameof(x: typed): string =
expectKind(x, [nnkSym])
if x.len() > 1:
x.last().toStrLit()
else:
x.toStrLit()
echo nameof(123)
Thanks! That's cool
How about getting a template name? is it impossible?
hmm... I've tried to use untyped but most things are passed as nnkIdent
so I could do something like nameof(asdjiaiosdjoasijdosad) and it outputs "asdjiaiosdjoasijdosad"
am I missing something?
Try this:
macro nameof(x: typed): string =
var x = x
if x.kind != nnkSym:
x.expectKind(nnkDotExpr)
x = x[^1]
newLit(x.strVal)
let a = (b: (c: 1))
type foo[T] = tuple[x: T]
echo nameof(a) # "a"
echo nameof(a.b.c) # "c"
echo nameof(foo) # "foo"
#echo nameof("abc") -- error
#echo nameof(foo[int]) -- error ... exercise for the reader
Pardon that previous message ... I wasn't paying attention. Try this:
import macros
macro checkDefined(name: untyped, x: typed): string =
return name.toStrLit
macro nameof(x: untyped): string =
var name = x
if name.kind != nnkIdent:
x.expectKind nnkDotExpr
name = x[^1]
return quote do: checkDefined(`name`, `x`)
let a = (b: (c: 1))
type foo[T] = tuple[x: T]
echo nameof(a) # "a"
echo nameof(a.b.c) # "c"
echo nameof(foo) # "foo"
#echo nameof(foo[int]) # error ... exercise left to the reader
#echo nameof("abc") # error
#echo nameof(bar) # error: undefined identifier 'bar'
proc wow = discard # note that this is needed, otherwise `hey` is not defined and thus not a valid argument to nameof
template hey() = wow()
echo nameof(wow) # "wow"
echo nameof(hey) # "hey"
Use untyped instead of typed, that will get you the template.
The problem with that is that the whole point of using nameof(foo) in place of just "foo" is to typecheck foo to make sure that it is defined. As a former C#/WPF programmer, I'm aware of this and would not expect anyone else to be. nameof was clamored for by WPF programmers because WPF's display DSL, XAML, isn't type-checked and so one gets runtime errors when fields are renamed or removed--that's what nameof avoids. I don't get the point of implementing it in Nim, but it's doable and was an interesting exercise for someone (me) trying to improve their macro chops.
Here's another approach, with better error messages:
macro nameofAux(x: untyped): string =
if x.kind notin {nnkIdent, nnkDotExpr}:
let msg = "'" & x.repr & "' is not valid for nameof"
quote do: {.error: `msg`.}
else:
let name = if x.kind == nnkIdent: x else: x[^1]
name.toStrLit
template nameof(x: untyped): string =
when not compiles(x):
{.error: "'" & x.repr & "' is not defined" .}
else:
nameofAux(x)
let a = (b: (c: 1))
type Foo[T] = tuple[x: T]
var foo: Foo[int]
echo nameof(a) # "a"
echo nameof(a.b.c) # "c"
echo nameof(Foo) # "Foo"
echo nameof(foo.x) # "x"
#echo nameof(Foo[int]) # error ... exercise left to the reader
#echo nameof("abc") # error
#echo nameof(bar) # error: 'bar' is not defined
proc wow = discard # note that this is needed, otherwise hey is not defined and thus is not a valid argument to nameof, the whole point of which is to guarantee that its argument is defined
template hey() = wow()
echo nameof(wow) # "wow"
echo nameof(hey) # "hey"
but his language is more powerful than even he realizes.
wow that's so cool lol
Am I doing it right?
proc containsKind*(n: NimNode, k: openArray[NimNodeKind]): bool {.compileTime.} =
k.contains(n.kind)
macro nameofAux(x: untyped): string =
if x.kind notin {nnkIdent, nnkDotExpr, nnkBracketExpr}:
let msg = "'" & x.repr & "' is not valid for nameof"
quote do: {.error: `msg`.}
else:
if x.len() < 2: return x.toStrLit()
if x.containsKind([nnkDotExpr]):
for i in 1 .. x.len():
if x[^i].kind() == nnkDotExpr:
return x[^(i-1)].toStrLit()
elif x.containsKind([nnkBracketExpr]):
return x[0].toStrLit()
return x.last().toStrLit()
template nameof(x: untyped): string =
when compiles(x):
nameofAux(x)
else:
{.error: "'" & x.repr & "' is not defined" .}
proc wow[T]() = echo typeof(T)
func yay(): int = 10
assert nameof(wow[string]) == "wow"
assert nameof(wow) == "wow"
Am I doing it right?
I find macro programming tricky and difficult and things that seem like they should work don't, either because of a misconception or sometimes because of a compiler bug ... bang on things until they work, with plenty of test cases. Make liberal use of the debugging tools ... read the macros documentation (https://nim-lang.org/docs/macros.html) and the very good tutorial (https://nim-lang.org/docs/tut3.html) carefully. Good luck!