How objects, classes and properties are compiled to C.


§1. Setting up the model.

void CObjectModel::initialise(code_generator *cgt) {
    METHOD_ADD(cgt, PSEUDO_OBJECT_MTID, CObjectModel::pseudo_object);
    METHOD_ADD(cgt, END_INSTANCE_MTID, CObjectModel::end_instance);
    METHOD_ADD(cgt, DECLARE_INSTANCE_MTID, CObjectModel::declare_instance);
    METHOD_ADD(cgt, DECLARE_KIND_MTID, CObjectModel::declare_kind);
    METHOD_ADD(cgt, END_KIND_MTID, CObjectModel::end_kind);

    METHOD_ADD(cgt, DECLARE_PROPERTY_MTID, CObjectModel::declare_property);
    METHOD_ADD(cgt, ASSIGN_PROPERTY_MTID, CObjectModel::assign_property);
    METHOD_ADD(cgt, ASSIGN_PROPERTIES_MTID, CObjectModel::assign_properties);
}

typedef struct C_generation_object_model_data {
    int owner_id_count;
    struct C_property_owner *arrow_chain[128];
    int property_id_counter;
    int C_property_offsets_made;
    struct C_property_owner *current_owner;
    struct dictionary *declared_properties;
    struct linked_list *declared_objects;  of C_property_owner
    struct C_property_owner *compass_instance;
    struct C_property_owner *direction_kind;
    struct dictionary *header_constants;
    int value_ranges_needed;
    int value_property_holders_needed;
} C_generation_object_model_data;

typedef struct C_property_owner {
    struct text_stream *name;
    struct text_stream *class;
    struct text_stream *identifier;
    struct linked_list *property_values;
    struct C_property_owner *initial_parent;
    struct C_property_owner *initial_sibling;
    struct C_property_owner *initial_child;
    int is_class;
    int id;
    CLASS_DEFINITION
} C_property_owner;

void CObjectModel::initialise_data(code_generation *gen) {
    C_GEN_DATA(objdata.owner_id_count) = 0;
    C_GEN_DATA(objdata.property_id_counter) = 0;
    C_GEN_DATA(objdata.C_property_offsets_made) = 0;
    C_GEN_DATA(objdata.declared_properties) = Dictionaries::new(1024, FALSE);
    C_GEN_DATA(objdata.declared_objects) = NEW_LINKED_LIST(C_property_owner);
    for (int i=0; i<128; i++) C_GEN_DATA(objdata.arrow_chain)[i] = NULL;
    C_GEN_DATA(objdata.compass_instance) = NULL;
    C_GEN_DATA(objdata.header_constants) = Dictionaries::new(1024, TRUE);
    C_GEN_DATA(objdata.value_ranges_needed) = FALSE;
    C_GEN_DATA(objdata.value_property_holders_needed) = FALSE;
}

void CObjectModel::begin(code_generation *gen) {
    CObjectModel::initialise_data(gen);
    Begin the initialiser function1.7;
}

void CObjectModel::end(code_generation *gen) {
    CObjectModel::write_property_values_table(gen);
    Complete the initialiser function1.8;
    Complete the property-offset creator function1.6;
    Predeclare the object count and class array1.5;
    if (C_GEN_DATA(objdata.value_ranges_needed)) Make the value ranges1.1;
    if (C_GEN_DATA(objdata.value_property_holders_needed)) Make the value property holders1.2;
}

§1.1. Make the value ranges1.1 =

    CMemoryModel::begin_array(NULL, gen, I"value_ranges", NULL, NULL, WORD_ARRAY_FORMAT, NULL);
    CMemoryModel::array_entry(NULL, gen, I"0", WORD_ARRAY_FORMAT);
    inter_symbol *max_weak_id = InterSymbolsTables::url_name_to_symbol(gen->from, NULL,
        I"/main/synoptic/kinds/BASE_KIND_HWM");
    if (max_weak_id) {
        int M = Inter::Symbols::evaluate_to_int(max_weak_id);
        for (int w=1; w<M; w++) {
            int written = FALSE;
            inter_symbol *kind_name;
            LOOP_OVER_LINKED_LIST(kind_name, inter_symbol, gen->kinds_in_declaration_order) {
                if (VanillaObjects::weak_id(kind_name) == w) {
                    if (VanillaObjects::value_kind_with_properties(gen, kind_name)) {
                        written = TRUE;
                        TEMPORARY_TEXT(N)
                        WRITE_TO(N, "%d", Inter::Kind::instance_count(kind_name));
                        CMemoryModel::array_entry(NULL, gen, N, WORD_ARRAY_FORMAT);
                        DISCARD_TEXT(N)
                    }
                }
            }
            if (written == FALSE) CMemoryModel::array_entry(NULL, gen, I"0", WORD_ARRAY_FORMAT);
        }
    }
    CMemoryModel::end_array(NULL, gen, WORD_ARRAY_FORMAT, NULL);

§1.2. Make the value property holders1.2 =

    CMemoryModel::begin_array(NULL, gen, I"value_property_holders", NULL, NULL, WORD_ARRAY_FORMAT, NULL);
    CMemoryModel::array_entry(NULL, gen, I"0", WORD_ARRAY_FORMAT);
    inter_symbol *max_weak_id = InterSymbolsTables::url_name_to_symbol(gen->from, NULL,
        I"/main/synoptic/kinds/BASE_KIND_HWM");
    if (max_weak_id) {
        int M = Inter::Symbols::evaluate_to_int(max_weak_id);
        for (int w=1; w<M; w++) {
            int written = FALSE;
            inter_symbol *kind_name;
            LOOP_OVER_LINKED_LIST(kind_name, inter_symbol, gen->kinds_in_declaration_order) {
                if (VanillaObjects::weak_id(kind_name) == w) {
                    if (VanillaObjects::value_kind_with_properties(gen, kind_name)) {
                        written = TRUE;
                        TEMPORARY_TEXT(N)
                        WRITE_TO(N, "i7_mgl_VPH_%d", w);
                        CMemoryModel::array_entry(NULL, gen, N, WORD_ARRAY_FORMAT);
                        DISCARD_TEXT(N)
                    }
                }
            }
            if (written == FALSE) CMemoryModel::array_entry(NULL, gen, I"0", WORD_ARRAY_FORMAT);
        }
    }
    CMemoryModel::end_array(NULL, gen, WORD_ARRAY_FORMAT, NULL);

