Tagged union

A union with an added tag to keep track of type of field currently being used. A Tagged Union can be created and typechecked through a metaprogram.

We start with the struct definition. The fields parameter in the Tag_Union represents the name of the field you want. The types are the field’s corresponding name:

Tag_Union :: struct(fields: [] string, types: []Type) {
  tag: Type;
  #insert -> string {
    builder: String_Builder;
    defer free_buffers(*builder);
    count := fields.count - 1;
    print_to_builder(*builder, "union {\n");
    for i: 0..count {
      print_to_builder(*builder, "  %1: %2;\n", fields[i], types[i]);
 
    }
    print_to_builder(*builder, "}\n");
    result := builder_to_string(*builder);
    return result;
  }
}

And define a set function for the Tagged Union:

set :: (u: *$Tag/Tag_Union, value: $T) {
  #insert -> string {
    count := u.fields.count - 1;
    for i: 0..count {
      if T == Tag.types[i] {
        code :: #string END
           u.tag = type_of(value);
           u.% = value;
        END
        return sprint(code, Tag.fields[i]);
      }
    }
    assert(false, "Invalid value: %\n", T);
    return "assert(false)";
  }
}

With both of these definitions, we can define and use a tagged union as follows:

fields :: string.["int_a", "float_b", "string_c"];
types  :: Type.[int, float, string];

tag_union: Tag_Union(fields, types);
set(*tag_union, 10);
print("tag_union.int_a=% tag_union.tag=%\n", tag_union.int_a, tag_union.tag);

set(*tag_union, 3.14);
print("tag_union.float_b=% tag_union.tag=%\n", tag_union.float_b, tag_union.tag);

set(*tag_union, "Han Solo");
print("tag_union.string_c=% tag_union.tag=%\n", tag_union.string_c, tag_union.tag);