Re: Help getting this macro to work in a for loop
Thank you! That was a very good explanation; it now helps me understand why macros cannot work in for loops.
Re: Help getting this macro to work in a for loop
Thank you! I am a real beginner in Nim macros. Your example snippet will serve really helpful to me as I am learning this more. \--- I'd also like to thank @JasperJenkins (from Gitter) for offering his solution to this problem on IRC here: [https://irclogs.nim-lang.org/14-06-2019.html#18:34:24](https://irclogs.nim-lang.org/14-06-2019.html#18:34:24) Here's the Nim playground link he shared: [https://play.nim-lang.org/index.html?ix=1LMY](https://play.nim-lang.org/index.html?ix=1LMY) For convenience, that solution is inlined below: import macros {.experimental: "forLoopMacros".} type Obj = object a, b, c: int proc replace(n: NimNode, symbolic, real: NimNode): NimNode = if n.kind == nnkIdent and n.eqIdent(symbolic): # this identifier is the for loop variable: return a real field ident instead return real else: result = n # return unchanged verbatim # continure recursion on children for i in 0 ..< n.len: n[i] = replace(n[i], symbolic, real) macro replaceField(forLoop: ForLoopStmt): untyped = echo "\nCode before:\n", toStrLit(forLoop).strVal result = newTree(nnkStmtList) let forVar = forLoop[0] # k fields = forLoop[1][1] # ["a", "b", "c"] stmts = forLoop[2] # loop body for f in fields: # Copy the loop body since the loop gets unrolled. # We need a block so any variables don't collide. let stmtsCopy = newBlockStmt(copyNimTree(stmts)) # recursively replace the forVar with a new identifer. result.add(replace( stmtsCopy, # this fields copy of the stmts forVar, # identifier to replace ident(f.strVal))) # new identifer made from one of the real obj fields echo "\nCode after:", toStrLit(result).strVal, "\n" var obj = Obj(a: 1, b: 2, c: 3) echo obj for k in replaceField(["a", "b", "c"]): # more elaborate example that would fail without a block statement let num = obj.k for i in 1 .. num: obj.k *= i echo obj Run \--- This is awesome! I now have more than one solutions to understand more complex macros than the `quote do` ones.
Re: Help getting this macro to work in a for loop
try this: import macros proc replaceSymAndIdent(a : NimNode, b : NimNode, c : NimNode, isLit : static[bool] = true) = for i in 0..len(a)-1: if a[i].kind == nnkSym: if ident(strVal(a[i])) == b: a[i] = c elif a[i].kind == nnkClosedSymChoice or a[i].kind == nnkOpenSymChoice: if ident(strVal(a[i][^1])) == b: a[i] = c elif a[i].kind == nnkIdent: if a[i] == b: a[i] = c elif a[i].len != 0: if a[i].kind == nnkDotExpr: when not isLit: if a[i][0].kind == nnkSym: if ident(strVal(a[i][0])) == b: a[i][0] = c elif a[i][0].kind == nnkClosedSymChoice or a[i][0].kind == nnkOpenSymChoice: if ident(strVal(a[i][0][^1])) == b: a[i][0] = c elif a[i][0].kind == nnkIdent: if a[i][0] == b: a[i][0] = c else: replaceSymAndIdent(a[i], b, c, isLit) macro iterateSeq(a : untyped, b : static[seq[string]], code : untyped) : untyped = let a = if a.kind == nnkSym: ident($a) elif a.kind == nnkClosedSymChoice or a.kind == nnkOpenSymChoice: ident($a[^1]) else: a #echo type(a) result = newStmtList() for item in b: var newCode = code.copy() replaceSymAndIdent(newCode, a, newLit(item), isLit = true) result.add(newCode) #echo treeRepr result #echo repr result #echo "" #result = newStmtList() macro assignField*(obj, fieldName, value: typed) : untyped = let fieldSym = newIdentNode($`fieldName`) result = quote do: `obj`.`fieldSym`=`value` type SomeObj* = object a* : int b* : int c* : int var foo = SomeObj() iterateSeq(field, @["a", "c"]): assignField(foo, field, 50) echo foo assignField(foo, "a", 100) assignField(foo, "c", 200) block: assignField(foo, "b", 300) echo foo Run Also, I noticed you were missing a return type on your macro so I don't know how your code compiled at all.
Re: Help getting this macro to work in a for loop
This is not possible with a macro. Macros are limited to compile time only, and what you want to do is access the fields at run time. Here is the code inline for reference: import macros macro assignField*(obj, fieldName, value: typed) = let fieldSym = newIdentNode($`fieldName`) result = quote do: `obj`.`fieldSym`=`value` type SomeObj* = object a*: int b*: int c*: int var foo = SomeObj() for field in @["a", "c"]: # this doesn't work assignField(foo, field, 50) # /usercode/in.nim(19, 14) template/generic instantiation of `assignField` from here # /usercode/in.nim(5, 12) Error: attempting to call undeclared routine: 'field=' # this works assignField(foo, "a", 100) assignField(foo, "c", 200) block: # this works assignField(foo, "b", 300) echo foo Run In `assignField(foo, field, 50)` above in the for loop, the compiler can only infer the name you passed directly to the macro, which is `field`. So what the compiler is doing is trying to call `foo.field = 50`, but field doesn't exist. Since you are trying to use a seq to loop through (`for field in @["a", "c"]:`), the compiler can't use that information since at run time a seq may change. In theory, you could use a constant array, but I was unable to get it to work since it seems that for loops always execute at runtime from the compiler's point of view.
Re: Help getting this macro to work in a for loop
Macros expand at compile time, while for loops execute at run time. You need to loop it inside a macro.
Help getting this macro to work in a for loop
Hello, ftsf on IRC and I were trying to get a macro work that helps assign value to a Nim object field where the field is specified by a string value. Discussion: [https://irclogs.nim-lang.org/14-06-2019.html#17:39:56](https://irclogs.nim-lang.org/14-06-2019.html#17:39:56) We almost have a solution: [https://play.nim-lang.org/index.html?ix=1LMK](https://play.nim-lang.org/index.html?ix=1LMK) How can that be fixed? Thanks!