§1.3. Owners. In this model, every class and every instance are represented by one "owner object" each. These owner objects own properties, as we shall see. Each has a name, an ID number, and a "class name", which is always the name of another owner: except that the owner Class has the class name Class, i.e., itself.

Here we create an owner. They are listed in a dynamically resized array in the model data:

C_property_owner *CObjectModel::assign_owner(code_generation *gen, int id, text_stream *name,
    text_stream *class_name, int is_class) {
    C_property_owner *co = CREATE(C_property_owner);
    if (Str::len(name) == 0) internal_error("nameless instance");
    co->name = Str::duplicate(name);
    co->class = Str::duplicate(class_name);
    co->is_class = is_class;
    co->property_values = NEW_LINKED_LIST(C_pv_pair);
    co->initial_parent = NULL;
    co->initial_sibling = NULL;
    co->initial_child = NULL;
    co->identifier = Str::new(); CNamespace::mangle(NULL, co->identifier, co->name);
    co->id = id;
    C_GEN_DATA(objdata.current_owner) = co;
    ADD_TO_LINKED_LIST(co, C_property_owner, C_GEN_DATA(objdata.declared_objects));
    return co;
}

§1.4. Owner IDs. At runtime, an ID number uniquely identifies possible owners of properties. The special ID 0 is reserved for nothing, meaning the absence of such an owner, so we can only use 1 upwards.

The four metaclasses Class, Object, String, Routine will get IDs 1 to 4. Those are not classes in the Inter tree, and must therefore be created here as special cases. After that, it's first come, first served.

int CObjectModel::next_owner_id(code_generation *gen) {
    C_GEN_DATA(objdata.owner_id_count)++;
    if (C_GEN_DATA(objdata.owner_id_count) == 1) {
        CObjectModel::declare_class_inner(gen, I"Class", NULL, 1, I"Class");
        C_GEN_DATA(objdata.owner_id_count)++;
        CObjectModel::declare_class_inner(gen, I"Object", NULL, 2, I"Class");
        C_GEN_DATA(objdata.owner_id_count)++;
        CObjectModel::declare_class_inner(gen, I"String", NULL, 3, I"Class");
        C_GEN_DATA(objdata.owner_id_count)++;
        CObjectModel::declare_class_inner(gen, I"Routine", NULL, 4, I"Class");
        C_GEN_DATA(objdata.owner_id_count)++;
    }
    return C_GEN_DATA(objdata.owner_id_count);
}

§1.5. The (constant) array i7_class_of[id] accepts any ID for a class or instance, and evaluates to the ID of its classname. So, for example, i7_class_of[1] == 1 expresses that the classname of Class is Class itself. Here we compile a declaration for that array.

ID numbers above our range used for classes and instances are reserved for double-quoted literal strings, and then for functions. Thus, each distinct literal string, and each distinct function, has an ID; and none of these IDs overlap.

Predeclare the object count and class array1.5 =

    segmentation_pos saved = CodeGen::select(gen, c_ids_and_maxima_I7CGS);
    text_stream *OUT = CodeGen::current(gen);

    WRITE("#define i7_max_objects %d\n", C_GEN_DATA(objdata.owner_id_count) + 1);

    WRITE("i7word_t i7_metaclass_of[] = { 0");
    C_property_owner *co;
    LOOP_OVER_LINKED_LIST(co, C_property_owner, C_GEN_DATA(objdata.declared_objects)) {
        if (co->is_class) WRITE(", i7_mgl_Class");
        else WRITE(", i7_mgl_Object");
    }
    WRITE(" };\n");

    WRITE("i7word_t i7_class_of[] = { 0");
    LOOP_OVER_LINKED_LIST(co, C_property_owner, C_GEN_DATA(objdata.declared_objects)) {
        WRITE(", "); CNamespace::mangle(NULL, OUT, co->class);
    }
    WRITE(" };\n");

    WRITE("#define I7VAL_STRINGS_BASE %d\n", C_GEN_DATA(objdata.owner_id_count) + 1);
    WRITE("#define I7VAL_FUNCTIONS_BASE %d\n",
        C_GEN_DATA(objdata.owner_id_count) + 1 + CLiteralsModel::no_strings(gen));

    WRITE("#define i7_no_property_ids %d\n", C_GEN_DATA(objdata.property_id_counter));
    CodeGen::deselect(gen, saved);

§2. Class and instance declarations. Each proper base kind in the Inter tree produces an owner as follows:

