Is it possible to create a macro with a variable count and named parameters?
Consider this:
import macros
macro test(n: varargs[untyped]): untyped =
for x in n.children:
echo x.repr
discard test(1)
discard test(1,2)
discard test(1,b=2) # error
discard test(a=1,b=2) # error
The above results in:
1
2
unpackvar.nim(9, 13) Error: type mismatch: got (int literal(1), b: int literal(2))
but expected one of:
unpackvar.test(n: varargs[untyped])
I don't expect that. I want it :) The code was just an example of how it "may could have worked".
Afaik there is a structured way for the AST which a macro can generate to create a named parameter call to use for a proc call. As such I expected (somehow) that a macro being called with named parameters can reach out for those names, because the children of the varargs contain these representation.
That may be not possible because of how stuff really works (parameter matching?),
This is why I asked if it is possible to get it work (in any way).
BTW my current solution works with pseudo stub proc (thanks again @araq) named after the default value:
proc internalPrintText(text: string, r: int = 0, b: int = 0, g: int = 0, alpha: int = 255) = ...
macro printText(n: varags[untyped]): untyped = ...
proc rgb(r,g,b: int) = discard
proc alpha(a: int) = discard # dummies needed for varargs to work
# calls:
printText("Text")
printText("Red",255,0,0)
printText("Red",rgb(255,0,0))
printText("Black 50%",alpha(128))
printText("Blue 50%",rgb(0,0,255),alpha(128))
This works by letting the macro create a proc call where the "alpha(128)" gets translated to
internalPrintText("transparency 50%",alpha:128)
and the rgb(1,2,3) gets split into single parameters: red=1,green=2,blue=3.
In reality there are procs which have 15 parameters where like 12 are usually being default values.
I know that I could solve that with creating real proc's for all cases. Thats not what I am looking for (as there would be probably thousands of extra definitions to have the wanted flexibility).
In addition to this the generated code would probably need intermediate objects for stuff like "rgb()" which is just eyecandy similar to:
#define rgb(r,g,b) (r), (g), (b)
void main(void) {
printf("Test %d,%d,%d", rgb(1,2,3));
}
I even have a macro which "unwraps" the first parameter from the varags and uses a given type to simulate "methods" for an object. That works really well and looks great in code to just have
somethig.add("test", pos(1,1), marked(true))
instead of
something.add("test", 1, 1, 0, 0, 0, 255, true, nil)
where add is
proc add(obj: Something, lft: int = 0, top: int = 0, red: int = 0, green: int= 0, blue: int= 0, alpha: int= 255, marked: bool, userdata: pointer)
I also know that I could try to create a DSL which supports stuff like this but I would like to have the "looks" of normal code and not multiple statements. Actually I want the look being "the same" as if I had defined all those special procs for real.
Starting point:
macro immediateM(x: untyped) {.immediate} =
let args = callsite()
for a in 1..<args.len:
if a[i].kind == nnkExprEqExpr:
echo "key=value pair "
else:
echo "unnamed argument"
result = x
immediateM(keyA="foo", keyB="bar", 80, 42)
Oderwat: I don't expect that. I want it :-)
I got that, but I was thrown off by the specificity of n: varargs[untyped] and I wasn't aware that {.immediate.} suppresses all checking of a match between the call site arguments and the macro parameters.
I messed with Araq's code to get it to compile:
import macros
macro immediateM(x: untyped): stmt {.immediate} =
let args = callsite()
for i in 1..<args.len:
if args[i].kind == nnkExprEqExpr:
echo "key=value pair"
else:
echo "unnamed argument"
immediateM(keyA="foo", keyB="bar", 80, 42)
Well this does not work for me as I need type matching for the first parameter to simulate the "method" of an object. See:
import macros
template declareUnpackingMacro*(what, nimname, extname) =
macro nimname*(x: what): stmt {.immediate.} =
var s: string = astToStr(extname)& "("
let args = callsite()
var first = true
for i in 1..<args.len:
if first:
first = false
else:
s.add(',')
if args[i].kind == nnkExprEqExpr:
s.add(repr(args[i][0]) & '=' & repr(args[i][1]))
else:
s.add(args[i].repr)
s.add(')')
#echo "made: ", x, s
result = parseStmt(s)
proc internalFooInt*(a: int=0, b: int=0, keyA: string="defint") =
echo "int ", locals()
proc internalFooFloat*(a: float= 0.0,b: int=0 ,c: int=0, keyA: string="deffloat", keyB: string="") =
echo "float ", locals()
declareUnpackingMacro(int, foo, internalFooInt)
declareUnpackingMacro(float, foo, internalFooFloat)
let a* = 12
let b* = 12.0
a.foo(2, keyA="bla")
b.foo(2)
Output:
immediate_m.nim(37, 2) Info: template/generic instantiation from here
lib/core/macros.nim(306, 15) Error: type mismatch: got (float, int literal(2))
but expected one of:
immediate_m.internalFooInt(a: int, b: int, keyA: string)
If you use just one of both types if works. And yes, I know that the "what" parameter does not make any sense here with the immediate macro. It's just to illustrate how I would call it if it would work :)
So if one has to use immediate for the macro I think there is no way to make it still react on the type of the first parameter and call different external functions.
This would forbid to have calls like "obj.delete(...)" for different types. It seems I can either have working named parameters or "object types". I may be forced to work with the way I implemented default parameter as described in post #4.
This looks similar to this and associates the unpacking macro with the specified type.
template doUnpackingT(what,nimname,extname) =
macro nimname*(p: what, n: varargs[untyped]): untyped =
var s: string = astToStr(extname) & "(" & repr(p)
for x in n.children:
.....
doUnpackingT(int, foo, fooInt)
doUnpackingT(float, foo, fooInt)
But then I can't have named parameters besides of "hacking" them in with stub procs as explained above (and currently used and working in my real code).
@araq: Would it be possible to get varargs[untyped] to work with named parameters or is this totally impossible (or stupid to ask for)?
Would it be possible to get varargs[untyped] to work with named parameters?
Consider it done.
Update: This now compiles, but requires more testing:
macro m(x: varargs[untyped]): expr =
...
m(some=12, pointless="xyz", names="abc")
OK. It compiles but this breaks my code now :(
import macros
macro m(x: varargs[untyped]): expr =
echo repr(x)
for c in x.children:
echo repr(c)
result = parseStmt("echo \"done\"")
m(some=12, pointless="xyz", names="abc")
Output:
[]
done
Is it me who does something wrong here?
Much better but for some reason this happens:
(just some snippets)
result = wxButton(parent, wxID_ANY, text, 0, 0, -1, -1, 0)
...
template wxcUnpacking(nimname,extname) =
macro nimname*(n: varargs[untyped]): expr =
echo repr(n)
var s: string = astToStr(extname) & "("
var first = true
for x in n.children:
var unpack = false
if x.kind in nnkCallKinds:
case $ x[0]
# "default parameter proxies"
of "hgt", "wdt", "top", "lft", "stl", "opt", "flg", "udt", "alpha":
expectLen(x, 2)
if first:
first = false
else:
add(s, ", ")
add(s, $x[0] & "=" & repr(x[1]))
continue
of "wxPoint":
expectLen(x, 3)
...
[parent, wxID_ANY, text, 0, 0, (system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|...) 1,
(system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|...) 1,
0]
stack trace: (most recent call last)
/Users/notthearaq/devsys/workspace/nim/wxcnim/src/wxunpacking.nim(32) wxButton
lib/core/macros.nim(673) $
lib/system.nim(3148) failedAssertImpl
lib/system.nim(3140) raiseAssert
lib/system.nim(2358) sysFatal
basic_window.nim(168, 23) template/generic instantiation from here
basic_window.nim(96, 20) template/generic instantiation from here
lib/system.nim(2358, 7) Error: unhandled exception: false Invalid node kind nnkOpenSymChoice for macros.`$`
Gladly I found that it works when I change macro nimname*(n: varargs[untyped]): expr = to macro nimname*(n: varargs[expr]): expr =
UPDATE: But using expr wont accept default parameters it seems:
basic_window.nim(132, 29) Error: type mismatch: got (int literal(-1), label: string)
but expected one of:
wxcnim.wxMenuItemEx(n: varargs[expr])
I tried to replicate that with less code which is basically the same and using varargs[untyped]. But that just works as expected (as does -1 work in other of my unpacking macros).
import macros
type WxButton = ptr object
proc wxButton_Create*(prt: pointer, id: int, txt: string, lft: int, top: int, wdt: int, hgt: int, stl: int): WxButton =
result = nil
macro wxButton(x: varargs[untyped]): untyped =
echo repr(x)
for c in x.children:
#echo repr(c)
if c.kind in nnkCallKinds:
case $c[0]:
of "foo": echo "foo!"
else: discard
result = parseStmt("wxButton_Create(nil, 0, \"test\", 0,0, -1,-1, 0)")
proc makeButton(a: string): WxButton =
result = wxButton(nil, 0, a, 0,0, -1,-1, 0)
let x: WxButton = makeButton("Test")
echo repr(x)
You nailed it... $ converts it to a string ... BUT that is:
[parent, wxID_ANY, text, 0, 0, (system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|...) 1,
(system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|...) 1,
0]
Which creates this call by the macro :)
wxButton_Create(parent, wxID_ANY, text, 0, 0, (system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|...) 1, (system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|...) 1, 0)
stack trace: (most recent call last)
/Users/hara/devsys/workspace/nim/wxcnim/src/wxunpacking.nim(82) wxButton
lib/core/macros.nim(307) parseStmt
basic_window.nim(165, 23) template/generic instantiation from here
basic_window.nim(16, 20) template/generic instantiation from here
lib/core/macros.nim(307, 17) Error: unhandled exception: lib/core/macros.nim(306, 186) Error: expression expected, but found ')'
Why is this (system.-|system.-|... generated for "- 1" as parameter? It does not do it in other cases nor in the "minimal" example I showed in the previous post.
P.S.: If that helps I could give you access to the full code.
@Araq I found the minimal code to reproduce this strange problem and created an issue on github for it: https://github.com/nim-lang/Nim/issues/3064
I also post it here:
import macros
macro newButton(x: varargs[untyped]): int =
echo repr(x)
# change `proc` to e.g. pointer and it will work as expected
proc makeButton(cb: proc): bool =
result = newButton(-1)
proc callBack() = discard
discard makeButton(callBack)
Output:
[(system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|system.-|...) 1]