type MyType = [... something complex ...]
macro( x : expr ) : stmt =
# here needs to be something that transforms MyType in a NimNode for the type.
if( x.getType.sameType(MyType) )
for some reason this doesn't compile with some error message that I do not understand
macro foobar(x: typed): stmt =
if macros.sameType( getType[float], x.getType):
echo "yea"
else:
echo "nope"
result = newStmtList()
let i = 0
foobar(i)
error:
Error: type mismatch: got (proc (n: typedesc[float]): NimNode{.noSideEffect.}, NimNode) but expected one of: macros.sameType(a: NimNode, b: NimNode)
EDIT: until here it is solved.
last but not least, for some reason it is not possible for me, to pass my dsl as something typed, but I need to know, what the type of some expressions would be in the calling context.
macrocall:
section1:
newident = a+b # I would like to know the type of a+b if it would be in the context of the caller.
Your second problem is a bit more complicated, and it's hard to give you advice without knowing exactly when your program needs to know the type of each expression. However, I'm guessing (based on your previous posts) that you're needing to bind/pass GLSL attributes/uniforms. In that case, something like the following might work for you:
import macros, typetraits # need 'typetraits' for `T.name`
proc passShaderAttr[T](name:string, value:T) =
echo "GLSL Attribute '", name, "' is ", T.name
macro section1(x:untyped): untyped =
echo x.treeRepr # check AST, prints:
# StmtList
# Asgn
# Ident !"newident"
# Infix
# Ident !"+"
# Ident !"a"
# Ident !"b"
result = newStmtList()
for n in x.children:
if n.kind == nnkAsgn:
let name = $n[0]
let asgn = n[1]
result.add quote do:
passShaderAttr(`name`, `asgn`)
macro macrocall(x:untyped): untyped =
# dummy macro, just return for now..
return x
let a = 1
let b = 2
macrocall:
section1:
newident = a + b
Output:
GLSL Attribute 'newident' is int
don't use bracket in this line:
if macros.sameType( getType[float], x.getType):
use parenthesis instead
if macros.sameType( getType(float), x.getType):
what the type of some expressions would be in the calling context?
use macros.bindSym, macros.getImpl, and then use macros.getType. because macros.bindSym only accept string literal, you need to generate intermediate macro from your first level macro, then expand the generated macro
example of multi level macro expansion:
import macros, strutils
#counter that will be used to generate unique intermediate macro name
#and avoid name collision
var macroCount {.compileTime.} = 0
#this proc is exported because of the NLBFunc macro expansion
#occured on bindFunction caller module
proc bindFuncImpl*(arg: openArray[NimNode]): NimNode {.compileTime.} =
result = newNimNode(nnkStmtList)
for n in arg:
if n.kind == nnkSym:
echo getImpl(n.symbol).treeRepr #see what the symbol actually is
#from here you can use getType/getImpl combination
else:
#overloaded symbol
for k in children(n):
echo getImpl(k.symbol).treeRepr #see what the symbol actually is
#here you can put your glue code generator
macro bindFunction*(arg: varargs[untyped]): stmt =
result = newNimNode(nnkStmtList)
#generate intermediate macro to utilize
#bindSym that can only accept string literal
let macroName = "NLBFunc" & $macroCount
var nlb = "macro " & macroName & "(): stmt =\n"
nlb.add " let procList = [\n"
var i = 0
for k in children(arg):
let comma = if i < arg.len-1: "," else: ""
nlb.add " bindSym\"$1\"$2\n" % [$k, comma]
inc i
nlb.add " ]\n"
nlb.add " result = bindFuncImpl(procList)\n"
nlb.add macroName & "()\n" #don't forget to call the intermediate macro
result.add parseStmt(nlb)
echo nlb #inspect the generated code, you can remove it safely
inc macroCount
proc mulv(a, b: int): int = discard
proc mulv(a, b: float): float = discard
proc abc(a: string): string = discard
bindFunction(mulv, abc)
#you can do something like this if you modified bindFunction properly
#bindFunction:
# mulv -> "newname"
# abc
sometimes, more than two level of macro expansion is needed to convert string/identifier into symbol using bindSym, a bit complicated, but once you know the basic principle, it's easy
ok, thanks, for the get float, part.
for the other part.
what I got so far was, I can't get it the within the same macro, I need to create code in the caller context, wher it properly get's typed and then call macros procs templates in the expanded code. That's a bit disappointing, because it means weird thinking for me.
currently I am trying to generate the code with the quote function, because I do not really like to work on strings.
this is what I have at the moment:
statement = quote do:
let uniforms: seq[ShaderParam] = @[]
for tt in uniformslist:
let lhsName = tt.lhsName
let value = tt.value
statement.add quote do:
system.add(uniforms, (name : `lhsName`, gl_type: glslUniformType(type(`value`))) )
but it doesn't compile, because in the two quote blocks, the two uniforms variables do not see each other. What I could need some variadic templates like in c++
template makeuniformsSeq(args: T ...)
let uniforms: seq[ShaderParam] = @[ (name: args.lhsName, gl_type: glslUniformType(type(args.value))) ... ]
@jangko: I am still trying what you have written.
the current bindSym implementation still has this limitation: only accept string literal, not computed string. to enable bindSym to accept computed string like other ordinary proc, the compiler internal need major restructurisation, so for the time being, the trick that i have used was using multi level macro expansion.
it will be much more simpler to write macros if the bindSym could accept computed string. but for now, it also much simpler to use the trick rather than reimplement the bindSym, many more important things need to be fixed and implemented in the compiler.
Reimplementing bindSym will have to wait(i wish not too far in the future)
Krux02: what I got so far was, I can't get it the within the same macro, I need to create code in the caller context, wher it properly get's typed and then call macros procs templates in the expanded code.
Well, yes.. but only because you're trying to assign identifiers that don't exist inside your section1 macro body. The compiler can't know which identifiers you want resolved, and which one's you don't, without you telling it explicitly. There are a couple things you might consider doing:
Example:
import macros
macro section1(x:typed): typed =
for n in x.children:
if n.kind in {nnkVarSection, nnkLetSection}:
for i in n.children:
echo i[0], " is ", getType(i[0])
macro section2(p:varargs[typed], x:untyped): typed =
for n in p:
echo n, " is ", getType(n)
# ..then determine each asgn type in `x` based on known types from `p`..
let a = 1
let b = 2
section1:
var foo: float
let bar = a + b
section2 a, b:
newident = a + b
Output:
foo is float
bar is int
a is int
b is int
Krux02: but it doesn't compile, because in the two quote blocks, the two uniforms variables do not see each other.
Yes, this is due to how quote generates the AST, apparently. However, it is very easy to work around:
let uniforms = genSym(nskLet, "uniforms")
statement = quote do:
let `uniforms`: seq[ShaderParam] = @[]
for tt in uniformslist:
...
statement.add quote do:
system.add(`uniforms`, ...)
import macros
# nim has no unit type so I use int here. void doesn't work it's not a type
# these functions are not meant to be called at all, they are just here to make valid expressions that can be passed to macros
proc shaderArg[T](name: string; value:T): int = 0
proc uniforms(args : varargs[int]): int = 0
proc attributes(args : varargs[int]):int = 0
macro inner(args:varargs[typed]) : stmt =
# just some proof that I have the types
echo args[0][1][1][0][2].getType.repr
echo args[0][1][1][1][2].getType.repr
echo args[1][1][1][0][2].getType.repr
echo args[1][1][1][1][2].getType.repr
# Now I have all macro arguments nicely grouped, but this time I have all the type information.
echo args.repr
macro outer(arg:untyped) : stmt =
result = newCall("inner")
for section in arg.items:
let call =
case $section[0].ident
of "uniforms":
newCall("uniforms")
of "attributes":
newCall("attributes")
else:
newCall($section[0].ident)
for capture in section[1].items:
capture.expectKind( {nnkAsgn, nnkIdent} )
if capture.kind == nnkAsgn:
capture[0].expectKind( nnkIdent )
call.add newCall( "shaderArg", newLit($capture[0]), capture[1] )
else:
call.add newCall( "shaderArg", newLit($capture), capture )
result.add(call)
let i = 18
let j = 123.456
let vertices : seq[ array[3,int] ] = @[ [1,2,3], [4,3,2] ]
let colors : seq[ array[3,float] ] = @[ [1.0,0.0,0.0], [0.0,0.0,1.0] ]
outer:
uniforms:
i
j
attributes:
pos = vertices
color = colors
# outer transforms to this exact call. This call can be fully type checked, and now getType of any sub-tree is legal, but this is not something you would want to write as a dsl
inner( uniforms( shaderArg("i", i), shaderArg("j",j) ), attributes( shaderArg("pos", vertices), shaderArg("color",colors) ) )
@filwit: Sadly it is not possible for me, to split the uniforms and attributes into distinct macros. the outer macro needs to generate something that depends on the content of all sections.