void CObjectModel::declare_kind(code_generator *cgt, code_generation *gen,
    inter_symbol *kind_s, segmentation_pos *saved) {
    if ((kind_s == object_kind_symbol) ||
        (VanillaObjects::is_kind_of_object(kind_s))) {
        text_stream *class_name = Inter::Symbols::name(kind_s);
        text_stream *printed_name = Metadata::read_optional_textual(
            Inter::Packages::container(kind_s->definition), I"^printed_name");
        text_stream *super_class = NULL;
        inter_symbol *super_name = Inter::Kind::super(kind_s);
        if (super_name) super_class = Inter::Symbols::name(super_name);

        *saved = CodeGen::select(gen, c_main_matter_I7CGS);
        if (Str::len(super_class) == 0) super_class = I"Class";
        CObjectModel::declare_class_inner(gen, class_name, printed_name,
            CObjectModel::next_owner_id(gen), super_class);
    } else if (VanillaObjects::value_kind_with_properties(gen, kind_s)) {
        TEMPORARY_TEXT(instance_name)
        WRITE_TO(instance_name, "VPH_%d", VanillaObjects::weak_id(kind_s));
        CObjectModel::C_runtime_object(NULL, gen, I"Object", instance_name, NULL, -1, FALSE, saved);
        DISCARD_TEXT(instance_name)
    }
}

void CObjectModel::end_kind(code_generator *cgt, code_generation *gen, inter_symbol *kind_s, segmentation_pos saved) {
    CodeGen::deselect(gen, saved);
}

void CObjectModel::declare_class_inner(code_generation *gen,
    text_stream *class_name, text_stream *printed_name, int id, text_stream *super_class) {
    CObjectModel::define_constant_for_owner_id(gen, class_name, id);
    if (printed_name) CObjectModel::define_header_constant_for_kind(gen, class_name, printed_name, id);
    CObjectModel::assign_owner(gen, id, class_name, super_class, TRUE);
}

§3. And each instance here:

void CObjectModel::pseudo_object(code_generator *cgt, code_generation *gen, text_stream *obj_name) {
    segmentation_pos saved;
    C_property_owner *obj = CObjectModel::C_runtime_object(cgt, gen, I"Object", obj_name, obj_name, -1, FALSE, &saved);
    CodeGen::deselect(gen, saved);
    if (Str::eq(obj_name, I"Compass")) C_GEN_DATA(objdata.compass_instance) = obj;
}

C_property_owner *CObjectModel::C_runtime_object(code_generator *cgt, code_generation *gen,
    text_stream *class_name, text_stream *instance_name, text_stream *printed_name, int acount, int is_dir, segmentation_pos *saved) {
    *saved = CodeGen::select(gen, c_main_matter_I7CGS);
    if (Str::len(instance_name) == 0) internal_error("nameless instance");
    int id = CObjectModel::next_owner_id(gen);
    CObjectModel::define_constant_for_owner_id(gen, instance_name, id);
    if (printed_name) {
        TEMPORARY_TEXT(val)
        WRITE_TO(val, "%d", id);
        CObjectModel::define_header_constant_for_instance(gen, instance_name, printed_name, val, FALSE);
        DISCARD_TEXT(val)
    }
    C_property_owner *this = CObjectModel::assign_owner(gen, id, instance_name, class_name, FALSE);
    if (acount >= 0) {
        this->initial_parent = NULL;
        if (acount > 0) {
            C_property_owner *par = C_GEN_DATA(objdata.arrow_chain)[acount-1];
            if (par == NULL) internal_error("arrows misaligned");
            if (par->initial_child == NULL) {
                par->initial_child = this;
            } else {
                C_property_owner *older = par->initial_child;
                while ((older) && (older->initial_sibling)) older = older->initial_sibling;
                older->initial_sibling = this;
            }
            this->initial_parent = par;
        } else if (is_dir) {
            C_property_owner *par = C_GEN_DATA(objdata.compass_instance);
            if (par) {
                if (par->initial_child == NULL) {
                    par->initial_child = this;
                } else {
                    C_property_owner *older = par->initial_child;
                    while ((older) && (older->initial_sibling)) older = older->initial_sibling;
                    older->initial_sibling = this;
                }
                this->initial_parent = par;
            }
        }
        C_GEN_DATA(objdata.arrow_chain)[acount] = this;
        for (int i=acount+1; i<128; i++) C_GEN_DATA(objdata.arrow_chain)[i] = NULL;
    }
    return this;
}

void CObjectModel::end_instance(code_generator *cgt, code_generation *gen, inter_symbol *inst_s, inter_symbol *kind_s, segmentation_pos saved) {
    CodeGen::deselect(gen, saved);
}

void CObjectModel::declare_instance(code_generator *cgt,
    code_generation *gen, inter_symbol *inst_s, inter_symbol *kind_s, int enumeration, segmentation_pos *saved) {
        text_stream *printed_name =
            Metadata::read_optional_textual(Inter::Packages::container(inst_s->definition), I"^printed_name");
    if ((kind_s == object_kind_symbol) ||
        (VanillaObjects::is_kind_of_object(kind_s))) {
        int c = Inter::Symbols::read_annotation(inst_s, ARROW_COUNT_IANN);
        if (c < 0) c = 0;
        int is_dir = Inter::Kind::is_a(kind_s, direction_kind_symbol);
        CObjectModel::C_runtime_object(cgt, gen,
            Inter::Symbols::name(kind_s), Inter::Symbols::name(inst_s), printed_name, c, is_dir, saved);
    } else {
        TEMPORARY_TEXT(val)
        WRITE_TO(val, "%d", enumeration);
        Generators::declare_constant(gen, inst_s, RAW_GDCFORM, val);
        CObjectModel::define_header_constant_for_instance(gen, Inter::Symbols::name(inst_s), printed_name, val, TRUE);
        DISCARD_TEXT(val)
    }
}

§4. So it is finally time to compile a #define for the owner's identifier, defining this as a constant equal to its ID.

void CObjectModel::define_constant_for_owner_id(code_generation *gen, text_stream *owner_name,
    int id) {
    segmentation_pos saved = CodeGen::select(gen, c_ids_and_maxima_I7CGS);
    text_stream *OUT = CodeGen::current(gen);
    WRITE("#define "); CNamespace::mangle(NULL, OUT, owner_name); WRITE(" %d\n", id);
    CodeGen::deselect(gen, saved);
}

