Hello all.

TLDR: Is there a way to access all variables in the current scope (anything defined in the enclosing `{}`

), much like `@This()`

(combined with `@field`

) gives access to all variables in the current struct/file?

Also welcome, of course, are ideas on how to achieve what I’m trying to do in a ‘better’ way.

After writing some basic linear algebra code in zig, I found myself wanting for some operator overloading. Of course, zig does not have this available.

After thinking about this a while, since zig has quite powerful compile-time code execution, I got the idea to try to get as close as possible to the usual mathematical formulas (in which addition, scalar multiplication &c are overloaded) at compile time.

In particular, I’m trying to write the mathematical formulas inside a comptime-known string, parse this string (at compile-time), and return a function (still at compile-time) which, when called at run-time, executes (evaluates) the contents of the string with the runtime values of the relevant variables.

As an example, if I have two vectors `v`

and `w`

, and two numbers (`f32`

, say) `a`

and `b`

, and I’m trying to assign `x=av+bw`

, then instead of

```
var x = add_vector_vector(multiply_number_vector(a, v), multiply_number_vector(b, w));
```

I would write

```
var x: V3 = undefined;
comptime_math("x=a*v+b*w")();
```

In fact, I got this implemented (mod some parsing code) as follows:

```
const std = @import("std");
const V3 = struct {
x: f32,
y: f32,
z: f32,
};
const M3 = struct{
_11: f32, _12: f32, _13: f32,
_21: f32, _22: f32, _23: f32,
_31: f32, _32: f32, _33: f32,
};
fn dot(v: V3, w: V3) f32 {
return v.x * w.x + v.y * w.y + v.z * w.z;
}
fn mul_matrix_vector(A: M3, v: V3) V3 {
return V3{
.x = A._11 * v.x + A._12 * v.y + A._13 * v.z,
.y = A._21 * v.x + A._22 * v.y + A._23 * v.z,
.z = A._31 * v.x + A._32 * v.y + A._33 * v.z,
};
}
fn mul_const_vector(alpha: f32, v: V3) V3 {
return V3{
.x = alpha * v.x,
.y = alpha * v.y,
.z = alpha * v.z,
};
}
fn sub_vector_vector(v: V3, w: V3) V3 {
return V3{
.x = v.x - w.x,
.y = v.y - w.y,
.z = v.z - w.z,
};
}
fn comptime_math(comptime string: []const u8) fn() void {
comptime {
// Here there'd be a proper parser.
// This terrible code is just for example's sake.
var it = std.mem.split(u8, string, "=");
const lhs = it.next().?;
const rhs = it.next().?;
it = std.mem.split(u8, rhs, "-");
const leftsub = it.next().?;
const rightsub = it.next().?;
it = std.mem.split(u8, leftsub, "*");
const llmul = it.next().?;
const lrmul = it.next().?;
it = std.mem.split(u8, rightsub, "*");
const rlmul = it.next().?;
const rrmul = it.next().?;
const outer = @This(); // global scope (the next 'up' struct is this file)
return struct {
fn f() void {
const lmulresult = mul_matrix_vector(@field(outer, llmul), @field(outer, lrmul));
const rmulresult = mul_const_vector(@field(outer, rlmul), @field(outer, rrmul));
const subresult = sub_vector_vector(lmulresult, rmulresult);
@field(outer, lhs) = subresult;
}
}.f;
}
}
// global (!!) variables
var p = V3{ .x = 1, .y = 2, .z = 3 };
var q: V3 = undefined;
const B = M3{
._11 = 1, ._12 = 2, ._13 = 0,
._21 = 0, ._22 = 1, ._23 = 0,
._31 = -2, ._32 = 1, ._33 = 1,
};
const lambda: f32 = 3.5;
pub fn main() void {
std.debug.print("{any}\n", .{p});
comptime_math("q=B*p-lambda*p")();
std.debug.print("{any}\n", .{q});
comptime_math("q=B*q-lambda*q")();
std.debug.print("{any}\n", .{q});
}
```

The obvious problem here is that I need to define the variables globally, since `@This()`

in `comptime_math`

accesses the current file (which is a struct) and so can not access any variables defined inside, say, the function `main()`

.

This then forces me to declare all variables globally, which seems like a bad idea.

There are also the less obvious problems, for example that this is not what you’re supposed to do in zig and I should just suck it up and write the verbose code, but I’m ignoring those for now.

I know there exists `@Vector`

, but that requires me to index with `v[0]`

instead of `v.x`

, which I’m not a fan of, and (I think) it’s not really supposed to be used in analogy with a mathematical vector anyways. Also, this only allows for addition and such to happen pointwise; anything more complicated isn’t implemented.