I'd like to implement an operator ... such that:
type Point = tuple[x, y: int]
let p1: Point = (2,2)
let p2: Point = (3,3)
echo sum(1, ...p1, ...p2) # expands to sum(1, 2, 2, 3, 3)
Based on https://forum.nim-lang.org/t/5518, which refers to https://stackoverflow.com/questions/48418386/tuple-to-function-arguments-in-nim, I came up with the following
import macros
macro `...`(t: typed): auto =
let ty = getTypeImpl(t)
# does not handle non-tuples at the moment
assert(ty.typeKind == ntyTuple)
var args = newNimNode(nnkArgList)
for child in ty:
expectKind(child, nnkIdentDefs)
args.add(newDotExpr(t, child[0]))
return args
func sum(xs: varargs[int]): auto =
result = 0
for x in xs:
result += x
type Point = tuple[x, y: int]
let p1: Point = (1,1)
let p2: Point = (2,2)
echo sum(...p1, ...p2)
The above program does not compile. That aside, I don't even know if it something like that would be feasible because each template instantiation produces a separate ArgList node and they'd need to be concatenated with the rest of the arguments. My gut feel from experimentation is that there's no support in the language for this use-case.
I am aware that it's possible to achieve something similar by producing a new Call node through a macro, as demonstrated by https://stackoverflow.com/a/48418876/8401696 and https://forum.nim-lang.org/t/1300#8188. I am however trying to use a template in the arguments position and those solutions are about using a template in the call position. i.e. those solutions are about calling the function through a macro which pre-processes the arguments, but I'd like to have a way of producing multiple function arguments from a macro. Is that possible?
However something like this should be possible to implement:
someCall() ... scalarParam ..* tupleParam
# expands to
someCall(scalarParam, tupleParam[0], tupleParam[1])
Finally got around to test my idea above and here's what it looks like:
import macros
proc addTupleToCall(c: NimNode, t: NimNode, temps: NimNode) =
let typ = getType(t)
var s = t
if t.kind != nnkSym:
# If it's something more complex than sym, store it in
# tmp to avoid double evaluation
s = genSym(nskLet, "tmp")
temps.add newIdentDefs(s, newEmptyNode(), t)
var a = 0
var b = 0
if $typ[0] == "tuple":
b = typ.len - 2
elif $typ[0] == "array":
a = typ[1][1].intVal
b = typ[1][2].intVal
else:
doAssert(false, "Wrong type")
for i in a .. b:
c.add(newTree(nnkBracketExpr, s, newLit(i)))
macro addToCall(a: untyped, b: varargs[typed]): untyped =
let tmps = newNimNode(nnkLetSection)
var isTuple = false
for i in 0 ..< b.len:
if i mod 2 == 0:
isTuple = bool(b[i].intVal)
else:
if isTuple:
addTupleToCall(a, b[i], tmps)
else:
a.add(b[i])
newTree(nnkStmtList, tmps, a)
proc addToCallAux(a: NimNode, b: NimNode, isTuple: bool): NimNode =
var args = @[b, newLit(isTuple)]
var n = a
while n.kind == nnkInfix and n[0].kind == nnkIdent:
if $n[0] == "<<<":
# echo 1
args.add(n[2])
args.add(newLit(false))
elif $n[0] == "<<*":
args.add(n[2])
args.add(newLit(true))
n = n[1]
result = newCall(bindSym"addToCall", n)
for i in countdown(args.high, 0):
result.add(args[i])
macro `<<<`*(a: untyped, b: untyped): untyped =
addToCallAux(a, b, false)
macro `<<*`*(a: untyped, b: untyped): untyped =
addToCallAux(a, b, true)
when isMainModule:
proc foo(a, b: int) =
echo "foo ", a, " ", b
echo() <<< 1 <<< 2 <<* (3, 4) <<* [5, 6] <<< 7
foo() <<< 5 <<< 6