To generate the initial state of storage for instances and their properties, and all associated metadata.


§1.

int properties_written = FALSE;
int FBNA_found = FALSE, properties_found = FALSE, attribute_slots_used = 0;

int no_property_frames = 0, no_instance_frames = 0, no_kind_frames = 0;
inter_tree_node **property_frames = NULL;
inter_tree_node **instance_frames = NULL;
inter_tree_node **kind_frames = NULL;

void CodeGen::IP::prepare(code_generation *gen) {
    properties_written = FALSE;
    FBNA_found = FALSE;
    properties_found = FALSE;
    attribute_slots_used = 0;
    no_property_frames = 0; no_instance_frames = 0; no_kind_frames = 0;
    Inter::Tree::traverse(gen->from, CodeGen::IP::count, NULL, NULL, 0);
    if (no_property_frames > 0)
        property_frames = (inter_tree_node **)
            (Memory::calloc(no_property_frames, sizeof(inter_tree_node *), CODE_GENERATION_MREASON));
    if (no_instance_frames > 0)
        instance_frames = (inter_tree_node **)
            (Memory::calloc(no_instance_frames, sizeof(inter_tree_node *), CODE_GENERATION_MREASON));
    if (no_kind_frames > 0)
        kind_frames = (inter_tree_node **)
            (Memory::calloc(no_kind_frames, sizeof(inter_tree_node *), CODE_GENERATION_MREASON));
    no_property_frames = 0; no_instance_frames = 0; no_kind_frames = 0;
    Inter::Tree::traverse(gen->from, CodeGen::IP::store, NULL, NULL, 0);
}

void CodeGen::IP::count(inter_tree *I, inter_tree_node *P, void *state) {
    if (P->W.data[ID_IFLD] == PROPERTY_IST) no_property_frames++;
    if (P->W.data[ID_IFLD] == INSTANCE_IST) no_instance_frames++;
    if (P->W.data[ID_IFLD] == KIND_IST) no_kind_frames++;
}

void CodeGen::IP::store(inter_tree *I, inter_tree_node *P, void *state) {
    if (P->W.data[ID_IFLD] == PROPERTY_IST) property_frames[no_property_frames++] = P;
    if (P->W.data[ID_IFLD] == INSTANCE_IST) instance_frames[no_instance_frames++] = P;
    if (P->W.data[ID_IFLD] == KIND_IST) kind_frames[no_kind_frames++] = P;
}

void CodeGen::IP::write_properties(code_generation *gen) {
    if (properties_written == FALSE) {
        generated_segment *saved = CodeGen::select(gen, CodeGen::Targets::default_segment(gen));
        text_stream *TO = CodeGen::current(gen);
        if (CodeGen::CL::quartet_present()) {
            WRITE_TO(TO, "Object Compass \"compass\" has concealed;\n");
            WRITE_TO(TO, "Object thedark \"(darkness object)\";\n");
            WRITE_TO(TO, "Object InformParser \"(Inform Parser)\" has proper;\n");
            WRITE_TO(TO, "Object InformLibrary \"(Inform Library)\" has proper;\n");
        }
        CodeGen::IP::knowledge(gen);
        CodeGen::deselect(gen, saved);
        properties_written = TRUE;
    }
}

§2. Representing instances in I6. Partly for historical reasons, partly to squeeze performance out of the virtual machines used in traditional parser IF, the I6 run-time implementation of instances and their properties is complicated.

The main complication is that there are two sorts of instance: objects, such as doors and people, and everything else, such as scenes. The two sorts are handled equally in Inter, but have completely different run-time representations in I6:

§3. Representing properties in I6. Both sorts of instance can have properties; for example:

A supporter has a number called carrying capacity.

A scene has a number called completion score.

allows a property to be held by object instances (supporters) and value instances (scenes). To the writer of I7 source text, no distinction between these cases is visible, and the same is true of Inter code.

How to store these at run-time is not so straightforward. Speed and compactness are unusually important here, and constraints imposed by the virtual machine targeted by I6 add further complications.

In practice, property access is slightly faster for object instances, and property storage is slightly more compact for value instances, which is probably the right bargain.

§4. Properties. Properties in I7 are of two sorts: either-or, which behave adjectivally, such as "open"; and value, which behave as nouns, such as "carrying capacity". We can distinguish these because the I7 compiler annotates the property name symbols with the EITHER_OR_IANN flag. It also always gives either-or properties the kind K_truth_state, but note that a few value properties also have this kind, so the annotation is the only way to be sure.

Some either-or properties of object instances can be stored as I6 "attributes". This is memory-efficient and fast at run-time: but only a limited number can be stored this way. Here we choose which.

void CodeGen::IP::property(inter_tree *I, inter_symbol *prop_name, code_generation *gen) {
    if (prop_name == NULL) internal_error("bad property");
    if (Inter::Symbols::read_annotation(prop_name, EITHER_OR_IANN) >= 0) {
        int translated = FALSE;
        if (Inter::Symbols::read_annotation(prop_name, EXPLICIT_ATTRIBUTE_IANN) >= 0) translated = TRUE;
        if (Inter::Symbols::read_annotation(prop_name, ASSIMILATED_IANN) >= 0) translated = TRUE;

        int make_attribute = NOT_APPLICABLE;
        Any either/or property which can belong to a value instance is ineligible4.1;
        An either/or property translated to an attribute declared in the I6 template must be chosen4.2;
        Otherwise give away attribute slots on a first-come-first-served basis4.3;
        if (make_attribute == TRUE) Inter::Symbols::set_flag(prop_name, ATTRIBUTE_MARK_BIT);
        Check against the I7 compiler's beliefs4.4;

        if (make_attribute) {
            Declare as an I6 attribute4.5;
        } else {
            Worry about the FBNA4.6;
        }
    }
}

§4.1. The dodge of using an attribute to store an either-or property won't work for properties of value instances, because then the value-property-holder object couldn't store the necessary table address (see next section). So we must rule out any property which might belong to any value.