text_stream *CObjectModel::new_header_name(code_generation *gen, text_stream *prefix, text_stream *raw) {
    dictionary *D = C_GEN_DATA(objdata.header_constants);
    text_stream *key = Str::new();
    WRITE_TO(key, "i7_%S_", prefix);
    LOOP_THROUGH_TEXT(pos, raw)
        if (Characters::isalnum(Str::get(pos)))
            PUT_TO(key, Str::get(pos));
        else
            PUT_TO(key, '_');
    text_stream *dv = Dictionaries::get_text(D, key);
    if (dv) {
        TEMPORARY_TEXT(keyx)
        int n = 2;
        while (TRUE) {
            Str::clear(keyx);
            WRITE_TO(keyx, "%S_%d", key, n);
            if (Dictionaries::get_text(D, keyx) == NULL) break;
            n++;
        }
        DISCARD_TEXT(keyx)
        WRITE_TO(key, "_%d", n);
    }
    Dictionaries::create_text(D, key);
    return key;
}

void CObjectModel::define_header_constant_for_instance(code_generation *gen, text_stream *owner_name,
    text_stream *printed_name, text_stream *val, int enumerated) {
    int seg = (enumerated)?c_enum_symbols_I7CGS:c_instances_symbols_I7CGS;
    segmentation_pos saved = CodeGen::select(gen, seg);
    text_stream *OUT = CodeGen::current(gen);
    WRITE("#define %S %S\n", CObjectModel::new_header_name(gen, I"I", printed_name), val);
    CodeGen::deselect(gen, saved);
}

void CObjectModel::define_header_constant_for_kind(code_generation *gen, text_stream *owner_name,
    text_stream *printed_name, int id) {
    segmentation_pos saved = CodeGen::select(gen, c_kinds_symbols_I7CGS);
    text_stream *OUT = CodeGen::current(gen);
    WRITE("#define %S %d\n", CObjectModel::new_header_name(gen, I"K", printed_name), id);
    CodeGen::deselect(gen, saved);
}

void CObjectModel::define_header_constant_for_action(code_generation *gen, text_stream *action_name,
    text_stream *printed_name, int id) {
    segmentation_pos saved = CodeGen::select(gen, c_actions_symbols_I7CGS);
    text_stream *OUT = CodeGen::current(gen);
    WRITE("#define %S %d\n", CObjectModel::new_header_name(gen, I"A", printed_name), id);
    CodeGen::deselect(gen, saved);
}

void CObjectModel::define_header_constant_for_property(code_generation *gen, text_stream *prop_name,
    text_stream *equiv) {
    segmentation_pos saved = CodeGen::select(gen, c_property_symbols_I7CGS);
    text_stream *OUT = CodeGen::current(gen);
    WRITE("#define %S %S\n", CObjectModel::new_header_name(gen, I"P", prop_name), equiv);
    CodeGen::deselect(gen, saved);
}

void CObjectModel::define_header_constant_for_variable(code_generation *gen, text_stream *var_name,
    int id) {
    segmentation_pos saved = CodeGen::select(gen, c_variable_symbols_I7CGS);
    text_stream *OUT = CodeGen::current(gen);
    WRITE("#define %S %d\n", CObjectModel::new_header_name(gen, I"V", var_name), id);
    CodeGen::deselect(gen, saved);
}

void CObjectModel::define_header_constant_for_function(code_generation *gen, text_stream *fn_name,
    text_stream *val) {
    segmentation_pos saved = CodeGen::select(gen, c_function_symbols_I7CGS);
    text_stream *OUT = CodeGen::current(gen);
    WRITE("#define %S %S\n", CObjectModel::new_header_name(gen, I"F", fn_name), val);
    CodeGen::deselect(gen, saved);
}

§5. Code to compute ofclass and metaclass. The easier case is metaclass. This is a built-in function, so we make it follow the calling conventions of other functions. It says which of five possible values an ID belongs to: 0, Class, Object, String or Routine.

i7word_t fn_i7_mgl_metaclass(i7process_t *proc, i7word_t id);
int i7_ofclass(i7process_t *proc, i7word_t id, i7word_t cl_id);
i7word_t fn_i7_mgl_metaclass(i7process_t *proc, i7word_t id) {
    if (id <= 0) return 0;
    if (id >= I7VAL_FUNCTIONS_BASE) return i7_mgl_Routine;
    if (id >= I7VAL_STRINGS_BASE) return i7_mgl_String;
    return i7_metaclass_of[id];
}

This function implements OFCLASS_BIP for us at runtime, and is a little harder, because we may need to recurse up the class hierarchy. If A is of class B whose superclass is C, then i7_ofclass(A, B) and i7_ofclass(A, C) are both true, as it i7_ofclass(B, C).

int i7_ofclass(i7process_t *proc, i7word_t id, i7word_t cl_id) {
    if ((id <= 0) || (cl_id <= 0)) return 0;
    if (id >= I7VAL_FUNCTIONS_BASE) {
        if (cl_id == i7_mgl_Routine) return 1;
        return 0;
    }
    if (id >= I7VAL_STRINGS_BASE) {
        if (cl_id == i7_mgl_String) return 1;
        return 0;
    }
    if (id == i7_mgl_Class) {
        if (cl_id == i7_mgl_Class) return 1;
        return 0;
    }
    if (cl_id == i7_mgl_Object) {
        if (i7_metaclass_of[id] == i7_mgl_Object) return 1;
        return 0;
    }
    int cl_found = i7_class_of[id];
    while (cl_found != i7_mgl_Class) {
        if (cl_id == cl_found) return 1;
        cl_found = i7_class_of[cl_found];
    }
    return 0;
}

