Trying out nim. I'm used to C# Attributes and ability to iterate them to operate on types and methods. Usually attribute should be added to proc or type and have arguments with some user provided data.
Previously I looked into pragmas and macros AST to replicate same functionality in nim and they look overcomplicated for my purposes. After some tweaking it appears proc and global sequence might be what I need. Proc call is just 1 line with arguments pretty close to C# Attribute syntax.
So can this be done with proc or there is another approach?
import tables
type
ButtonAttr* = object of RootObj
label*: string
onClick*: proc()
ProcAttr* = object of RootObj
procPointer*: pointer
attrs*: seq[RootObj]
var
buttonAttrs*: seq[ButtonAttr] = @[]
procAttrs*: Table[pointer, ProcAttr] = initTable[pointer, ProcAttr]()
proc getOrAddProcAttr*(procPointer: pointer): var ProcAttr =
if not procAttrs.hasKey(procPointer):
procAttrs[procPointer] = ProcAttr(procPointer: procPointer, attrs: @[])
result = procAttrs[procPointer]
proc buttonAttr*(label: string, onClick: proc()) =
let buttonAttr = ButtonAttr(label: label, onClick: onClick)
buttonAttrs.add(buttonAttr)
let procPointer = addr onClick
var methodAttr = getOrAddProcAttr(procPointer)
methodAttr.attrs.add(buttonAttr)
proc a() =
echo "hello a"
echo "adding a ", $(cast[uint64](a))
buttonAttr("a", a)
proc b() =
echo "hello b"
echo "adding b ", $(cast[uint64](b))
buttonAttr("b", b)
when isMainModule:
for key, value in procAttrs.pairs:
echo "key: ", $(cast[uint64](key)), " value: ", value
#adding a 4330180280
#adding b 4330183624
#key: 6136786448 value: (procPointer: ..., attrs: @[])
Look into how Nim UI libraries do this with custom DSLs etc. Don't try to code C# in Nim, C# focusses on runtime dynamism, Nim on compile-time mechanisms.
That said, custom annotations that you extract via macros is Nim's way. "overcomplicated" ... well you have to learn them but it's worth it.
Thank you, I'll look into UI libraries for custom DSLs.
If it is doable at compile-time it's great. Currently shaping functionality using familiar language parts, and will improve after it works.
What bothers me when dealing with AST is how many possibilities there are to write same thing. I'd be happy to work with results of AST at later stage after semantic analysis.
I've fixed errors from previous post. Now it collects proc attributes into a table and can be iterated per proc. Most errors were because of not understanding how var, let, ref work.
import tables
type ProcAttr* = ref object of RootObj
procPointer*: pointer
attrs*: seq[ref RootObj]
var procAttrs*: Table[pointer, ProcAttr] = initTable[pointer, ProcAttr]()
proc getOrAddProcAttr*(procPointer: pointer): var ProcAttr =
if not procAttrs.hasKey(procPointer):
procAttrs[procPointer] = ProcAttr(procPointer: procPointer, attrs: @[])
return procAttrs[procPointer]
type ButtonAttr* = ref object of RootObj
label*: string
onClick*: proc()
proc buttonAttr*(label: string, onClick: proc(), procPointer: pointer) =
let buttonAttr = ButtonAttr(label: label, onClick: onClick)
var procAttr = getOrAddProcAttr(procPointer)
procAttr.attrs.add(buttonAttr)
proc `$`*(attr: ProcAttr): string =
result = "ProcAttr(procPointer: " & $(cast[uint64](attr.procPointer)) & ", attrs.len: " & $attr.attrs.len & ")"
when isMainModule:
proc hello1() =
echo "Hello1"
buttonAttr("1", hello1, hello1)
buttonAttr("1_2", hello1, hello1)
proc hello2() =
echo "Hello2"
buttonAttr(label="2", onClick=hello2, procPointer=hello2)
for value in procAttrs.values:
echo value
for attr in value.attrs:
let buttonAttr = cast[ButtonAttr](attr)
echo "label: ", buttonAttr.label, " .onClick:"
buttonAttr.onClick()
#ProcAttr(procPointer: 4343355792, attrs.len: 1)
#label: 2 .onClick:
#Hello2
#ProcAttr(procPointer: 4343355700, attrs.len: 2)
#label: 1 .onClick:
#Hello1
#label: 1_2 .onClick:
#Hello1
In the end what I'd like to make is serialized object inspector, UI will be built automatically from type fields information. Users usually won't edit UI directly, and instead add attributes to Type parts.
User selects serialized file, its type is used to build UI, fields are filled with serialized data, after tweaking user presses save to serialize changes back into file. Then user selects another file with any other serialized object type. If this already exists in nim I'd be happy to learn about it.