So, the next step in my quest in Nim learning is to create a yet another vector type:
type
Vector*[N: static[int], T] = array[N, T]
In addition to accessing the vector elements as, say, v[0], I'd like to offer the possibility to access them like v.x. I did this:
proc x*[N: static[int], T](v: Vector[N,T]): T =
result = v[0]
proc `x=`*[N: static[int], T](v: var Vector[N,T], val: T) =
v[0] = val
Nice. Simple and concise.
The problem is, I'd also like to support this:
v.x += 1.0
I can make this work by changing my definition of x, making it take and return var s:
proc x*[N: static[int], T](v: var Vector[N,T]): var T =
result = v[0]
The problem with this approach is that then x works only with mutable vectors. I can lo longer say
let v = [1.0, 2.0, 3.0]
echo v.x
I tried defining both versions of x (with and without var), but it seems that "overloading by var" doesn't work as I expected (I get "ambiguous call" errors).
I also tried using term rewriting macros, but couldn't devise anything that works (and it seemed like an overcomplicated solution).
Any idea on how can I solve this?
(Bonus question: I suppose that dot operators would be the best language feature to implement swizzling. Does this make sense?)
As a workaround, you can turn the x procedure into a template, i.e.
template x*[N: static[int], T](v: Vector[N,T]): T =
v[0]
This should allow for v.x += 1 to work, too.
import macros
type Vector*[N: static[int], T] = array[N, T]
template x*[N,T](v: Vector[N,T]): T = v[0]
template y*[N,T](v: Vector[N,T]): T = v[1]
template z*[N,T](v: Vector[N,T]): T = v[2]
template w*[N,T](v: Vector[N,T]): T = v[3]
macro `.`[N, T](v: Vector[N,T], field: string): expr =
result = newNimNode nnkBracket
for c in field.strVal:
result.add newDotExpr(v, newIdentNode($c))
proc `$`[N,T](v: Vector[N,T]): string =
result = "["
for i, e in v:
if i > 0: result.add ", "
result.add($e)
result.add "]"
var v: Vector[3, float] = [1.0, 2.0, 3.0]
v.x *= 2
echo v.x
let u = [1.0, 2.0, 3.0]
echo u.x
let vec: Vector[4, float] = [1.0, 2.0, 3.0, 4.0]
echo vec.wwxy
Edit: To figure out how to create the macro, use dumpTree: [v.w, v.w, v.x, v.y], which gives the AST you have to create:
StmtList
Bracket
DotExpr
Ident !"v"
Ident !"w"
DotExpr
Ident !"v"
Ident !"w"
DotExpr
Ident !"v"
Ident !"x"
DotExpr
Ident !"v"
Ident !"y"
Hi,
I was able to return to this only today, and I really must thank you all for the explanations and code snippets. Great stuff, really!
My humble constribution to the thread:
macro `.=`*[N, M, T](v: Vector[N,T], field: string, rhs: Vector[M,T]): expr = ##
## Implements swizzling for writing.
result = newStmtList()
var i = 0
for c in field.strVal:
let dotExpr = newDotExpr(v, newIdentNode($c))
var assignment = newAssignment(dotExpr, rhs[i])
result.add(assignment)
inc(i)
var v2 = [0.0, 1.0, 2.0, 3.0]
doAssert(v2.x == 0.0)
doAssert(v2.y == 1.0)
doAssert(v2.z == 2.0)
doAssert(v2.w == 3.0)
v2.xy = [8.0, 9.0]
doAssert(v2.x == 8.0)
doAssert(v2.y == 9.0)
doAssert(v2.z == 2.0)
doAssert(v2.w == 3.0)
Perhaps not an award-winning piece of code, but I enjoyed being able to write it :-)
macro `.=`*[N, M, T](v: Vector[N,T], field: string, rhs: Vector[M,T]): expr = ##
## Implements swizzling for writing.
result = newStmtList()
for i, c in field.strVal:
let dotExpr = newDotExpr(v, newIdentNode($c))
let assignment = newAssignment(dotExpr, rhs[i])
result.add(assignment)