§6. Property IDs. Each distinct property has a distinct ID. These count upwards from 0, and can freely overlap with owner IDs or anything else.

In Inform 6, owing to the complicated VMs it compiles to, there is a complicated distinction between "VM attributes" (some but not all either-or properties) and "VM properties" (everything else). But not here.

If a property is never given to anything this is nevertheless called, with used set false, so that a suitable constant is #sefined in the code, and therefore that references to it will not fail to compile.

void CObjectModel::declare_property(code_generator *cgt, code_generation *gen,
    inter_symbol *prop_name, linked_list *all_forms) {
    text_stream *name = Inter::Symbols::name(prop_name);
    text_stream *inner_name = VanillaObjects::inner_property_name(gen, prop_name);
    int attr = FALSE;
    if (Inter::Symbols::read_annotation(prop_name, EITHER_OR_IANN) == 1) attr = TRUE;
    CObjectModel::property_by_name(gen, name, inner_name, attr);
    text_stream *pname = Metadata::read_optional_textual(Inter::Packages::container(prop_name->definition), I"^name");
    if (pname) {
        int A = Inter::Symbols::read_annotation(prop_name, C_ARRAY_ADDRESS_IANN);
        if (A > 0) {
            TEMPORARY_TEXT(m)
            WRITE_TO(m, "%d", A);
            CObjectModel::define_header_constant_for_property(gen, pname, m);
            DISCARD_TEXT(m)
        }
    }
}

§7. Property IDs count upwards from 0 in declaration order, though they really only need to be unique, so the order is not significant.

typedef struct C_property {
    struct text_stream *name;
    int id;
    int attr;
    CLASS_DEFINITION
} C_property;

C_property *CObjectModel::property_by_name(code_generation *gen, text_stream *name, text_stream *inner_name, int attr) {
    dictionary *D = C_GEN_DATA(objdata.declared_properties);
    C_property *cp;
    if (Dictionaries::find(D, name) == NULL) {
        cp = CREATE(C_property);
        cp->name = Str::duplicate(name);
        cp->attr = attr;
        cp->id = C_GEN_DATA(objdata.property_id_counter)++;
        Dictionaries::create(D, name);
        Dictionaries::write_value(D, name, (void *) cp);

        segmentation_pos saved = CodeGen::select(gen, c_predeclarations_I7CGS);
        text_stream *OUT = CodeGen::current(gen);
        WRITE("#define ");
        CNamespace::mangle(NULL, OUT, inner_name);
        WRITE(" %d\n", cp->id);
        CodeGen::deselect(gen, saved);

        TEMPORARY_TEXT(val)
        WRITE_TO(val, "%d", (attr)?2:1);
        Generators::array_entry(gen, val, WORD_ARRAY_FORMAT);
        Str::clear(val);
        CNamespace::mangle(NULL, val, inner_name);
        Generators::array_entry(gen, val, WORD_ARRAY_FORMAT);
        DISCARD_TEXT(val)
    } else {
        cp = Dictionaries::read_value(D, name);
    }
    return cp;
}

§.1. This function is created only if properties actually exist to have offsets; that avoids a meaningless function being created in small test runs of inter not deriving from an Inform program.

Begin the property-offset creator function.1 =

    WRITE("i7word_t fn_i7_mgl_CreatePropertyOffsets(i7process_t *proc) {\n"); INDENT;
    WRITE("for (int i=0; i<i7_mgl_attributed_property_offsets_SIZE; i++)\n"); INDENT;
    WRITE("i7_write_word(proc, i7_mgl_attributed_property_offsets, i, -1, i7_lvalue_SET);\n"); OUTDENT;
    WRITE("for (int i=0; i<i7_mgl_valued_property_offsets_SIZE; i++)\n"); INDENT;
    WRITE("i7_write_word(proc, i7_mgl_valued_property_offsets, i, -1, i7_lvalue_SET);\n"); OUTDENT;

§1.6. This function has no meaningful return value, but has to conform to our calling convention for Inform programs, which means it has to return something. By fiat, that will be 0.

Complete the property-offset creator function1.6 =

    if (C_GEN_DATA(objdata.C_property_offsets_made) > 0) {
        segmentation_pos saved = CodeGen::select(gen, c_property_offset_creator_I7CGS);
        text_stream *OUT = CodeGen::current(gen);
        WRITE("return 0;\n");
        OUTDENT;
        WRITE("}\n");
        CodeGen::deselect(gen, saved);
    }

§1.7. Property-value initialiser function. When generating code for I6, property values are initialised with direct declarations in the I6 language, which tell that compiler to set up a large and complicated data structure.

We will not use any of that here, and will not attempt to create static data arrays which already have the right contents. Instead we will compile an initialiser function which runs early and sets the property values up by hand:

Begin the initialiser function1.7 =

    segmentation_pos saved = CodeGen::select(gen, c_initialiser_I7CGS);
    text_stream *OUT = CodeGen::current(gen);
    WRITE("void i7_initializer(i7process_t *proc) {\n"); INDENT;
    WRITE("for (int id=0; id<i7_max_objects; id++) {\n"); INDENT;
    WRITE("for (int p=0; p<i7_no_property_ids; p++) {\n"); INDENT;
    WRITE("i7_properties[id].address[p] = 0;\n");
    WRITE("i7_properties[id].len[p] = 0;\n");
    OUTDENT; WRITE("}\n");
    OUTDENT; WRITE("}\n");
    CodeGen::deselect(gen, saved);

