I'm trying to use templates to paste code:
template someCode(): untyped =
var
fname: string = "Mark"
age: int = "44"
someCode()
echo fname
The above code does not recognise the var declarations and the code below doesn't run either
template someCode(): untyped =
fname: string
age: int
type
Person = object
someCode()
First code:
Let's start with why any of this template's instantiation won't compile --- it has a type error. "44" is a string but you assign it to an int variable. In fact, Nim's type inference makes it redundant for you to specify these variables' types.
Secondly: templates provide something called hygiene. It means that what you see inside the template is a local name. In fact, a new name is generated each time a template is instantiated. For example:
template someCode(): untyped =
var
fname: string = "Mark"
age: int = 44
someCode()
someCode() # ok, this instantation's `fname` is a NEW `fname`
To avoid this and inject symbols directly into the scope of instantiation, {.inject.} pragma should be used:
template someCode(): untyped =
var
fname {.inject.}: string = "Mark"
age {.inject.}: int = 44
someCode()
someCode() # error, redefinition of `fname`
echo fname # compiles, prints "Mark"
Second code:
Unluckily, Nim's no Lisp. A template or macro call is first parsed as a call and then substituted with the code yielded. It's quite unfortunate for some cases but if your example code is more-or-less what you need, here is a solution:
template someCode(name: untyped): untyped =
type name {.inject.} = object
fname: string
age: int
By the way... Funny thing...
type name = object
fname: string # Error: cannot use symbol of kind 'var' as a 'field'
age: int
var fname = "Mark"
var age = 44
# compiles
var fname = "Mark"
var age = 44
type name = object
fname: string # Error: cannot use symbol of kind 'var' as a 'field'
age: int
# compilation aborted
It seems to me it means something is wrong with Nim's symbol lookup. Or am I missing something?
You can also mark the whole template with {.dirty.}
template someCode(): untyped {.dirty.}=
var
fname: string = "Mark"
age: int = 44
someCode()
echo fname # "Mark"
echo age # 44 Notice both symbols are visible outside the template.
See also https://nim-lang.org/docs/manual.html#templates-hygiene-in-templates
@Udiknedormin many thanks for the thorough explanation and for your correction of age from string - silly error on my part.
@stisa thanks for the heads up on dirty templates
I guess the way to generate types using templates is to insert code into a template type structure rather than inserting code block templated code into a type. I am trying this alternative method similar to the manual and using the {.inject.} pragma but it doesn't work:
template typeGen(tName, extraCode: untyped): untyped =
type
tName {.inject.} = object
fname: string
age: int
extraCode
typeGen(Employee):
id: int
var x = Employee(fname: "Mark", age: 44, id: 962536)
echo x
@dataPulverizer I think
typeGen(Employee):
id: int
is invalid syntax. You cant declare variables like id:int and templates expect valid nim code. @Udiknedormin, concerning Error: cannot use symbol of kind 'var' as a 'field':
that should be a bug, but I don't see such a behaviour with Nim 0.17.1.
No, parser cannot understand extraCode in an object declaration, in the template, which is there an invalid syntax.
I would like to substitute id: int for extra code in the template by doing:
typeGen(Employee):
id: int
I thought (incorrectly) that this is how you substitute id: int for extraCode. It clearly isn't but I'm still none the wiser on what alternations to this I should make.
Your syntax for macro's call is OK, your key-value pair is parsed as a call (id being regarded as a proc name, and int as statement list (block), passed to it). Now you need to write a macro, that can use it. In that macro you first assign to result the constant part of your type, and then add to it new nodes, via procs from macros module (search forum for examples).
import macros
template obj(tName: untyped): untyped =
type
tName = object
fname: string
age: int
macro typeGen(tName, extraCode: untyped): untyped =
result = getAst(obj(tName))
echo result.treeRepr # so you can see, where and how you add fields
echo extraCode.treeRepr # so you can see, from where and how you get them
# and here you parse ``extraCode`` and add additional nodes to result[0][0][2][2];
# as is now, it will create a type with just 2 fields in the template
typeGen(Employee):
id: int
Your syntax for macro's call is OK
Exactly.
@slimshady, @dataPulverizer: General tip: if a macro call is passed to dumpTree as a block argument and that compiles and prints the call as a (pre-symbol-binding) AST, then the call syntax is ok:
import macros
dumpTree:
typeGen(Employee):
id: int
@LeuGim That's bad. I'm using Nim 1.17.2 so it seems to imply the bug was created between 1.17.1 and 1.17.2, I guess...
As for the macro: the syntax is ok, it's the template body that generated the error. It is entirely possible to create a macro to do what you wanted to do in your type (I've once done something similar). It's a little more complex but still possible.
If you only need one extra field, you could write the template like this:
template typeGen(tName, name, kind: untyped): untyped =
type
tName {.inject.} = object
fname: string
age: int
name: kind
typeGen(Employee, id, int)
But rarely makes sense. If you have some idea in mind, we can come up with ways to macro/template it.The above code was just an example. In general I would like to be able to paste code using templates or macros in a similar way that you can with D's string mixins which allow you to generate strings using functions and then paste them anywhere and generate code in compile time. In the above code I would like to end up with something like this
type
MyType = object
fname: string
age: int
extraCode
extraCode could be any number of lines of code that extend the type generated at compile time. But in general I would like to be able to paste code anywhere.
Well, you have no choice than use macros. If you don't want to deal with the ast, you can still work with strings:
import macros, strutils
macro typeGen(tName, extra: untyped): untyped =
var typeStr = """
type
tName = object
fname: string
age: int"""
typeStr = replace(typeStr, "tName", $tName)
add(typeStr, "\l ")
for f in extra:
add(typeStr, repr(f[0]) & ':' & repr(f[1][0]))
add(typeStr, "\l ")
return parseStmt(typeStr)
typeGen Test:
a: bool
b: byte
It seems to me it means something is wrong with Nim's symbol lookup. Or am I missing something?
I failed to guess what example you used to produce this error. This compiles:
template someCode(name: untyped): untyped =
type name {.inject.} = object
fname: string
age: int
someCode somename
var fname = "Mark"
var age = 44
I think this is the code he was referring to.
template someCode(name: untyped): untyped =
var fname = "Mark"
var age = 44
type name = object
fname: string # Error: cannot use symbol of kind 'var' as a 'field'
age: int # Error: cannot use symbol of kind 'var' as a 'field'
# compilation aborted
someCode somename
though maybe it's not supposed to work with how symbol binding works in templates.
Both of these work and are probably what he intended to do.
template someCode(name: untyped): untyped =
var fname {.inject.} = "Mark"
var age {.inject.} = 44
type name = object
fname: string
age: int
someCode somename
template someCode(name: untyped): untyped {.dirty.} =
var fname = "Mark"
var age = 44
type name = object
fname: string
age: int
someCode somename
import macros
macro someCode(name : untyped) : untyped =
var fname = ident"fname"
var age = ident"age"
result = quote do:
var fname = "Mark"
var age = 44
type `name` = object
`fname`: string
`age` : int
#echo treeRepr result
#echo repr result
someCode somename
const fltk = "fltk-c-1.3.3-64.dll" type long = int64 type Fl_ButtonEx* = object proc Fl_ButtonExNew (x: long, y: long, w: long, h: long, title: cstring=nil): ptr Fl_ButtonEx {.cdecl, importc: "Fl_ButtonExNew", dynlib: fltk, discardable.} proc Fl_ButtonExDelete(x: ptr Fl_ButtonEx) {.cdecl, importc: "Fl_ButtonExDelete", dynlib: fltk, discardable.} type Fl_BoxEx* = object proc Fl_BoxExNew (x: long, y: long, w: long, h: long, title: cstring=nil): ptr Fl_BoxEx {.cdecl, importc: "Fl_BoxExNew", dynlib: fltk, discardable.} proc Fl_BoxExDelete(x: ptr Fl_BoxEx) {.cdecl, importc: "Fl_BoxExDelete", dynlib: fltk, discardable.}
After studying above text, I came to
import macros, strutils const fltk = "fltk-c-1.3.3-64.dll" type long = int64 macro DeclareEx*(name) : untyped = var typeStr = """ type nameEx* = object proc nameExNew*(x: long, y: long, w: long, h: long, title: cstring=nil): ptr nameEx {.cdecl, importc: "nameExNew", dynlib: fltk, discardable.} proc nameExDelete*(x: ptr nameEx) {.cdecl, importc: "nameExDelete", dynlib: fltk, discardable.} """ typeStr = replace(typeStr, "name", $name) return parseStmt(typeStr) DeclareEx(Fl_Button) DeclareEx(Fl_Box)
but I get
stack trace: (most recent call last) d.nim(15, 19) DeclareEx ..\..\msys64\home\USER\_nim\nim\lib\core\macros.nim(514, 17) parseStmt d.nim(17, 10) template/generic instantiation of `DeclareEx` from here ..\..\msys64\home\USER\_nim\nim\lib\core\macros.nim(514, 17) Error: unhandled exception: ..\..\msys64\home\USER\_nim\nim\lib\core\macros.nim(514, 5) Error: identifier expected, but got 'keyword proc'
so any solution? thanks