Search code examples
zig

How can I "simulate" the behaviour of a C macro in Zig, with an operator passed as a macro argument?


I want to learn some Zig (that is, literally this is my third day with Zig), so I've decided to write a simple 6502 based 8 bit computer emulator in Zig, based on an existing C project of mine. However I have encountered a problem, I have no idea how I can solve in Zig in an elegant way at least:

#define SOME_MACRO(a,b,OP) do { \
   ...
   some_stuff = a OP b; \
   ...
   } while (0)
... much later ...
SOME_MACRO(u1,u2,|); // OR operand version
...
SOME_MACRO(u1,u2,&); // AND operand version
... etc ...

Obviously, the real code is much more complicated than this, but the key point, that the actual operator (like | or &) is passed as "C macro argument".

As far as I know about Zig so far, I can use inline'd comptime stuff to have similar behaviour as the C macro, but I have no idea how to pass an operator as an argument. This is also a curiosity in me, since, of course, with some meta-programming like pattern (maybe using for with comptime) I can generate the needed inline functions before and I can use one of them on demand later, instead of the same "SOME_MACRO".

Is there some @builtin to push directly an operator "name" to the compiler from a constant given by a call parameter or something similar?


Solution

  • Zig doesn't have a way to pass an operator to a function, instead you'll have to use comptime or runtime branching or pass a callback function

    #define SOME_MACRO(a,b,OP) do { \
       ...
       some_stuff = a OP b; \
       ...
       } while (0)
    

    With a callback function:

    fn SOME_MACRO(a: i32, b: i32, comptime OP: fn(a: i32, b: i32) i32) i32 {
        const my_value = OP(a, b);
        // or force inline with @call(.always_inline, OP, .{a, b});
        return my_value;
    }
    
    fn OP_ADD(a: i32, b: i32) i32 {
        return a + b;
    }
    
    const std = @import("std");
    test "my macro" {
        try std.testing.assert(SOME_MACRO(5, 6, OP_ADD) == 11);
    }
    

    With a comptime switch:

    const Operator = enum {add, sub, mul, div, bitwise_or, bitwise_and};
    fn SOME_MACRO(a: i32, b: i32, comptime OP: Operator) i32 {
        return inline switch(OP) {
            .add => a + b,
            .sub => a - b,
            .mul => a * b,
            .div => @divTrunc(a, b),
            .bitwise_or => a | b,
            .bitwise_and => a & b,
        };
    }
    
    const std = @import("std");
    test "my macro" {
        try std.testing.assert(SOME_MACRO(5, 6, .add) == 11);
    }