Skip to content

Metaprogramming

danieltan1517 edited this page Mar 21, 2026 · 5 revisions

Generate Getters and Setters

One can use the insert directive to automatically generate getter and setter functions. This is an elementary example for didactic purposes, but one can extrapolate this example to do much more complex metaprogramming.

generate_getters_and_setters :: ($T: Type) -> string {
    builder: String_Builder;
    info := type_info(T);
    for member: info.members {
        print("%\n", type_of(member.type));
        print_to_builder(*builder, "get_% :: (s: %) -> type_of(s.%) { return s.%; }\n",
            member.name, T, member.name, member.name);
        print_to_builder(*builder, "set_% :: (s: *%, val: type_of(s.%)) { s.% = val; }\n",
            member.name, T, member.name, member.name);
    }

    s := builder_to_string(*builder);
    return s;
}

#insert -> string {
    return generate_getters_and_setters(Person);
}

Given the following Person struct as an example:

Person :: struct {
    name: string;
    age: int;
}

The following code generated by the function would result in this:

get_name :: (s: Person) -> type_of(s.name) { return s.name; }
set_name :: (s: *Person, val: type_of(s.name)) { s.name = val; }
get_age :: (s: Person) -> type_of(s.age) { return s.age; }
set_age :: (s: *Person, val: type_of(s.age)) { s.age = val; }

Struct of Arrays

Structs of Arrays is a way of rearranging the layout of the data fields of a struct. Specifically, Structs of Arrays (SoA) is a data layout separating elements of a struct into one parallel array per field. Take the following example:

// this is just a normal struct, no SOA
Vec3 :: struct {
  x: float;
  y: float;
  z: float;
}

// this is a SOA Vec3 struct
SOA_Vec3 :: struct {
  x: [100] float;
  y: [100] float;
  z: [100] float;
}

Using an #insert directive and generating code at compile time, we can generalize the concept of SOA to any structs using the following:

Vec3 :: struct {
    x: float;
    y: float;
    z: float;
}

Person :: struct {
    age: int;
    is_cool: bool;
}

SOA :: struct(T: Type, count: int) {
    #insert -> string {
        t_info := type_info(T);
        builder: String_Builder;
        defer free_buffers(*builder);
        for fields: t_info.members {
            print_to_builder(*builder, "  %1: [%2] type_of(T.%1);\n", fields.name, count);
        }
        result := builder_to_string(*builder);
        return result;
    }
}

#import "Basic";

main :: () {

    // create an soa_vec3
    soa_vec: SOA(Vec3, 10);
    for i: 0..soa_vec.count-1 {
        print("soa_vec.x[i]=%, soa_vec.y[i]=%, soa_vec.z[i]=%\n", soa_vec.x[i], soa_vec.y[i], soa_vec.z[i]);
    }

    // create an soa_person
    soa_person: SOA(Person, 10);
    for i: 0..soa_person.count-1 {
        print("soa_person.age[i]=%, soa_person.is_cool[i]=%\n", soa_person.age[i], soa_person.is_cool[i]);
    }

}

Initialize Data with No Reset

Applying #no_reset macro allows one to initialize complex data structures at compile time easily without the need of complex assembly language macros like in C++. Here is a simple example of #no_reset directive.

#no_reset array: [5] int;

#run {
    array[0] = 1;
    array[1] = 10;
    array[2] = 3;
    array[3] = 5;
    array[4] = 700;
}

Initialize NNUE No Reset

Applying #no_reset macro allows one to initialize complex data structures at compile time easily without the need of complex assembly language macros like in C++. In this example, one can load a binary NNUE file into memory and populate the data structures at compile time.

#run load_model("orange7_8_24.nnue");

#no_reset nnue: NNUE #align 64;

NNUE :: struct {
  feature_weights: [2][9][90][128] s16;
  feature_biases:  [128] s16;
  output_weights:  [2][128] s16;
  output_bias:     s32;
}

load_model :: (filename: string) {
  print("loading file %\n", filename);
  weights, success := read_entire_file(filename);
  assert(success, "File % not found.", filename);
  memcpy(*nnue, *weights[0], size_of(NNUE));
  free(weights);
}

Clone this wiki locally