Any either/or property which can belong to a value instance is ineligible4.1 =

    inter_node_list *PL =
        Inter::Warehouse::get_frame_list(
            Inter::Tree::warehouse(I),
            Inter::Property::permissions_list(prop_name));
    if (PL == NULL) internal_error("no permissions list");
    inter_tree_node *X;
    LOOP_THROUGH_INTER_NODE_LIST(X, PL) {
        inter_symbol *owner_name =
            Inter::SymbolsTables::symbol_from_id(Inter::Packages::scope_of(X), X->W.data[OWNER_PERM_IFLD]);
        if (owner_name == NULL) internal_error("bad owner");
        inter_symbol *owner_kind = NULL;
        inter_tree_node *D = Inter::Symbols::definition(owner_name);
        if ((D) && (D->W.data[ID_IFLD] == INSTANCE_IST)) {
            owner_kind = Inter::Instance::kind_of(owner_name);
        } else {
            owner_kind = owner_name;
        }
        if (CodeGen::IP::is_kind_of_object(owner_kind) == FALSE) make_attribute = FALSE;
    }

§4.2. An either/or property which has been deliberately equated to an I6 template attribute with a sentence like...

The fixed in place property translates into I6 as "static".

...is (we must assume) already declared as an Attribute, so we need to remember that it's implemented as an attribute when compiling references to it.

An either/or property translated to an attribute declared in the I6 template must be chosen4.2 =

    if (translated) make_attribute = TRUE;

§4.3. We have in theory 48 Attribute slots to use up, that being the number available in versions 5 and higher of the Z-machine, but the I6 template layer consumes so many that only a few slots remain for the user's own creations. Giving these away to the first-created properties is the simplest way to allocate them, and in fact it works pretty well, because the first such either/or properties tend to be created in extensions and to be frequently used.

define ATTRIBUTE_SLOTS_TO_GIVE_AWAY 11

Otherwise give away attribute slots on a first-come-first-served basis4.3 =

    if (make_attribute == NOT_APPLICABLE) {
        if (attribute_slots_used++ < ATTRIBUTE_SLOTS_TO_GIVE_AWAY)
            make_attribute = TRUE;
        else
            make_attribute = FALSE;
    }

§4.4. At present the I7 compiler makes a decision matching this one for its own internal needs. We want to make sure its decision matches ours, so we check that here. (It tells us by marking the property name with the ATTRIBUTE_IANN annotation.) But this code will eventually go.

Check against the I7 compiler's beliefs4.4 =

    int made_attribute = FALSE;
    if (Inter::Symbols::read_annotation(prop_name, ATTRIBUTE_IANN) >= 0)
        made_attribute = TRUE;
    if (made_attribute != make_attribute) {
        LOG("Disagree on %S: %d vs %d\n", prop_name->symbol_name, made_attribute, make_attribute);
        internal_error("attribute allocation dispute");
    }

§4.5. A curiosity of I6 is that attributes must be declared before use, whereas properties need not be. We generate suitable Attribute statements here. Note that if the property has been translated onto an existing I6 name, then we assume that's the name of an attribute already declared (for example in the I6 template, or some extension), and we therefore do nothing.

Declare as an I6 attribute4.5 =

    generated_segment *saved = CodeGen::select(gen, CodeGen::Targets::basic_constant_segment(gen, 1));
    if (Inter::Symbols::read_annotation(prop_name, ASSIMILATED_IANN) >= 0) {
        text_stream *A = Inter::Symbols::get_translate(prop_name);
        if (A == NULL) A = CodeGen::CL::name(prop_name);
        WRITE_TO(CodeGen::current(gen), "Attribute %S;\n", A);
    } else {
        if (translated == FALSE)
            WRITE_TO(CodeGen::current(gen), "Attribute %S;\n", CodeGen::CL::name(prop_name));
    }
    CodeGen::deselect(gen, saved);

§4.6. The weak point in our scheme for making some either/or properties into Attributes is that run-time code is going to need a fast way to determine which, since they have to be accessed differently. We rely on the facts that

Thus an either/or property P must be an I6 attribute if P < F and must be an I6 property if P >= F, where F is the earliest-defined either/or property which isn't stored as an attribute.

This cutoff value F is customarily called FBNA, the "first boolean not an attribute". (Perhaps she ought to be called FEONA.) The following compiles an I6 constant for this value.

Worry about the FBNA4.6 =

    if (FBNA_found == FALSE) {
        FBNA_found = TRUE;
        generated_segment *saved = CodeGen::select(gen, CodeGen::Targets::constant_segment(gen));
        CodeGen::Targets::begin_constant(gen, I"FBNA_PROP_NUMBER", TRUE);
        WRITE_TO(CodeGen::current(gen), "%S", CodeGen::CL::name(prop_name));
        CodeGen::Targets::end_constant(gen, I"FBNA_PROP_NUMBER");
        CodeGen::deselect(gen, saved);
    }

§5. It's unlikely, but just possible, that no FBNAs ever exist, so after the above has been tried on all properties:

void CodeGen::IP::knowledge(code_generation *gen) {
    text_stream *OUT = CodeGen::current(gen);
    inter_tree *I = gen->from;
    if ((FBNA_found == FALSE) && (properties_found)) {
        generated_segment *saved = CodeGen::select(gen, CodeGen::Targets::constant_segment(gen));
        CodeGen::Targets::begin_constant(gen, I"FBNA_PROP_NUMBER", TRUE);
        WRITE_TO(CodeGen::current(gen), "MAX_POSITIVE_NUMBER");
        CodeGen::Targets::end_constant(gen, I"FBNA_PROP_NUMBER");
        CodeGen::deselect(gen, saved);
    }
    inter_symbol **all_props_in_source_order = NULL;
    inter_symbol **props_in_source_order = NULL;
    int no_properties = 0, total_no_properties = 0;
    Make a list of properties in source order5.1;
    Compile the property numberspace forcer5.5;

    inter_symbol **kinds_in_source_order = NULL;
    inter_symbol **kinds_in_declaration_order = NULL;
    Make a list of kinds in source order5.2;

    inter_symbol **instances_in_declaration_order = NULL;
    Make a list of instances in declaration order5.4;

    if (properties_found) Write Value Property Holder objects for each kind of value instance5.8;
    Make a list of kinds in declaration order5.3;
    Annotate kinds of object with a sequence counter5.6;
    Write the KindHierarchy array5.7;
    Write an I6 Class definition for each kind of object5.9;
    Write an I6 Object definition for each object instance5.10;
    Write the property metadata array5.11;

    Stub the properties5.12;
}

