type Person = object
name: string
age: int
weight: float
for person in csv[Person]("people.csv"):
doSomething(person)
including some educate guesses about parsing dates contained therein, numerical formats and so on.
I am starting by writing a simple macro that will do for types that only contain numbers in english format and strings. First, I need - given a type such as the above - to generate a proc that will (try to) read that type out of a sequence of strings. My attempt goes like this
import macros, parseutils
proc string2int(s: string): int =
doAssert parseInt(s, result) > 0
proc string2float(s: string): float =
doAssert parseFloat(s, result) > 0
proc nthField(pos: int, field, all: NimNode): NimNode {. compileTime .} =
let
tp = getType(field)
value = newNimNode(nnkBracketExpr).add(all, newIntLitNode(pos))
var arg: NimNode
case $(tp):
of "string": arg = value
of "int": arg = newCall("string2int", value)
of "float": arg = newCall("string2float", value)
else: error("Unsupported type " & $(tp))
newNimNode(nnkExprColonExpr).add(ident($(field)), arg)
proc objectTypeName(T: NimNode): NimNode {. compileTime .} =
# BracketExpr
# Sym "typeDesc"
# Sym "Person"
getType(T)[1]
macro genPackM(t, T: typed): untyped =
let
typeSym = objectTypeName(T)
typeSpec = getType(t)
typeSym.expectKind(nnkSym)
typeSpec.expectKind(nnkObjectTy)
let param = genSym(nskParam, "s")
var
pos = 0
body = newNimNode(nnkObjConstr).add(typeSym)
for sym in typeSpec[1]:
body.add(nthField(pos, sym, param))
inc(pos)
result = newProc(
params = [typeSym,
newIdentDefs(param, newNimNode(nnkBracketExpr).add(ident("seq"), ident("string")))],
body = newStmtList(body)
)
echo treeRepr(result)
echo toStrLit(result)
proc genPack(T: typedesc): proc(s: seq[string]): T =
var t: T
return genPackM(t, T)
Unfortunately, if I try to use it like this
type Person = object
name: string
age: int
weight: float
let pack = genPack(Person)
echo pack(@["andrea", "34", "63.5"])
the compiler complains at the phase of compiling the generated code with gcc. The error is
/home/papillon/playground/nim/nimcache/expr_example.c: In function ‘genpack_100157’:
/home/papillon/playground/nim/nimcache/expr_example.c:181:26: error: expected expression before ‘)’ token
LOC1.ClPrc = ((TMP165) ()); LOC1.ClEnv = NIM_NIL;
but I am not able to follow what is going on. Note that manually putting
let pack = proc (s: seq[string]): Person =
Person(name: s[0], age: string2int(s[1]), weight: string2float(s[2]))
(which is the thing the macro generates) does work correctly. Any ideas where to go from here?
This sounds like a bug...
Pretty much any time when invalid C code is generated like this is a bug.
The compiler should raise an error rather than generating invalid C code [1], but the original problem in the code is that newProc generates a procedure definition and not a closure. This means that you'll need a name (which would go between the parentheses after TMP65 in the generated code) and will also have to return the generated procedure.
Example:
macro genPackM(t, T: typed): untyped =
let
typeSym = objectTypeName(T)
typeSpec = getType(t)
typeSym.expectKind(nnkSym)
typeSpec.expectKind(nnkObjectTy)
let param = genSym(nskParam, "s")
var
pos = 0
body = newNimNode(nnkObjConstr).add(typeSym)
for sym in typeSpec[1]:
body.add(nthField(pos, sym, param))
inc(pos)
let procname = genSym(nskProc)
result = newStmtList(
newProc(
name = procname,
params = [typeSym,
newIdentDefs(param, newNimNode(nnkBracketExpr).add(ident("seq"), ident("string")))],
body = newStmtList(body)
),
procname)
[1] Which it doesn't because without macros it's impossible to generate such an AST, so it's not a case the compiler is prepared for.