Now there is macro hasCustomPragma, it is fine within normal proc:
template table(name: string) {.pragma.}
type
User {.table("tuser").} = object
id: int
name: string
age: int
echo User.hasCustomPragma(table)
But when I need call it in another macro, I don't know how to do it, anyone know there is a workaround, thanks.
## doesn't works
macro m1(T: typedesc): untyped =
echo T.hasCustomPragma(table)
Here are 2 ways.
There is something special about hasCustomPragma or typedesc in macros I think. For some reason the getAST version is crashing but it should work and the quote do version only works on Nim #devel.
import macros
template table(name: string) {.pragma.}
type
User {.table("tuser").} = object
id: int
name: string
age: int
echo User.hasCustomPragma(table)
# macro m1(T: typedesc): untyped =
# getAST hasCustomPragma(T, table)
# echo m1(User) # Oops crash
macro m2(T: typedesc): untyped =
result = quote do:
`T`.hasCustomPragma(table)
echo m2(User)
@mratsim Thanks. 'quote do' is not what I want. I want the code can return the result(true or false), not the code can be called in the program. For example:
macro m2(T: typedesc): untyped =
if some_proc_call_has_custom_param(T, table):
......
else:
......
Yes, it is not so difficult to implements 'hasCustomPragma' proc which can be called in the macros. But I try to find a way we can call macro from another macro. In fact, call macro in a macro with triditional macro semantics is meanless from my point of view, I think when a macro is called from another macro, it should be called just like a proc call a proc, a macro is just like a proc with parameter type is NimNode(except static[T] and typedesc type). So the following code should give same result:
import macros
template table(name: string) {.pragma.}
type
User {.table("tuser").} = object
id: int
name: string
age: int
macro m2(T: typedesc): untyped =
echo T.hasCustomPragma(table)
# output true
echo User.hasCustomPragma(table)
#should output true too
m2(User)
I am not arguing whether "call macro in a macro with traditional macro semantics" is meaningless, but it aligns with other Lisp languages (e.g. Racket). It's called higher-phase macros in Racket, and it means you can introduce macros to use in other macro definitions. To obtain the result of another macro in Racket, you need to first construct a piece of code with that, and then use local-expand on that code. I think you can do just the same with Nim's getAST. (I'm not sure, because the documentation is a bit unclear)
In your example, echo T.hasCustomPragma(table) is probably not what you want, because the returned value from an arbitrary macro should be a general NimNode.
Call a macro in another macro with macro semantics is useful? Please give me an example.
Ever used > or != or notin in a macro? These are templates, simple forms of a macro in Nim.
@slangmgh
I've already gave you one. What breeze does IS a macro with macro semantics. And just like Araq said, you can use templates or macros without even realizing it.
@Udiknedormin
Sorry, you are absolutely right! Thank you. :)
@Udiknedormin
The breeze is still a macro which build AST.
We know a macro can turn code into AST, is there any way to turn AST into code and I can call the code in a macro.
@slangmgh
Well, most of macros build AST, I'd say. What you really mean, I think, is: breeze generates code which generates AST. It's a subtle difference which is connected to your question, actually. :)
As for your question: well, it's a little tricky. Templates and macros do code->code. Proc do AST->AST. getAst makes macros behave like procs here. The general problem is: how do you want to have AST -> code when it means the caller modifies itself in runtime?
There is a solution however. The caller can't modify itself, so no AST -> code is possible. But macros operate on AST or static[...] in the meantime. So code --AST--> code --static[...]--> code is possible. Then, you can split your macros in two or more steps. Each of them can return some code (from AST) and the call to the next macro step. Like this:
import macros
macro stepTwo(input: static[int]): untyped =
echo "In step 2: "
echo "wow! normal int! ", input
echo ""
result = (2*input).newLit
macro stepOne(fun, input: typed): untyped =
echo "In step 1:"
echo fun.treeRepr
echo input.treeRepr
echo ""
result = newCall("stepTwo", newCall(fun, input))
proc incrementor(by: int): proc(x: int): int =
proc inner(x: int): int =
x + by
inner
const x = 3
static:
echo "Outside: ", stepOne(incrementor(2), x)
Here you have it. :) The integer value that stepTwo operated on was actually calculated using AST generated by macro, so you really turned AST into code while still working with macros. Please note you can move all the NimNodes you operated on through AST->code->AST (AST->code at macro return, code->AST at another macro call). You can even typecheck them if you want to (see: typed arguments). I think it might be the only way to get typed nodes from untyped ones, actually.
Sadly, no generic for static[...] is possible in macros. Oh well. Could have been worse.
To tell you the truth, I was thinking about a macro utilities library (with hope for merging with core/macros :) ) and it's quite possible I'll include a macro to do what I've just shown you, so that you could do:
macro stepOne(fun, input: typed): untyped {.withAstExec.} =
let calc = newCall(fun, input)
let num = execAst[int](calc)
result = (2*num).newLit
So now you tell me whenever using macros inside macros sounds feasible or not? ;) And now maybe a one-liner for that:
macro stepOne(fun, input: typed): untyped {.withAstExec.} =
newLit(2 * execQuote[int]( `fun`(`input`) ))
@Udiknedormin
It's just like magic!!!
I still need time to understand it.
@Udiknedormin
Sorry, silly of me, I still cannot figure out how to make the following code possible:
let calc = newCall(fun, input)
let num = execAst[int](calc) ***** howto *****
The code shows in the stepTwo, we get the result of the function, but how to return this value to stepOne?
@slangmgh
That's why I added withAstExec pragma. It should split your macro into as many steps as needed, each with all local variables of the previous one with additional argument of static[T] where T is the type you provided explicitly (int here).