§5.1. Make a list of properties in source order5.1 =

    for (int i=0; i<no_property_frames; i++) {
        inter_tree_node *P = property_frames[i];
        inter_symbol *prop_name = Inter::SymbolsTables::symbol_from_frame_data(P, DEFN_PROP_IFLD);
        if (Inter::Symbols::read_annotation(prop_name, ASSIMILATED_IANN) != 1)
            total_no_properties++;
        if (Inter::Symbols::read_annotation(prop_name, ASSIMILATED_IANN) != 1)
            no_properties++;
    }
    if (no_properties > 0) properties_found = TRUE;

    if (total_no_properties > 0) {
        all_props_in_source_order = (inter_symbol **)
            (Memory::calloc(total_no_properties, sizeof(inter_symbol *), CODE_GENERATION_MREASON));
        int c = 0;
        for (int i=0; i<no_property_frames; i++) {
            inter_tree_node *P = property_frames[i];
            inter_symbol *prop_name = Inter::SymbolsTables::symbol_from_frame_data(P, DEFN_PROP_IFLD);
            if (Inter::Symbols::read_annotation(prop_name, ASSIMILATED_IANN) != 1)
                all_props_in_source_order[c++] = prop_name;
            else
                CodeGen::IP::property(I, prop_name, gen);
        }
        qsort(all_props_in_source_order, (size_t) total_no_properties, sizeof(inter_symbol *),
            CodeGen::IP::compare_kind_symbols);
        for (int p=0; p<total_no_properties; p++) {
            inter_symbol *prop_name = all_props_in_source_order[p];
            CodeGen::IP::property(I, prop_name, gen);
        }
    }

    if (properties_found) {
        props_in_source_order = (inter_symbol **)
            (Memory::calloc(no_properties, sizeof(inter_symbol *), CODE_GENERATION_MREASON));
        int c = 0;
        for (int i=0; i<no_property_frames; i++) {
            inter_tree_node *P = property_frames[i];
            inter_symbol *prop_name = Inter::SymbolsTables::symbol_from_frame_data(P, DEFN_PROP_IFLD);
            if (Inter::Symbols::read_annotation(prop_name, ASSIMILATED_IANN) != 1)
                props_in_source_order[c++] = prop_name;
        }

        for (int i=0; i<no_property_frames; i++) {
            inter_tree_node *P = property_frames[i];
            inter_symbol *prop_name = Inter::SymbolsTables::symbol_from_frame_data(P, DEFN_PROP_IFLD);
            if ((Inter::Symbols::read_annotation(prop_name, ASSIMILATED_IANN) == 1) &&
                (Inter::Symbols::read_annotation(prop_name, ATTRIBUTE_IANN) != 1)) {
                CodeGen::Targets::declare_property(gen, prop_name, TRUE);
            }
        }
    }

§5.2. Make a list of kinds in source order5.2 =

    if (no_kind_frames == 0) return;

    kinds_in_source_order = (inter_symbol **)
        (Memory::calloc(no_kind_frames, sizeof(inter_symbol *), CODE_GENERATION_MREASON));
    for (int i=0; i<no_kind_frames; i++) {
        inter_tree_node *P = kind_frames[i];
        inter_symbol *kind_name = Inter::SymbolsTables::symbol_from_frame_data(P, DEFN_KIND_IFLD);
        kinds_in_source_order[i] = kind_name;
    }
    qsort(kinds_in_source_order, (size_t) no_kind_frames, sizeof(inter_symbol *),
        CodeGen::IP::compare_kind_symbols);

§5.3. Make a list of kinds in declaration order5.3 =

    kinds_in_declaration_order = (inter_symbol **)
        (Memory::calloc(no_kind_frames, sizeof(inter_symbol *), CODE_GENERATION_MREASON));
    for (int i=0; i<no_kind_frames; i++) {
        inter_tree_node *P = kind_frames[i];
        inter_symbol *kind_name = Inter::SymbolsTables::symbol_from_frame_data(P, DEFN_KIND_IFLD);
        kinds_in_declaration_order[i] = kind_name;
    }
    qsort(kinds_in_declaration_order, (size_t) no_kind_frames, sizeof(inter_symbol *),
        CodeGen::IP::compare_kind_symbols_decl);

§5.4. Make a list of instances in declaration order5.4 =

    if (no_instance_frames > 0) {
        instances_in_declaration_order = (inter_symbol **)
            (Memory::calloc(no_instance_frames, sizeof(inter_symbol *), CODE_GENERATION_MREASON));
        for (int i=0; i<no_instance_frames; i++) {
            inter_tree_node *P = instance_frames[i];
            inter_symbol *inst_name = Inter::SymbolsTables::symbol_from_frame_data(P, DEFN_INST_IFLD);
            instances_in_declaration_order[i] = inst_name;
        }
        qsort(instances_in_declaration_order, (size_t) no_instance_frames, sizeof(inter_symbol *),
            CodeGen::IP::compare_kind_symbols_decl);
    }

§5.5. But there's a snag. The above assumes that property values will have the same ordering at run-time as their definition order here, but that isn't necessarily true. The run-time ordering depends on how early in the I6 source code they appear, and that in turn depends on which objects have which properties, and so on — nothing we can rely on.

We finesse this by creating the following spurious object before the class hierarchy and object tree are created: its properties are therefore all new creations, and since we declare them in I7 creation order, they are now allocated I6 property numbers in a sequence matching this. (We don't care about the numbering of non-either/or properties, so we don't bother to force them.)

