Hello everyone.
I found possible in Nim to do that:
type
Foo = tuple
a: int
b: float
var f: Foo = (123, 0.98)
echo f[0]
## result: `123`
But it's not correct for objects:
type
Foo = object
a: int
b: float
var f: Foo = Foo(a:123, b:0.98)
echo f["a"]
## Error: could not resolve: [](f, "a")
type
Foo = object
a: int
b: float
var f: Foo = Foo(a:123, b:0.98)
echo f[0]
### Error: could not resolve: [](f, 0)
Is there any way to get object field (property) dynamically by their names? May be macros?
System module has iterator fieldPairs:
iterator fieldPairs[T](x: T): RootObj
Iterator return only field names with type string and it's values. Is it possible to get references to object fields in the same way?
Please!
The tuple example is misleading, as the index has to be a constant. The typeinfo module has some reflective utilities, but be warned that they carry a non-trivial amount of overhead. Macros can indeed also do some things that normal Nim code can't do.
Could you explain what the actual problem is that you are trying to solve? Perhaps then I could offer some more help.
Of course, I could.
I mean something like:
proc setField[T: object,V](a:var T, b:string, c:V) =
a[b] = c
type
Foo = object
a,b = int
var f = Foo(a:123,b:321)
var js: tuple[name: string, age: int]
## get js value from http
try:
f[js.name] = js.age
## or f.setField(js.name,js.age)
except:
discard
## as variant
if f.has(js.name):
f[js.name] = js.age
## and also get request for N:string property of Foo
## then reply with `f[N]` if it exists
That is in principle possible, but it'd be very expensive. As an alternative, you could write a macro that generates a [] accessor, e.g. so that you have:
proc `[]=`(obj: var Foo, field: string, value: int) =
case index
of "a": obj.a = value
of "b": obj.b = value
else: raise newException(FieldError, "no such field: " & field)
That macro would take a type as its argument and use macros.getType to retrieve the field elements and generate such a procedure.
In general, however, this is like trying to fit a square peg into a round hole (i.e. try to do dynamic typing in a statically typed language). It can be made to work, but it's not clear if it's worth the effort.
Thanks a lot.
I can not understand macros in Nim enough yet. Can anyone make wide manual for macros?
I implemented something as exercise for myself.
Here the GIST with the code: https://gist.github.com/oderwat/854dc491b396ed2d0f67 if somebody wants to pick it up for real work.
It looks like this:
import macros
type Foo = object
a: int
b: int
c: float
#[
dumpTree:
proc `[]=`(obj: var Foo, field: string, value: int) =
case field
of "a": obj.a = value
of "b": obj.b = value
else: raise newException(FieldError, "no such field: " & field)
dumpTree:
proc `[]`(obj: Foo, field: string): int =
case field
of "a": return obj.a
of "b": return obj.b
else: raise newException(FieldError, "no such field: " & field)
]#
macro makeStringAccessObj(obj: stmt, fld: stmt): stmt =
var objType = obj.getType
result = newStmtList()
#[
proc newProc(name = newEmptyNode(); params: openArray[NimNode] = [newEmptyNode()];
body: NimNode = newStmtList(); procType = nnkProcDef): NimNode {.
compileTime, raises: [], tags: [].}
]#
let procName1 = newNimNode(nnkAccQuoted).add(newIdentNode("[]="))
var body1 = newStmtList()
var params1: seq[NimNode] = @[newEmptyNode()] # no return type
params1.add(newIdentDefs(ident("obj"), newNimNode(nnkVarTy).add(ident(obj.repr))))
params1.add(newIdentDefs(ident("field"), ident("string")))
params1.add(newIdentDefs(ident("value"), ident(fld.repr)))
var casof1 = newNimNode(nnkCaseStmt);
casof1.add(ident("field"))
var ty = objType
while ty[1].kind == nnkSym:
ty = getType(ty[1])
if ty.kind != nnkObjectTy or ty[1].kind != nnkRecList:
error "can't create named accessors"
for child in ty[1].children:
if fld.repr == repr(child.getType()):
var branch = newNimNode(nnkOfBranch)
branch.add(newStrLitNode($child))
let ass = newAssignment(newDotExpr(ident("obj"), ident($child)), ident("value"))
branch.add(newStmtList(ass))
casof1.add(branch)
let call1 = newCall(ident("newException"), ident("FieldError"),
infix(newStrLitNode("No such field: "), "&", ident("field")));
let els = newNimNode(nnkElse).add(newStmtList().add(newNimNode(nnkRaiseStmt).add(call1)))
casof1.add(els)
body1.add(casof1)
result.add(newProc(procName1, params1, body1))
let procName2 = newNimNode(nnkAccQuoted).add(newIdentNode("[]"))
var body2 = newStmtList()
var params2: seq[NimNode] = @[ident(fld.repr)] # returns int
params2.add(newIdentDefs(ident("obj"), newNimNode(nnkVarTy).add(ident(obj.repr))))
params2.add(newIdentDefs(ident("field"), ident("string")))
var casof2 = newNimNode(nnkCaseStmt);
casof2.add(ident("field"))
for child in ty[1].children:
if fld.repr == repr(child.getType()):
var branch = newNimNode(nnkOfBranch)
branch.add(newStrLitNode($child))
let ass = newNimNode(nnkReturnStmt).add(newDotExpr(ident("obj"), ident($child)))
branch.add(newStmtList(ass))
casof2.add(branch)
casof2.add(copyNimTree(els)) # same as in proc1
body2.add(casof2)
result.add(newProc(procName2, params2, body2))
#echo result.treeRepr()
makeStringAccessObj(Foo, int)
var o = Foo( a: 1, b: 2 )
echo o["b"], ' ', o.b
o["a"] = 3
o.b = 4
echo o.a, ' ', o["b"]
let x = "a"
let y = "b"
o[x]= o[x] + o[y] * 2
echo o.a, ' ', o.b
echo o.c, ' ', o["c"] # runtime error
And consists of a macro which creates a wrapper proc for [] and []= at once. So you can use a string for access and assignment to the fields.
[UPDATE] I changed it, that you can have multiple types in the object. Still the accessor can only handle one type and will not work for the other fields. Not sure if that limitation can be removed (without an additional wrapping layer)
Of course this creates a lot of run time overhead. But it may come handy for something!
Probably solution (from here):
import std/macros
macro getField(obj: object, fld: string): untyped =
## Turn ``obj.getField("fld")`` into ``obj.fld``.
newDotExpr(obj, newIdentNode(fld.strVal))