§1.8. Complete the initialiser function1.8 =

    segmentation_pos saved = CodeGen::select(gen, c_initialiser_I7CGS);
    text_stream *OUT = CodeGen::current(gen);

    C_property_owner *co;
    LOOP_OVER_LINKED_LIST(co, C_property_owner, C_GEN_DATA(objdata.declared_objects)) {
        if (co->initial_parent) WRITE("proc->state.object_tree_parent[%S] = %S;\n", co->identifier, co->initial_parent->identifier);
        if (co->initial_sibling) WRITE("proc->state.object_tree_sibling[%S] = %S;\n", co->identifier, co->initial_sibling->identifier);
        if (co->initial_child) WRITE("proc->state.object_tree_child[%S] = %S;\n", co->identifier, co->initial_child->identifier);
    }

    OUTDENT; WRITE("}\n");
    CodeGen::deselect(gen, saved);

§8. And this function call is compiled to initialise a property value for a given owner. Note that it must be called after the owner's declaration call, and before the next owner is declared.

typedef struct C_pv_pair {
    struct C_property *prop;
    struct text_stream *val;
    int inlined;
    CLASS_DEFINITION
} C_pv_pair;

void CObjectModel::assign_property(code_generator *cgt, code_generation *gen,
    inter_symbol *prop_name, inter_ti val1, inter_ti val2, inter_tree_node *X) {

    int inline_this = FALSE;
    if (Inter::Symbols::is_stored_in_data(val1, val2)) {
        inter_symbol *S = InterSymbolsTables::symbol_from_data_pair_and_frame(val1, val2, X);
        if ((S) && (Inter::Symbols::read_annotation(S, INLINE_ARRAY_IANN) == 1))
            inline_this = TRUE;
    }

    TEMPORARY_TEXT(val)
    CodeGen::select_temporary(gen, val);
    CodeGen::pair(gen, X, val1, val2);
    CodeGen::deselect_temporary(gen);
    CObjectModel::C_prop(gen, prop_name, val, inline_this);
    DISCARD_TEXT(val)
}

void CObjectModel::C_prop(code_generation *gen, inter_symbol *prop_name, text_stream *val, int inline_this) {
    text_stream *property_name = Inter::Symbols::name(prop_name);
    text_stream *inner_name = VanillaObjects::inner_property_name(gen, prop_name);
    C_property_owner *owner = C_GEN_DATA(objdata.current_owner);
    C_property *prop = CObjectModel::property_by_name(gen, property_name, inner_name, FALSE);
    C_pv_pair *pair = CREATE(C_pv_pair);
    pair->prop = prop;
    pair->val = Str::duplicate(val);
    pair->inlined = inline_this;
    ADD_TO_LINKED_LIST(pair, C_pv_pair, owner->property_values);
}

void CObjectModel::assign_properties(code_generator *cgt, code_generation *gen, inter_symbol *kind_name, inter_symbol *prop_name, text_stream *array) {
    TEMPORARY_TEXT(mgl)
    CNamespace::mangle(NULL, mgl, array);
    CObjectModel::C_prop(gen, prop_name, mgl, FALSE);
    DISCARD_TEXT(mgl)
}

C_property_owner *CObjectModel::super(code_generation *gen, C_property_owner *owner) {
    C_property_owner *co;
    LOOP_OVER_LINKED_LIST(co, C_property_owner, C_GEN_DATA(objdata.declared_objects)) {
        if (Str::eq(co->name,  owner->class)) return co;
    }
    return NULL;
}

int not_added_ops_yet = TRUE;
void CObjectModel::gather_properties(code_generation *gen, C_property_owner *owner, C_pv_pair **vals) {
    C_property_owner *super = CObjectModel::super(gen, owner);
    if ((Str::eq(owner->name, I"Class")) && (not_added_ops_yet)) {
        C_property *prop;
        LOOP_OVER(prop, C_property) {
            if (prop->attr) {
                C_pv_pair *np = CREATE(C_pv_pair);
                np->prop = prop;
                np->val = I"0";
                np->inlined = FALSE;
                ADD_TO_LINKED_LIST(np, C_pv_pair, owner->property_values);
            }
        }
        not_added_ops_yet = FALSE;
    }
    if (super != owner) CObjectModel::gather_properties(gen, super, vals);
    C_pv_pair *pair;
    LOOP_OVER_LINKED_LIST(pair, C_pv_pair, owner->property_values) {
        vals[pair->prop->id] = pair;
    }
}

void CObjectModel::write_property_values_table(code_generation *gen) {
    segmentation_pos saved = CodeGen::select(gen, c_initialiser_I7CGS);
    text_stream *OUT = CodeGen::current(gen);
    C_property_owner *owner;
    LOOP_OVER_LINKED_LIST(owner, C_property_owner, C_GEN_DATA(objdata.declared_objects)) {
        C_pv_pair *vals[1024];
        for (int i=0; i<1024; i++) vals[i] = NULL;
        CObjectModel::gather_properties(gen, owner, vals);
        for (int i=0; i<1024; i++) if (vals[i]) {
            C_pv_pair *pair = vals[i];
            if (pair->inlined) {
                WRITE("i7_properties[");
                CNamespace::mangle(NULL, OUT, owner->name);
                WRITE("].address[i7_read_word(proc, ");
                CNamespace::mangle(NULL, OUT, pair->prop->name);
                WRITE(", 1)] = %S;\n", pair->val);
                WRITE("i7_properties[");
                CNamespace::mangle(NULL, OUT, owner->name);
                WRITE("].len[i7_read_word(proc, ");
                CNamespace::mangle(NULL, OUT, pair->prop->name);
                WRITE(", 1)] = xt_%S + 1;\n", pair->val);
            } else {
                WRITE("i7_properties[");
                CNamespace::mangle(NULL, OUT, owner->name);
                WRITE("].address[i7_read_word(proc, ");
                CNamespace::mangle(NULL, OUT, pair->prop->name);
                WRITE(", 1)] = %d;  %S\n", C_GEN_DATA(memdata.himem), pair->val);
                CMemoryModel::array_entry(NULL, gen, pair->val, WORD_ARRAY_FORMAT);
                WRITE("i7_properties[");
                CNamespace::mangle(NULL, OUT, owner->name);
                WRITE("].len[i7_read_word(proc, ");
                CNamespace::mangle(NULL, OUT, pair->prop->name);
                WRITE(", 1)] = 1;\n");
            }
        }
    }
    CodeGen::deselect(gen, saved);
}