Compile the property numberspace forcer5.5 =

    if (properties_found) {
        WRITE("Object property_numberspace_forcer\n"); INDENT;
        for (int p=0; p<no_properties; p++) {
            inter_symbol *prop_name = props_in_source_order[p];
            if (Inter::Symbols::get_flag(prop_name, ATTRIBUTE_MARK_BIT) == FALSE) {
                inter_symbol *kind_name = Inter::Property::kind_of(prop_name);
                if (kind_name == truth_state_kind_symbol) {
                    WRITE("  with %S false\n", CodeGen::CL::name(prop_name));
                }
            }
        }
        OUTDENT; WRITE(";\n");
    }

§5.6. Annotate kinds of object with a sequence counter5.6 =

    inter_t c = 1;
    for (int i=0; i<no_kind_frames; i++) {
        inter_symbol *kind_name = kinds_in_source_order[i];
        if (CodeGen::IP::is_kind_of_object(kind_name))
            Inter::Symbols::annotate_i(kind_name, OBJECT_KIND_COUNTER_IANN,  c++);
    }

§5.7. The kind inheritance tree. We begin with an array providing metadata on the kinds of object: there are just two words per kind — the Inform 6 class corresponding to the kind, then the instance count for its own kind. For instance, "door" is usually kind number 4, so it occupies record 4 in this array — words 8 and 9. Word 8 will be K4_door, the Inform 6 class for doors, and word 9 will be the number 2, meaning kind number 2, "thing". This tells us that a door is a kind of thing. In this way, we store the hierarchy of N kinds in 2N words of memory; it's needed at run-time for checking dynamically that property usage is legal.

Write the KindHierarchy array5.7 =

    int no_kos = 0;
    for (int i=0; i<no_kind_frames; i++) {
        inter_symbol *kind_name = kinds_in_source_order[i];
        if (CodeGen::IP::is_kind_of_object(kind_name)) no_kos++;
    }

    if (no_kos > 0) {
        WRITE("Array KindHierarchy --> K0_kind (0)");
        for (int i=0; i<no_kind_frames; i++) {
            inter_symbol *kind_name = kinds_in_source_order[i];
            if (CodeGen::IP::is_kind_of_object(kind_name)) {
                inter_symbol *super_name = Inter::Kind::super(kind_name);
                if ((super_name) && (super_name != object_kind_symbol)) {
                    WRITE(" %S (%d)", CodeGen::CL::name(kind_name),
                        CodeGen::IP::kind_of_object_count(super_name));
                } else {
                    WRITE(" %S (0)", CodeGen::CL::name(kind_name));
                }
            }
        }
        WRITE(";\n");
    } else {
        WRITE("Array KindHierarchy --> (0) (0);\n");
    }

§5.8. Lookup mechanism for properties of value instances. As noted above, if K is a kind which can have properties but is not a subkind of object, then a property for instances of K is stored in an array called a "stick". At run-time, given the property number and K, we will need to find where in memory the correct stick is, and this needs to be quick.

This is essentially a dictionary lookup problem and we solve it by compiling a faux object V for each K, called a "value property holder" or VPH. Given K we find V by looking it up in the array value_property_holders.

Once we know V, we then look up V.P to get the address of the stick for property P, something which the virtual machine can do quickly.

This comes at the cost of several hundred bytes of overhead, which we don't take lightly in the Z-machine. But speed and flexibility are worth more.

Write Value Property Holder objects for each kind of value instance5.8 =

    Define the I6 VPH class5.8.1;
    inter_symbol *max_weak_id = Inter::SymbolsTables::symbol_from_name_in_main_or_basics(I, I"MAX_WEAK_ID");
    if (max_weak_id) {
        inter_tree_node *P = Inter::Symbols::definition(max_weak_id);
        int M = (int) P->W.data[DATA_CONST_IFLD + 1];

        Decide who gets a VPH5.8.2;
        Write the VPH lookup array5.8.3;
        for (int w=1; w<M; w++) {
            for (int i=0; i<no_kind_frames; i++) {
                inter_symbol *kind_name = kinds_in_source_order[i];
                if (CodeGen::IP::weak_id(kind_name) == w) {
                    if (Inter::Symbols::get_flag(kind_name, VPH_MARK_BIT)) {
                        TEMPORARY_TEXT(sticks);
                        WRITE("VPH_Class VPH_%d\n    with value_range %d\n",
                            w, Inter::Kind::instance_count(kind_name));
                        for (int p=0; p<no_properties; p++) {
                            inter_symbol *prop_name = props_in_source_order[p];
                            CodeGen::unmark(prop_name);
                        }
                        inter_node_list *FL =
                            Inter::Warehouse::get_frame_list(Inter::Tree::warehouse(I), Inter::Kind::permissions_list(kind_name));
                        Work through this frame list of permissions5.8.4;
                        for (int in=0; in<no_instance_frames; in++) {
                            inter_symbol *inst_name = instances_in_declaration_order[in];
                            if (Inter::Kind::is_a(Inter::Instance::kind_of(inst_name), kind_name)) {
                                inter_node_list *FL =
                                    Inter::Warehouse::get_frame_list(Inter::Tree::warehouse(I), Inter::Instance::permissions_list(inst_name));
                                Work through this frame list of permissions5.8.4;
                            }
                        }
                        WRITE(";\n%S\n", sticks);
                        DISCARD_TEXT(sticks);
                    }
                }
            }
        }
    }

§5.8.1. It's convenient to be able to distinguish, at run-time, which objects are the VPH objects used only for kind-property indexing; we can test if O is such an object with the I6 condition (O ofclass VPH_Class).

The property value_range for a VPH object is the number N such that the legal values at run-time for this kind are 1, 2, 3, ..., N: or in other words, the number of instances of this kind.

Define the I6 VPH class5.8.1 =

    WRITE("Class VPH_Class;\n");

§5.8.2. Decide who gets a VPH5.8.2 =

    for (int i=0; i<no_kind_frames; i++) {
        inter_symbol *kind_name = kinds_in_source_order[i];
        if (CodeGen::IP::is_kind_of_object(kind_name)) continue;
        if (kind_name == object_kind_symbol) continue;
        if (kind_name == unchecked_kind_symbol) continue;
        int vph_me = FALSE;
        inter_node_list *FL =
            Inter::Warehouse::get_frame_list(Inter::Tree::warehouse(I), Inter::Kind::permissions_list(kind_name));
        if (FL->first_in_inl) vph_me = TRUE;
        else for (int in=0; in<no_instance_frames; in++) {
            inter_symbol *inst_name = instances_in_declaration_order[in];
            if (Inter::Kind::is_a(Inter::Instance::kind_of(inst_name), kind_name)) {
                inter_node_list *FL =
                    Inter::Warehouse::get_frame_list(Inter::Tree::warehouse(I), Inter::Instance::permissions_list(inst_name));
                if (FL->first_in_inl) vph_me = TRUE;
            }
        }
        if (vph_me) Inter::Symbols::set_flag(kind_name, VPH_MARK_BIT);
    }

§.1. Look through this frame list of permissions.1 =

§5.8.3. This array is indexed by the weak kind ID of K. The entry is 0 if K doesn't have a VPH, or the object number of its VPH if it has.

Write the VPH lookup array5.8.3 =

    WRITE("Array value_property_holders --> 0");
    int vph = 0;
    for (int w=1; w<M; w++) {
        int written = FALSE;
        for (int i=0; i<no_kind_frames; i++) {
            inter_symbol *kind_name = kinds_in_source_order[i];
            if (CodeGen::IP::weak_id(kind_name) == w) {
                if (Inter::Symbols::get_flag(kind_name, VPH_MARK_BIT)) {
                    written = TRUE;
                    WRITE(" VPH_%d", w);
                }
            }
        }
        if (written) vph++; else WRITE(" 0");
    }
    WRITE(";\n");
    Stub a faux VPH if none have otherwise been created5.8.3.1;

§5.8.3.1. In the event that no value instances have properties, there'll be no instances of the VPH_Class, and no I6 object will be compiled with a value_range property; that means I6 code referring to this will fail with an I6 error. We don't want that, so if necessary we compile a useless VPH object just to force the property into being.

Stub a faux VPH if none have otherwise been created5.8.3.1 =

    if (vph == 0) WRITE("VPH_Class UnusedVPH with value_range 0;\n");

§5.8.4. Work through this frame list of permissions5.8.4 =

    inter_tree_node *X;
    LOOP_THROUGH_INTER_NODE_LIST(X, FL) {
        inter_symbol *prop_name = Inter::SymbolsTables::symbol_from_frame_data(X, PROP_PERM_IFLD);
        if (prop_name == NULL) internal_error("no property");
        if (CodeGen::marked(prop_name) == FALSE) {
            CodeGen::mark(prop_name);
            text_stream *call_it = CodeGen::CL::name(prop_name);
            WRITE("    with %S ", call_it);
            if (X->W.data[STORAGE_PERM_IFLD]) {
                inter_symbol *store = Inter::SymbolsTables::symbol_from_frame_data(X, STORAGE_PERM_IFLD);
                if (store == NULL) internal_error("bad PP in inter");
                WRITE("%S", CodeGen::CL::name(store));
            } else {
                Compile a stick of property values and put its address here5.8.4.1;
            }
            WRITE("\n");
        }
    }

§5.8.4.1. These little arrays are sticks of property values, and they are laid out as if they were column arrays in a Table data structure. This means they must be table arrays (which wastes one word of memory) and must have blanked-out table column header words at the front (which wastes a further COL_HSIZE words). But the cost is a simple overhead, not rising with the number of instances, and it's a small price for the gain in simplicity and speed.

The entries here are bracketed to avoid the Inform 6 syntax ambiguity between 4 -5 (two entries, four followed by minus five) and 4-5 (one entry, just minus one). Inform 6 always uses the second interpretation, so just in case there are negative literal integers in these array entries, we use brackets: thus (4) (-5). This cannot be confused with function calling because I6 doesn't allow function calls in a constant context.

Compile a stick of property values and put its address here5.8.4.1 =

    TEMPORARY_TEXT(ident);
    WRITE_TO(ident, "KOVP_%d_P%d", w, CodeGen::IP::pnum(prop_name));
    WRITE("%S", ident);
    WRITE_TO(sticks, "Array %S table 0 0", ident);
    for (int j=0; j<no_instance_frames; j++) {
        inter_symbol *inst_name = instances_in_declaration_order[j];
        if (Inter::Kind::is_a(Inter::Instance::kind_of(inst_name), kind_name)) {
            int found = 0;
            inter_node_list *PVL =
                Inter::Node::ID_to_frame_list(X,
                    Inter::Instance::properties_list(inst_name));
            Work through this frame list of values5.8.4.1.1;
            PVL = Inter::Node::ID_to_frame_list(X,
                    Inter::Kind::properties_list(kind_name));
            Work through this frame list of values5.8.4.1.1;
            if (found == 0) WRITE_TO(sticks, " (0)");
        }
    }
    WRITE_TO(sticks, ";\n");

§5.8.4.1.1. Work through this frame list of values5.8.4.1.1 =

    inter_tree_node *Y;
    LOOP_THROUGH_INTER_NODE_LIST(Y, PVL) {
        inter_symbol *p_name = Inter::SymbolsTables::symbol_from_id(Inter::Packages::scope_of(Y), Y->W.data[PROP_PVAL_IFLD]);
        if ((p_name == prop_name) && (found == 0)) {
            found = 1;
            inter_t v1 = Y->W.data[DVAL1_PVAL_IFLD];
            inter_t v2 = Y->W.data[DVAL2_PVAL_IFLD];
            WRITE_TO(sticks, " (");
            CodeGen::select_temporary(gen, sticks);
            CodeGen::CL::literal(gen, NULL, Inter::Packages::scope_of(Y), v1, v2, FALSE);
            CodeGen::deselect_temporary(gen);
            WRITE_TO(sticks, ")");
        }
    }