§9. Primitives for property usage. The following primitives are all implemented by calling suitable C functions, which we will then need to write in inform7_clib.h.

int CObjectModel::handle_store_by_ref(code_generation *gen, inter_tree_node *ref) {
    if (Inter::Reference::node_is_ref_to(gen->from, ref, PROPERTYVALUE_BIP)) return TRUE;
    return FALSE;
}

int CObjectModel::invoke_primitive(code_generation *gen, inter_ti bip, inter_tree_node *P) {
    text_stream *OUT = CodeGen::current(gen);
    switch (bip) {
        case PROPERTYARRAY_BIP: WRITE("i7_prop_addr(proc, "); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(", "); VNODE_3C; WRITE(")"); break;
        case PROPERTYLENGTH_BIP: WRITE("i7_prop_len(proc, "); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(", "); VNODE_3C; WRITE(")"); break;
        case PROPERTYVALUE_BIP: CConditions::comparison_r(gen, bip, InterTree::first_child(P), InterTree::second_child(P), InterTree::third_child(P), 0);
                                break;
        case MESSAGE0_BIP:      WRITE("i7_mcall_0(proc, "); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(")"); break;
        case MESSAGE1_BIP:      WRITE("i7_mcall_1(proc, "); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(", ");
                                VNODE_3C; WRITE(")"); break;
        case MESSAGE2_BIP:      WRITE("i7_mcall_2(proc, "); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(", ");
                                VNODE_3C; WRITE(", "); VNODE_4C; WRITE(")"); break;
        case MESSAGE3_BIP:      WRITE("i7_mcall_3(proc, "); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(", ");
                                VNODE_3C; WRITE(", "); VNODE_4C; WRITE(", "); VNODE_5C; WRITE(")"); break;
        case MOVE_BIP:          WRITE("i7_move(proc, "); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(")"); break;
        case REMOVE_BIP:        WRITE("i7_move(proc, "); VNODE_1C; WRITE(", 0)"); break;

        default: return NOT_APPLICABLE;
    }
    return FALSE;
}

§10. Reading and writing properties. So here is the run-time storage for property values, and simple code to read and write them.

i7word_t fn_i7_mgl_CreatePropertyOffsets(i7process_t *proc);
void i7_write_prop_value(i7process_t *proc, i7word_t owner_id, i7word_t prop_id, i7word_t val);
i7word_t i7_read_prop_value(i7process_t *proc, i7word_t owner_id, i7word_t pr_array);
i7word_t i7_change_prop_value(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t pr, i7word_t to, int way);
void i7_give(i7process_t *proc, i7word_t owner, i7word_t prop, i7word_t val);
i7word_t i7_prop_len(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t pr);
i7word_t i7_prop_addr(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t pr);
#define I7_MAX_PROPERTY_IDS 1000
typedef struct i7_property_set {
    i7word_t address[I7_MAX_PROPERTY_IDS];
    i7word_t len[I7_MAX_PROPERTY_IDS];
} i7_property_set;
i7_property_set i7_properties[i7_max_objects];

void i7_write_prop_value(i7process_t *proc, i7word_t owner_id, i7word_t pr_array, i7word_t val) {
    i7word_t prop_id = i7_read_word(proc, pr_array, 1);
    if ((owner_id <= 0) || (owner_id >= i7_max_objects) ||
        (prop_id < 0) || (prop_id >= i7_no_property_ids)) {
        printf("impossible property write (%d, %d)\n", owner_id, prop_id);
        i7_fatal_exit(proc);
    }
    i7word_t address = i7_properties[(int) owner_id].address[(int) prop_id];
    if (address) i7_write_word(proc, address, 0, val, i7_lvalue_SET);
    else {
        printf("impossible property write (%d, %d)\n", owner_id, prop_id);
        i7_fatal_exit(proc);
    }
}

§11. And here sre the functions called by the above primitives:

i7word_t i7_read_prop_value(i7process_t *proc, i7word_t owner_id, i7word_t pr_array) {
    i7word_t prop_id = i7_read_word(proc, pr_array, 1);
    if ((owner_id <= 0) || (owner_id >= i7_max_objects) ||
        (prop_id < 0) || (prop_id >= i7_no_property_ids)) return 0;
    while (i7_properties[(int) owner_id].address[(int) prop_id] == 0) {
        owner_id = i7_class_of[owner_id];
        if (owner_id == i7_mgl_Class) return 0;
    }
    i7word_t address = i7_properties[(int) owner_id].address[(int) prop_id];
    return i7_read_word(proc, address, 0);
}

i7word_t i7_change_prop_value(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t pr, i7word_t to, int way) {
    i7word_t val = i7_read_prop_value(proc, obj, pr), new_val = val;
    switch (way) {
        case i7_lvalue_SET:      i7_write_prop_value(proc, obj, pr, to); new_val = to; break;
        case i7_lvalue_PREDEC:   new_val = val-1; i7_write_prop_value(proc, obj, pr, val-1); break;
        case i7_lvalue_POSTDEC:  new_val = val; i7_write_prop_value(proc, obj, pr, val-1); break;
        case i7_lvalue_PREINC:   new_val = val+1; i7_write_prop_value(proc, obj, pr, val+1); break;
        case i7_lvalue_POSTINC:  new_val = val; i7_write_prop_value(proc, obj, pr, val+1); break;
        case i7_lvalue_SETBIT:   new_val = val | new_val; i7_write_prop_value(proc, obj, pr, new_val); break;
        case i7_lvalue_CLEARBIT: new_val = val &(~new_val); i7_write_prop_value(proc, obj, pr, new_val); break;
    }
    return new_val;
}