§5.9. Write an I6 Class definition for each kind of object5.9 =

    for (int i=0; i<no_kind_frames; i++) {
        inter_symbol *kind_name = kinds_in_declaration_order[i];
        if ((kind_name == object_kind_symbol) ||
            (CodeGen::IP::is_kind_of_object(kind_name))) {
            WRITE("Class %S\n", CodeGen::CL::name(kind_name));
            inter_symbol *super_name = Inter::Kind::super(kind_name);
            if (super_name) WRITE("    class %S\n", CodeGen::CL::name(super_name));
            CodeGen::IP::append(gen, kind_name);
            inter_node_list *FL =
                Inter::Warehouse::get_frame_list(Inter::Tree::warehouse(I), Inter::Kind::properties_list(kind_name));
            CodeGen::IP::plist(gen, FL);
            WRITE(";\n\n");
        }
    }

§5.10. Write an I6 Object definition for each object instance5.10 =

    for (int i=0; i<no_instance_frames; i++) {
        inter_symbol *inst_name = instances_in_declaration_order[i];
        inter_tree_node *D = Inter::Symbols::definition(inst_name);
        CodeGen::IP::object_instance(gen, D);
    }

§5.11. The following lets the run-time environment know what properties are called, and which kinds of object are allowed to have them. This might look a little odd: why does the run-time code need to know any of that?

The answer is that the Inform compiler will prevent grossly type-unsafe property accesses at compile time — for example, asking if a number is "recurring" (an either/or property of scenes), which can be ruled out because numbers and scenes are wholly disjoint as values. But it will allow any object property of any object to be accessed, because it's not usually possible for the typechecker to know if an object value O is a vehicle, a direction, and so on. So the finer access controls for properties of objects are left until run-time (whereas no such regime is needed for properties of values). To make this possible, we need to tell the run-time code what is and is not allowed.

The property_metadata array is organised as a sequence of variable-sized records. Because of that, we also need arrays telling us where to find the start of the record for a given I6 property (or attribute): we have two of these, called attributed_property_offsets and valued_property_offsets. The dummy value -1 means that the relevant property has no metadata record, though this won't happen for any property created by I7 source text.

Write the property metadata array5.11 =

    if (properties_found) {
        TEMPORARY_TEXT(pm_writer);
        WRITE_TO(pm_writer, "[ CreatePropertyOffsets i;\n"); STREAM_INDENT(pm_writer);
        WRITE_TO(pm_writer, "for (i=0: i<attributed_property_offsets_SIZE: i++)"); STREAM_INDENT(pm_writer);
        WRITE_TO(pm_writer, "attributed_property_offsets-->i = -1;\n"); STREAM_OUTDENT(pm_writer);
        WRITE_TO(pm_writer, "for (i=0: i<valued_property_offsets_SIZE: i++)"); STREAM_INDENT(pm_writer);
        WRITE_TO(pm_writer, "valued_property_offsets-->i = -1;\n"); STREAM_OUTDENT(pm_writer);

        WRITE("Array property_metadata -->\n"); INDENT;
        int pos = 0;
        for (int p=0; p<no_properties; p++) {
            inter_symbol *prop_name = props_in_source_order[p];
            WRITE("! offset %d: property %S\n", pos, CodeGen::CL::name(prop_name));
            if (Inter::Symbols::get_flag(prop_name, ATTRIBUTE_MARK_BIT))
                WRITE_TO(pm_writer, "attributed_property_offsets");
            else
                WRITE_TO(pm_writer, "valued_property_offsets");
            WRITE_TO(pm_writer, "-->%S = %d;\n", CodeGen::CL::name(prop_name), pos);

            Write the property name in double quotes5.11.1;
            Write a list of kinds or objects which are permitted to have this property5.11.2;
            WRITE("NULL\n"); pos++;
        }
        OUTDENT; WRITE(";\n");
        STREAM_OUTDENT(pm_writer);
        WRITE_TO(pm_writer, "];\n");
        WRITE("%S", pm_writer);
        DISCARD_TEXT(pm_writer);
    }

§5.11.1. Write the property name in double quotes5.11.1 =

    WRITE("\"");
    int N = Inter::Symbols::read_annotation(prop_name, PROPERTY_NAME_IANN);
    if (N <= 0) WRITE("<nameless>");
    else WRITE("%S", Inter::Warehouse::get_text(Inter::Tree::warehouse(I), (inter_t) N));
    WRITE("\" ");
    pos++;

§5.11.2. A complete list here would be wasteful both of space and run-time checking time, but we only need a list \(O_1, O_2, ..., O_k\) such that for each \(W\) allowed to have the property, either \(W = O_i\) for some \(i\), or \(W\) is of kind \(O_i\) for some \(i\) (perhaps indirectly).

In a tricksy complication, we need to allow for the possibility that two or more different I7 properties are actually equal at run-time. This wouldn't happen by itself, but does happen if two different properties are translated to the same I6 property, and in fact the template does this: "lighted" and "lit" both translate to I6 light. The only way to reconcile this is to make the list a union of the lists of both. This does mean the routine runs in \(O(P^2N)\) time, where \(P\) is the number of properties and \(N\) the number of objects, but we can live with that. \(P\) does not in practice rise linearly with the size of the source text, even though \(N\) does.

Write a list of kinds or objects which are permitted to have this property5.11.2 =

    for (int e=0; e<no_properties; e++) {
        inter_symbol *eprop_name = props_in_source_order[e];
        if (Str::eq(CodeGen::CL::name(eprop_name), CodeGen::CL::name(prop_name))) {
            inter_node_list *EVL =
                Inter::Warehouse::get_frame_list(Inter::Tree::warehouse(I), Inter::Property::permissions_list(eprop_name));

            List any O with an explicit permission5.11.2.1;
            List all top-level kinds if "object" itself has an explicit permission5.11.2.2;
        }
    }