void i7_give(i7process_t *proc, i7word_t owner, i7word_t prop, i7word_t val) {
    i7_write_prop_value(proc, owner, prop, val);
}

i7word_t i7_prop_len(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t pr_array) {
    i7word_t pr = i7_read_word(proc, pr_array, 1);
    if ((obj <= 0) || (obj >= i7_max_objects) ||
        (pr < 0) || (pr >= i7_no_property_ids)) return 0;
    return 4*i7_properties[(int) obj].len[(int) pr];
}

i7word_t i7_prop_addr(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t pr_array) {
    i7word_t pr = i7_read_word(proc, pr_array, 1);
    if ((obj <= 0) || (obj >= i7_max_objects) ||
        (pr < 0) || (pr >= i7_no_property_ids)) return 0;
    return i7_properties[(int) obj].address[(int) pr];
}

§12. Special object-related conditions.

text_stream *CObjectModel::test_with_function(inter_ti bip, int *positive) {
    switch (bip) {
        case OFCLASS_BIP:   *positive = TRUE;  return I"i7_ofclass"; break;
        case PROPERTYVALUE_BIP: *positive = TRUE;  return I"i7_read_gprop"; break;
        case IN_BIP:        *positive = TRUE;  return I"i7_in"; break;
        case NOTIN_BIP:     *positive = FALSE; return I"i7_in"; break;
    }
    *positive = NOT_APPLICABLE; return NULL;
}

§13.

int i7_has(i7process_t *proc, i7word_t obj, i7word_t attr);
int i7_provides(i7process_t *proc, i7word_t owner_id, i7word_t prop_id);
int i7_in(i7process_t *proc, i7word_t obj1, i7word_t obj2);
i7word_t fn_i7_mgl_parent(i7process_t *proc, i7word_t id);
#define i7_parent fn_i7_mgl_parent
i7word_t fn_i7_mgl_child(i7process_t *proc, i7word_t id);
#define i7_child fn_i7_mgl_child
i7word_t fn_i7_mgl_children(i7process_t *proc, i7word_t id);
i7word_t fn_i7_mgl_sibling(i7process_t *proc, i7word_t id);
#define i7_sibling fn_i7_mgl_sibling
void i7_move(i7process_t *proc, i7word_t obj, i7word_t to);
int i7_has(i7process_t *proc, i7word_t obj, i7word_t attr) {
    if (i7_read_prop_value(proc, obj, attr)) return 1;
    return 0;
}

int i7_provides(i7process_t *proc, i7word_t owner_id, i7word_t pr_array) {
    i7word_t prop_id = i7_read_word(proc, pr_array, 1);
    if ((owner_id <= 0) || (owner_id >= i7_max_objects) ||
        (prop_id < 0) || (prop_id >= i7_no_property_ids)) return 0;
    while (owner_id != 1) {
        if (i7_properties[(int) owner_id].address[(int) prop_id] != 0)
            return 1;
        owner_id = i7_class_of[owner_id];
    }
    return 0;
}

int i7_in(i7process_t *proc, i7word_t obj1, i7word_t obj2) {
    if (fn_i7_mgl_metaclass(proc, obj1) != i7_mgl_Object) return 0;
    if (obj2 == 0) return 0;
    if (proc->state.object_tree_parent[obj1] == obj2) return 1;
    return 0;
}

i7word_t fn_i7_mgl_parent(i7process_t *proc, i7word_t id) {
    if (fn_i7_mgl_metaclass(proc, id) != i7_mgl_Object) return 0;
    return proc->state.object_tree_parent[id];
}
i7word_t fn_i7_mgl_child(i7process_t *proc, i7word_t id) {
    if (fn_i7_mgl_metaclass(proc, id) != i7_mgl_Object) return 0;
    return proc->state.object_tree_child[id];
}
i7word_t fn_i7_mgl_children(i7process_t *proc, i7word_t id) {
    if (fn_i7_mgl_metaclass(proc, id) != i7_mgl_Object) return 0;
    i7word_t c=0;
    for (int i=0; i<i7_max_objects; i++) if (proc->state.object_tree_parent[i] == id) c++;
    return c;
}
i7word_t fn_i7_mgl_sibling(i7process_t *proc, i7word_t id) {
    if (fn_i7_mgl_metaclass(proc, id) != i7_mgl_Object) return 0;
    return proc->state.object_tree_sibling[id];
}

void i7_move(i7process_t *proc, i7word_t obj, i7word_t to) {
    if ((obj <= 0) || (obj >= i7_max_objects)) return;
    int p = proc->state.object_tree_parent[obj];
    if (p) {
        if (proc->state.object_tree_child[p] == obj) {
            proc->state.object_tree_child[p] = proc->state.object_tree_sibling[obj];
        } else {
            int c = proc->state.object_tree_child[p];
            while (c != 0) {
                if (proc->state.object_tree_sibling[c] == obj) {
                    proc->state.object_tree_sibling[c] = proc->state.object_tree_sibling[obj];
                    break;
                }
                c = proc->state.object_tree_sibling[c];
            }
        }
    }
    proc->state.object_tree_parent[obj] = to;
    proc->state.object_tree_sibling[obj] = 0;
    if (to) {
        proc->state.object_tree_sibling[obj] = proc->state.object_tree_child[to];
        proc->state.object_tree_child[to] = obj;
    }
}