§5.11.2.1. List any O with an explicit permission5.11.2.1 =

    for (int k=0; k<no_kind_frames; k++) {
        inter_symbol *kind_name = kinds_in_source_order[k];
        if (CodeGen::IP::is_kind_of_object(kind_name)) {
            inter_tree_node *X;
            LOOP_THROUGH_INTER_NODE_LIST(X, EVL) {
                inter_symbol *owner_name = Inter::SymbolsTables::symbol_from_frame_data(X, OWNER_PERM_IFLD);
                if (owner_name == kind_name) {
                    WRITE("%S ", CodeGen::CL::name(kind_name));
                    pos++;
                }
            }
        }
    }
    for (int in=0; in<no_instance_frames; in++) {
        inter_symbol *inst_name = instances_in_declaration_order[in];
        if (CodeGen::IP::is_kind_of_object(Inter::Instance::kind_of(inst_name))) {
            inter_tree_node *X;
            LOOP_THROUGH_INTER_NODE_LIST(X, EVL) {
                inter_symbol *owner_name = Inter::SymbolsTables::symbol_from_frame_data(X, OWNER_PERM_IFLD);
                if (owner_name == inst_name) {
                    WRITE("%S ", CodeGen::CL::name(inst_name));
                    pos++;
                }
            }
        }
    }

§5.11.2.2. List all top-level kinds if "object" itself has an explicit permission5.11.2.2 =

    if (Inter::Symbols::read_annotation(eprop_name, RTO_IANN) < 0) {
        inter_tree_node *X;
        LOOP_THROUGH_INTER_NODE_LIST(X, EVL) {
            inter_symbol *owner_name = Inter::SymbolsTables::symbol_from_frame_data(X, OWNER_PERM_IFLD);
            if (owner_name == object_kind_symbol) {
                WRITE("K0_kind "); pos++;
                for (int k=0; k<no_kind_frames; k++) {
                    inter_symbol *kind_name = kinds_in_source_order[k];
                    if (Inter::Kind::super(kind_name) == object_kind_symbol) {
                        WRITE("%S ", CodeGen::CL::name(kind_name));
                        pos++;
                    }
                }
            }
        }
    }

§5.12. Stub the properties5.12 =

    for (int p=0; p<no_properties; p++) {
        inter_symbol *prop_name = props_in_source_order[p];
        if (Inter::Symbols::read_annotation(prop_name, ASSIMILATED_IANN) != 1) {
            CodeGen::Targets::declare_property(gen, prop_name, FALSE);
        }
    }

§6. Instances.

void CodeGen::IP::instance(code_generation *gen, inter_tree_node *P) {
    inter_symbol *inst_name = Inter::SymbolsTables::symbol_from_frame_data(P, DEFN_INST_IFLD);
    inter_symbol *inst_kind = Inter::SymbolsTables::symbol_from_frame_data(P, KIND_INST_IFLD);

    if (Inter::Kind::is_a(inst_kind, object_kind_symbol) == FALSE) {
        inter_t val1 = P->W.data[VAL1_INST_IFLD];
        inter_t val2 = P->W.data[VAL2_INST_IFLD];
        text_stream *OUT = CodeGen::current(gen);
        int defined = TRUE;
        if (val1 == UNDEF_IVAL) defined = FALSE;
        CodeGen::Targets::begin_constant(gen, CodeGen::CL::name(inst_name), defined);
        if (defined) {
            int hex = FALSE;
            if (Inter::Annotations::find(&(inst_name->ann_set), HEX_IANN)) hex = TRUE;
            if (hex) WRITE("$%x", val2); else WRITE("%d", val2);
        }
        CodeGen::Targets::end_constant(gen, CodeGen::CL::name(inst_name));
    }
}

§7.

int CodeGen::IP::pnum(inter_symbol *prop_name) {
    int N = Inter::Symbols::read_annotation(prop_name, SOURCE_ORDER_IANN);
    if (N >= 0) return N;
    return 0;
}

int CodeGen::IP::compare_kind_symbols(const void *elem1, const void *elem2) {
    const inter_symbol **e1 = (const inter_symbol **) elem1;
    const inter_symbol **e2 = (const inter_symbol **) elem2;
    if ((*e1 == NULL) || (*e2 == NULL))
        internal_error("Disaster while sorting kinds");
    int s1 = CodeGen::IP::kind_sequence_number(*e1);
    int s2 = CodeGen::IP::kind_sequence_number(*e2);
    if (s1 != s2) return s1-s2;
    return Inter::Symbols::sort_number(*e1) - Inter::Symbols::sort_number(*e2);
}

int CodeGen::IP::compare_kind_symbols_decl(const void *elem1, const void *elem2) {
    const inter_symbol **e1 = (const inter_symbol **) elem1;
    const inter_symbol **e2 = (const inter_symbol **) elem2;
    if ((*e1 == NULL) || (*e2 == NULL))
        internal_error("Disaster while sorting kinds");
    int s1 = CodeGen::IP::kind_sequence_number_decl(*e1);
    int s2 = CodeGen::IP::kind_sequence_number_decl(*e2);
    if (s1 != s2) return s1-s2;
    return Inter::Symbols::sort_number(*e1) - Inter::Symbols::sort_number(*e2);
}

int CodeGen::IP::kind_sequence_number(const inter_symbol *kind_name) {
    int N = Inter::Symbols::read_annotation(kind_name, SOURCE_ORDER_IANN);
    if (N >= 0) return N;
    return 100000000;
}

int CodeGen::IP::kind_sequence_number_decl(const inter_symbol *kind_name) {
    int N = Inter::Symbols::read_annotation(kind_name, DECLARATION_ORDER_IANN);
    if (N >= 0) return N;
    return 100000000;
}

int CodeGen::IP::weak_id(inter_symbol *kind_name) {
    int N = Inter::Symbols::read_annotation(kind_name, WEAK_ID_IANN);
    if (N >= 0) return N;
    return 0;
}

§8. For the I6 header syntax, see the DM4. Note that the "hardwired" short name is intentionally made blank: we always use I6's short_name property instead. I7's spatial plugin, if loaded (as it usually is), will have annotated the Inter symbol for the object with an arrow count, that is, a measure of its spatial depth. This we translate into I6 arrow notation. If the spatial plugin wasn't loaded then we have no notion of containment, all arrow counts are 0, and we define a flat sequence of free-standing objects.

One last oddball thing is that direction objects have to be compiled in I6 as if they were spatially inside a special object called Compass. This doesn't really make much conceptual sense, and I7 dropped the idea — it has no "compass".

void CodeGen::IP::object_instance(code_generation *gen, inter_tree_node *P) {
    inter_symbol *inst_name = Inter::SymbolsTables::symbol_from_frame_data(P, DEFN_INST_IFLD);
    inter_symbol *inst_kind = Inter::SymbolsTables::symbol_from_frame_data(P, KIND_INST_IFLD);

    if (Inter::Kind::is_a(inst_kind, object_kind_symbol)) {
        text_stream *OUT = CodeGen::current(gen);
        WRITE("Object ");
        int c = Inter::Symbols::read_annotation(inst_name, ARROW_COUNT_IANN);
        for (int i=0; i<c; i++) WRITE("-> ");
        WRITE("%S \"\"", CodeGen::CL::name(inst_name));
        if (Inter::Kind::is_a(inst_kind, direction_kind_symbol)) { WRITE(" Compass"); }
        WRITE("\n    class %S\n", CodeGen::CL::name(inst_kind));
        CodeGen::IP::append(gen, inst_name);
        inter_node_list *FL =
            Inter::Node::ID_to_frame_list(P,
                Inter::Instance::properties_list(inst_name));
        CodeGen::IP::plist(gen, FL);
        WRITE(";\n\n");
    }
}

void CodeGen::IP::plist(code_generation *gen, inter_node_list *FL) {
    text_stream *OUT = CodeGen::current(gen);
    if (FL == NULL) internal_error("no properties list");
    inter_tree_node *X;
    LOOP_THROUGH_INTER_NODE_LIST(X, FL) {
        inter_symbol *prop_name = Inter::SymbolsTables::symbol_from_frame_data(X, PROP_PVAL_IFLD);
        if (prop_name == NULL) internal_error("no property");
        text_stream *call_it = CodeGen::CL::name(prop_name);
        if (Inter::Symbols::get_flag(prop_name, ATTRIBUTE_MARK_BIT)) {
            char *maybe = "";
            if ((X->W.data[DVAL1_PVAL_IFLD] == LITERAL_IVAL) &&
                (X->W.data[DVAL2_PVAL_IFLD] == 0)) maybe = "~";
            WRITE("    has %s%S\n", maybe, call_it);
        } else {
            WRITE("    with %S ", call_it);
            int done = FALSE;
            if (Inter::Symbols::is_stored_in_data(X->W.data[DVAL1_PVAL_IFLD], X->W.data[DVAL2_PVAL_IFLD])) {
                inter_symbol *S = Inter::SymbolsTables::symbol_from_data_pair_and_frame(X->W.data[DVAL1_PVAL_IFLD], X->W.data[DVAL2_PVAL_IFLD], X);
                if ((S) && (Inter::Symbols::read_annotation(S, INLINE_ARRAY_IANN) == 1)) {
                    inter_tree_node *P = Inter::Symbols::definition(S);
                    for (int i=DATA_CONST_IFLD; i<P->W.extent; i=i+2) {
                        if (i>DATA_CONST_IFLD) WRITE(" ");
                        CodeGen::CL::literal(gen, NULL, Inter::Packages::scope_of(P), P->W.data[i], P->W.data[i+1], FALSE);
                    }
                    done = TRUE;
                }
            }
            if (done == FALSE)
                CodeGen::CL::literal(gen, NULL, Inter::Packages::scope_of(X),
                    X->W.data[DVAL1_PVAL_IFLD], X->W.data[DVAL2_PVAL_IFLD], FALSE);
            WRITE("\n");
        }
    }
}

void CodeGen::IP::append(code_generation *gen, inter_symbol *symb) {
    text_stream *OUT = CodeGen::current(gen);
    inter_tree *I = gen->from;
    text_stream *S = Inter::Symbols::read_annotation_t(symb, I, APPEND_IANN);
    if (Str::len(S) == 0) return;
    WRITE("    ");
    int L = Str::len(S);
    for (int i=0; i<L; i++) {
        wchar_t c = Str::get_at(S, i);
        if (c == URL_SYMBOL_CHAR) {
            TEMPORARY_TEXT(T);
            for (i++; i<L; i++) {
                wchar_t c = Str::get_at(S, i);
                if (c == URL_SYMBOL_CHAR) break;
                PUT_TO(T, c);
            }
            inter_symbol *symb = Inter::SymbolsTables::url_name_to_symbol(I, NULL, T);
            WRITE("%S", CodeGen::CL::name(symb));
            DISCARD_TEXT(T);
        } else PUT(c);
        if ((c == '\n') && (i != Str::len(S)-1)) WRITE("    ");
    }
}

§9.

int CodeGen::IP::is_kind_of_object(inter_symbol *kind_name) {
    if (kind_name == object_kind_symbol) return FALSE;
    inter_data_type *idt = Inter::Kind::data_type(kind_name);
    if (idt == unchecked_idt) return FALSE;
    if (Inter::Kind::is_a(kind_name, object_kind_symbol)) return TRUE;
    return FALSE;
}

§10. Counting kinds of object, not very quickly:

inter_t CodeGen::IP::kind_of_object_count(inter_symbol *kind_name) {
    if ((kind_name == NULL) || (kind_name == object_kind_symbol)) return 0;
    int N = Inter::Symbols::read_annotation(kind_name, OBJECT_KIND_COUNTER_IANN);
    if (N >= 0) return (inter_t) N;
    return 0;
}