To generate I6 code from intermediate code.


§1. Target.

enum pragmatic_matter_I7CGS
enum compiler_versioning_matter_I7CGS
enum predeclarations_I7CGS
enum very_early_matter_I7CGS
enum constants_1_I7CGS
enum predeclarations_2_I7CGS
enum early_matter_I7CGS
enum text_literals_code_I7CGS
enum summations_at_eof_I7CGS
enum arrays_at_eof_I7CGS
enum globals_array_I7CGS
enum main_matter_I7CGS
enum routines_at_eof_I7CGS
enum code_at_eof_I7CGS
enum verbs_at_eof_I7CGS
enum stubs_at_eof_I7CGS
enum property_offset_creator_I7CGS
int I6_target_segments[] = {
    pragmatic_matter_I7CGS,
    compiler_versioning_matter_I7CGS,
    predeclarations_I7CGS,
    predeclarations_2_I7CGS,
    very_early_matter_I7CGS,
    constants_1_I7CGS,
    early_matter_I7CGS,
    text_literals_code_I7CGS,
    summations_at_eof_I7CGS,
    arrays_at_eof_I7CGS,
    globals_array_I7CGS,
    main_matter_I7CGS,
    routines_at_eof_I7CGS,
    code_at_eof_I7CGS,
    verbs_at_eof_I7CGS,
    stubs_at_eof_I7CGS,
    property_offset_creator_I7CGS,
    -1
};

§2.

code_generator *inform6_target = NULL;
void I6Target::create_generator(void) {
    code_generator *cgt = Generators::new(I"inform6");
    METHOD_ADD(cgt, BEGIN_GENERATION_MTID, I6Target::begin_generation);
    METHOD_ADD(cgt, INVOKE_PRIMITIVE_MTID, I6Target::invoke_primitive);
    METHOD_ADD(cgt, MANGLE_IDENTIFIER_MTID, I6Target::mangle);
    METHOD_ADD(cgt, COMPILE_DICTIONARY_WORD_MTID, I6Target::compile_dictionary_word);
    METHOD_ADD(cgt, COMPILE_LITERAL_NUMBER_MTID, I6Target::compile_literal_number);
    METHOD_ADD(cgt, COMPILE_LITERAL_REAL_MTID, I6Target::compile_literal_real);
    METHOD_ADD(cgt, COMPILE_LITERAL_TEXT_MTID, I6Target::compile_literal_text);
    METHOD_ADD(cgt, DECLARE_PROPERTY_MTID, I6Target::declare_property);
    METHOD_ADD(cgt, DECLARE_ATTRIBUTE_MTID, I6Target::declare_attribute);
    METHOD_ADD(cgt, PROPERTY_OFFSET_MTID, I6Target::property_offset);
    METHOD_ADD(cgt, DECLARE_VARIABLES_MTID, I6Target::declare_variables);
    METHOD_ADD(cgt, EVALUATE_VARIABLE_MTID, I6Target::evaluate_variable);
    METHOD_ADD(cgt, DECLARE_CLASS_MTID, I6Target::declare_class);
    METHOD_ADD(cgt, END_CLASS_MTID, I6Target::end_class);
    METHOD_ADD(cgt, DECLARE_VALUE_INSTANCE_MTID, I6Target::declare_value_instance);
    METHOD_ADD(cgt, DECLARE_INSTANCE_MTID, I6Target::declare_instance);
    METHOD_ADD(cgt, END_INSTANCE_MTID, I6Target::end_instance);
    METHOD_ADD(cgt, OPTIMISE_PROPERTY_MTID, I6Target::optimise_property_value);
    METHOD_ADD(cgt, ASSIGN_PROPERTY_MTID, I6Target::assign_property);
    METHOD_ADD(cgt, DECLARE_CONSTANT_MTID, I6Target::declare_constant);
    METHOD_ADD(cgt, DECLARE_FUNCTION_MTID, I6Target::declare_function);
    METHOD_ADD(cgt, PLACE_LABEL_MTID, I6Target::place_label);
    METHOD_ADD(cgt, EVALUATE_LABEL_MTID, I6Target::evaluate_label);
    METHOD_ADD(cgt, INVOKE_FUNCTION_MTID, I6Target::invoke_function);
    METHOD_ADD(cgt, INVOKE_OPCODE_MTID, I6Target::invoke_opcode);
    METHOD_ADD(cgt, BEGIN_ARRAY_MTID, I6Target::begin_array);
    METHOD_ADD(cgt, ARRAY_ENTRY_MTID, I6Target::array_entry);
    METHOD_ADD(cgt, COMPILE_LITERAL_SYMBOL_MTID, I6Target::compile_literal_symbol);
    METHOD_ADD(cgt, ARRAY_ENTRIES_MTID, I6Target::array_entries);
    METHOD_ADD(cgt, END_ARRAY_MTID, I6Target::end_array);
    METHOD_ADD(cgt, OFFER_PRAGMA_MTID, I6Target::offer_pragma)
    METHOD_ADD(cgt, END_GENERATION_MTID, I6Target::end_generation);
    METHOD_ADD(cgt, PSEUDO_OBJECT_MTID, I6Target::pseudo_object);
    METHOD_ADD(cgt, NEW_ACTION_MTID, I6Target::new_action);
    inform6_target = cgt;
}

§3. Segmentation. The loss of life is so appalling that I6 will not even compile a story file which doesn't define the property number life (well, strictly speaking, it checks the presence of constants suggesting the I6 library first, but the template layer does define constants like that). We define it as a null constant to be sure of avoiding any valid property number; I6 being typeless, that enables the veneer to compile again. (The relevant code is in CA__Pr, defined in the veneer.c section of I6.)

debug_flag is traditionally called so, but is actually now a bitmap of flags for tracing actions, calls to object routines, and so on. It's used in the I6 veneer, and need not exist on any other final compilation target.

define I6_GEN_DATA(x) ((I6_generation_data *) (gen->generator_private_data))->x
typedef struct I6_generation_data {
    int I6_property_offsets_made;
    CLASS_DEFINITION
} I6_generation_data;

int I6Target::begin_generation(code_generator *cgt, code_generation *gen) {
    I6_generation_data *data = CREATE(I6_generation_data);
    data->I6_property_offsets_made = 0;

    CodeGen::create_segments(gen, data, I6_target_segments);

    segmentation_pos saved = CodeGen::select(gen, compiler_versioning_matter_I7CGS);
    text_stream *OUT = CodeGen::current(gen);
    WRITE("Constant Grammar__Version 2;\n");
    WRITE("Global debug_flag;\n");
    CodeGen::deselect(gen, saved);

    saved = CodeGen::select(gen, routines_at_eof_I7CGS);
    OUT = CodeGen::current(gen);
    WRITE("#Ifdef TARGET_ZCODE;\n");
    WRITE("Global max_z_object;\n");
    WRITE("[ OC__Cl obj cla j a n objflag;\n"); INDENT;
    WRITE("@jl obj 1 ?NotObj;\n");
    WRITE("@jg obj max_z_object ?NotObj;\n");
    WRITE("@inc objflag;\n");
    WRITE("#Ifdef K1_room;\n");
    WRITE("@je cla K1_room ?~NotRoom;\n");
    WRITE("@test_attr obj mark_as_room ?rtrue;\n");
    WRITE("@rfalse;\n");
    WRITE(".NotRoom;\n");
    WRITE("#Endif;\n");
    WRITE("#Ifdef K2_thing;\n");
    WRITE("@je cla K2_thing ?~NotObj;\n");
    WRITE("@test_attr obj mark_as_thing ?rtrue;\n");
    WRITE("@rfalse;\n");
    WRITE("#Endif;\n");
    WRITE(".NotObj;\n");
    WRITE("\n");
    WRITE("@je cla Object Class ?ObjOrClass;\n");
    WRITE("@je cla Routine String ?RoutOrStr;\n");
    WRITE("\n");
    WRITE("@jin cla 1 ?~Mistake;\n");
    WRITE("\n");
    WRITE("@jz objflag ?rfalse;\n");
    WRITE("@get_prop_addr obj 2 -> a;\n");
    WRITE("@jz a ?rfalse;\n");
    WRITE("@get_prop_len a -> n;\n");
    WRITE("\n");
    WRITE("@div n 2 -> n;\n");
    WRITE(".Loop;\n");
    WRITE("@loadw a j -> sp;\n");
    WRITE("@je sp cla ?rtrue;\n");
    WRITE("@inc j;\n");
    WRITE("@jl j n ?Loop;\n");
    WRITE("@rfalse;\n");
    WRITE("\n");
    WRITE(".ObjOrClass;\n");
    WRITE("@jz objflag ?rfalse;\n");
    WRITE("@je cla Object ?JustObj;\n");
    WRITE("\n");
    WRITE("! So now cla is Class\n");
    WRITE("@jg obj String ?~rtrue;\n");
    WRITE("@jin obj Class ?rtrue;\n");
    WRITE("@rfalse;\n");
    WRITE("\n");
    WRITE(".JustObj;\n");
    WRITE("! So now cla is Object\n");
    WRITE("@jg obj String ?~rfalse;\n");
    WRITE("@jin obj Class ?rfalse;\n");
    WRITE("@rtrue;\n");
    WRITE("\n");
    WRITE(".RoutOrStr;\n");
    WRITE("@jz objflag ?~rfalse;\n");
    WRITE("@call_2s Z__Region obj -> sp;\n");
    WRITE("@inc sp;\n");
    WRITE("@je sp cla ?rtrue;\n");
    WRITE("@rfalse;\n");
    WRITE("\n");
    WRITE(".Mistake;\n");
    WRITE("RT__Err(\"apply 'ofclass' for\", cla, -1);\n");
    WRITE("rfalse;\n");
    OUTDENT; WRITE("];\n");
    WRITE("#Endif;\n");
    CodeGen::deselect(gen, saved);

    return FALSE;
}

int I6_DebugAttribute_seen = FALSE;
int I6Target::end_generation(code_generator *cgt, code_generation *gen) {
    if (I6_GEN_DATA(I6_property_offsets_made) > 0) {
        segmentation_pos saved = CodeGen::select(gen, property_offset_creator_I7CGS);
        text_stream *OUT = CodeGen::current(gen);
        OUTDENT;
        WRITE("];\n");
        CodeGen::deselect(gen, saved);
    }

    if (I6_DebugAttribute_seen == FALSE) {
        segmentation_pos saved = CodeGen::select(gen, routines_at_eof_I7CGS);
        text_stream *OUT = CodeGen::current(gen);
        WRITE("[ DebugAttribute a anames str;\n");
        WRITE("    print \"<attribute \", a, \">\";\n");
        WRITE("];\n");
        CodeGen::deselect(gen, saved);
    }

    return FALSE;
}

void I6Target::offer_pragma(code_generator *cgt, code_generation *gen,
    inter_tree_node *P, text_stream *tag, text_stream *content) {
    if (Str::eq(tag, I"Inform6")) {
        segmentation_pos saved = CodeGen::select(gen, pragmatic_matter_I7CGS);
        text_stream *OUT = CodeGen::current(gen);
        WRITE("!%% %S\n", content);
        CodeGen::deselect(gen, saved);
    }
}

void I6Target::mangle(code_generator *cgt, OUTPUT_STREAM, text_stream *identifier) {
    WRITE("%S", identifier);
}

void I6Target::invoke_primitive(code_generator *cgt, code_generation *gen,
    inter_symbol *prim_name, inter_tree_node *P, int void_context) {
    text_stream *OUT = CodeGen::current(gen);
    int suppress_terminal_semicolon = FALSE;
    inter_tree *I = gen->from;
    inter_ti bip = Primitives::to_bip(I, prim_name);
    switch (bip) {
        case PLUS_BIP:          WRITE("("); VNODE_1C; WRITE(" + "); VNODE_2C; WRITE(")"); break;
        case MINUS_BIP:         WRITE("("); VNODE_1C; WRITE(" - "); VNODE_2C; WRITE(")"); break;
        case UNARYMINUS_BIP:    WRITE("(-("); VNODE_1C; WRITE("))"); break;
        case TIMES_BIP:         WRITE("("); VNODE_1C; WRITE("*"); VNODE_2C; WRITE(")"); break;
        case DIVIDE_BIP:        WRITE("("); VNODE_1C; WRITE("/"); VNODE_2C; WRITE(")"); break;
        case MODULO_BIP:        WRITE("("); VNODE_1C; WRITE("%%"); VNODE_2C; WRITE(")"); break;
        case BITWISEAND_BIP:    WRITE("(("); VNODE_1C; WRITE(")&("); VNODE_2C; WRITE("))"); break;
        case BITWISEOR_BIP:     WRITE("(("); VNODE_1C; WRITE(")|("); VNODE_2C; WRITE("))"); break;
        case BITWISENOT_BIP:    WRITE("(~("); VNODE_1C; WRITE("))"); break;

        case NOT_BIP:           WRITE("(~~("); VNODE_1C; WRITE("))"); break;
        case AND_BIP:           WRITE("(("); VNODE_1C; WRITE(") && ("); VNODE_2C; WRITE("))"); break;
        case OR_BIP:            WRITE("(("); VNODE_1C; WRITE(") || ("); VNODE_2C; WRITE("))"); break;
        case EQ_BIP:            WRITE("("); VNODE_1C; WRITE(" == "); VNODE_2C; WRITE(")"); break;
        case NE_BIP:            WRITE("("); VNODE_1C; WRITE(" ~= "); VNODE_2C; WRITE(")"); break;
        case GT_BIP:            WRITE("("); VNODE_1C; WRITE(" > "); VNODE_2C; WRITE(")"); break;
        case GE_BIP:            WRITE("("); VNODE_1C; WRITE(" >= "); VNODE_2C; WRITE(")"); break;
        case LT_BIP:            WRITE("("); VNODE_1C; WRITE(" < "); VNODE_2C; WRITE(")"); break;
        case LE_BIP:            WRITE("("); VNODE_1C; WRITE(" <= "); VNODE_2C; WRITE(")"); break;
        case OFCLASS_BIP:       WRITE("("); VNODE_1C; WRITE(" ofclass "); VNODE_2C; WRITE(")"); break;
        case HAS_BIP:           WRITE("("); VNODE_1C; WRITE(" has "); VNODE_2C; WRITE(")"); break;
        case HASNT_BIP:         WRITE("("); VNODE_1C; WRITE(" hasnt "); VNODE_2C; WRITE(")"); break;
        case IN_BIP:            WRITE("("); VNODE_1C; WRITE(" in "); VNODE_2C; WRITE(")"); break;
        case NOTIN_BIP:         WRITE("("); VNODE_1C; WRITE(" notin "); VNODE_2C; WRITE(")"); break;
        case PROVIDES_BIP:      WRITE("("); VNODE_1C; WRITE(" provides "); VNODE_2C; WRITE(")"); break;
        case ALTERNATIVE_BIP:   VNODE_1C; WRITE(" or "); VNODE_2C; break;

        case PUSH_BIP:          WRITE("@push "); VNODE_1C; break;
        case PULL_BIP:          WRITE("@pull "); VNODE_1C; break;
        case PREINCREMENT_BIP:  WRITE("++("); VNODE_1C; WRITE(")"); break;
        case POSTINCREMENT_BIP: WRITE("("); VNODE_1C; WRITE(")++"); break;
        case PREDECREMENT_BIP:  WRITE("--("); VNODE_1C; WRITE(")"); break;
        case POSTDECREMENT_BIP: WRITE("("); VNODE_1C; WRITE(")--"); break;
        case STORE_BIP:         WRITE("("); VNODE_1C; WRITE(" = "); VNODE_2C; WRITE(")"); break;
        case SETBIT_BIP:        VNODE_1C; WRITE(" = "); VNODE_1C; WRITE(" | "); VNODE_2C; break;
        case CLEARBIT_BIP:      VNODE_1C; WRITE(" = "); VNODE_1C; WRITE(" &~ ("); VNODE_2C; WRITE(")"); break;
        case LOOKUP_BIP:        WRITE("("); VNODE_1C; WRITE("-->("); VNODE_2C; WRITE("))"); break;
        case LOOKUPBYTE_BIP:    WRITE("("); VNODE_1C; WRITE("->("); VNODE_2C; WRITE("))"); break;
        case PROPERTYADDRESS_BIP: WRITE("("); VNODE_1C; WRITE(".& "); VNODE_2C; WRITE(")"); break;
        case PROPERTYLENGTH_BIP: WRITE("("); VNODE_1C; WRITE(".# "); VNODE_2C; WRITE(")"); break;
        case PROPERTYVALUE_BIP: WRITE("("); VNODE_1C; WRITE("."); VNODE_2C; WRITE(")"); break;

        case BREAK_BIP:         WRITE("break"); break;
        case CONTINUE_BIP:      WRITE("continue"); break;
        case RETURN_BIP:        Generate primitive for return3.1; break;
        case JUMP_BIP:          WRITE("jump "); VNODE_1C; break;
        case QUIT_BIP:          WRITE("quit"); break;
        case RESTORE_BIP:       WRITE("restore "); VNODE_1C; break;

        case INDIRECT0_BIP: case INDIRECT0V_BIP: case CALLMESSAGE0_BIP:
                                WRITE("("); VNODE_1C; WRITE(")()"); break;
        case INDIRECT1_BIP: case INDIRECT1V_BIP: case CALLMESSAGE1_BIP:
                                WRITE("("); VNODE_1C; WRITE(")(");
                                VNODE_2C; WRITE(")"); break;
        case INDIRECT2_BIP: case INDIRECT2V_BIP: case CALLMESSAGE2_BIP:
                                WRITE("("); VNODE_1C; WRITE(")(");
                                VNODE_2C; WRITE(","); VNODE_3C; WRITE(")"); break;
        case INDIRECT3_BIP: case INDIRECT3V_BIP: case CALLMESSAGE3_BIP:
                                WRITE("("); VNODE_1C; WRITE(")(");
                                VNODE_2C; WRITE(","); VNODE_3C; WRITE(","); VNODE_4C; WRITE(")"); break;
        case INDIRECT4_BIP: case INDIRECT4V_BIP:
                                WRITE("("); VNODE_1C; WRITE(")(");
                                VNODE_2C; WRITE(","); VNODE_3C; WRITE(","); VNODE_4C; WRITE(",");
                                VNODE_5C; WRITE(")"); break;
        case INDIRECT5_BIP: case INDIRECT5V_BIP:
                                WRITE("("); VNODE_1C; WRITE(")(");
                                VNODE_2C; WRITE(","); VNODE_3C; WRITE(","); VNODE_4C; WRITE(",");
                                VNODE_5C; WRITE(","); VNODE_6C; WRITE(")"); break;
        case MESSAGE0_BIP:      WRITE("("); VNODE_1C; WRITE("."); VNODE_2C; WRITE("())"); break;
        case MESSAGE1_BIP:      WRITE("("); VNODE_1C; WRITE("."); VNODE_2C; WRITE("(");
                                VNODE_3C; WRITE("))"); break;
        case MESSAGE2_BIP:      WRITE("("); VNODE_1C; WRITE("."); VNODE_2C; WRITE("(");
                                VNODE_3C; WRITE(","); VNODE_4C; WRITE("))"); break;
        case MESSAGE3_BIP:      WRITE("("); VNODE_1C; WRITE("."); VNODE_2C; WRITE("(");
                                VNODE_3C; WRITE(","); VNODE_4C; WRITE(","); VNODE_5C; WRITE("))"); break;

        case EXTERNALCALL_BIP:  internal_error("external calls impossible in Inform 6"); break;

        case SPACES_BIP:        WRITE("spaces "); VNODE_1C; break;
        case FONT_BIP:
            WRITE("if ("); VNODE_1C; WRITE(") { font on; } else { font off; }");
            suppress_terminal_semicolon = TRUE;
            break;
        case STYLE_BIP: {
            inter_tree_node *N = InterTree::first_child(P);
            if ((N->W.data[ID_IFLD] == CONSTANT_IST) &&
                (N->W.data[FORMAT_CONST_IFLD] == CONSTANT_DIRECT)) {
                inter_ti val2 = N->W.data[DATA_CONST_IFLD + 1];
                switch (val2) {
                    case 1: WRITE("style bold"); break;
                    case 2: WRITE("style underline"); break;
                    case 3: WRITE("style reverse"); break;
                    default: WRITE("style roman");
                }
            } else {
                WRITE("style roman");
            }
            break;
        }

        case MOVE_BIP: WRITE("move "); VNODE_1C; WRITE(" to "); VNODE_2C; break;
        case REMOVE_BIP: WRITE("remove "); VNODE_1C; break;
        case GIVE_BIP: WRITE("give "); VNODE_1C; WRITE(" "); VNODE_2C; break;
        case TAKE_BIP: WRITE("give "); VNODE_1C; WRITE(" ~"); VNODE_2C; break;

        case ALTERNATIVECASE_BIP: VNODE_1C; WRITE(", "); VNODE_2C; break;
        case SEQUENTIAL_BIP: WRITE("("); VNODE_1C; WRITE(","); VNODE_2C; WRITE(")"); break;
        case TERNARYSEQUENTIAL_BIP: Generate primitive for ternarysequential3.2; break;

        case PRINT_BIP: WRITE("print "); CodeGen::lt_mode(gen, PRINTING_LTM); VNODE_1C; CodeGen::lt_mode(gen, REGULAR_LTM); break;
        case PRINTCHAR_BIP: WRITE("print (char) "); VNODE_1C; break;
        case PRINTNL_BIP: WRITE("new_line"); break;
        case PRINTOBJ_BIP: WRITE("print (object) "); VNODE_1C; break;
        case PRINTNUMBER_BIP: WRITE("print "); VNODE_1C; break;
        case PRINTDWORD_BIP: WRITE("print (address) "); VNODE_1C; break;
        case PRINTSTRING_BIP: WRITE("print (string) "); VNODE_1C; break;
        case BOX_BIP: WRITE("box "); CodeGen::lt_mode(gen, BOX_LTM); VNODE_1C; CodeGen::lt_mode(gen, REGULAR_LTM); break;

        case IF_BIP: Generate primitive for if3.3; break;
        case IFDEBUG_BIP: Generate primitive for ifdebug3.4; break;
        case IFSTRICT_BIP: Generate primitive for ifstrict3.5; break;
        case IFELSE_BIP: Generate primitive for ifelse3.6; break;
        case WHILE_BIP: Generate primitive for while3.7; break;
        case DO_BIP: Generate primitive for do3.8; break;
        case FOR_BIP: Generate primitive for for3.9; break;
        case OBJECTLOOP_BIP: Generate primitive for objectloop3.10; break;
        case OBJECTLOOPX_BIP: Generate primitive for objectloopx3.11; break;
        case LOOP_BIP: Generate primitive for loop3.12; break;
        case SWITCH_BIP: Generate primitive for switch3.13; break;
        case CASE_BIP: Generate primitive for case3.14; break;
        case DEFAULT_BIP: Generate primitive for default3.15; break;

        case RANDOM_BIP: WRITE("random("); VNODE_1C; WRITE(")"); break;

        case READ_BIP: WRITE("read "); VNODE_1C; WRITE(" "); VNODE_2C; break;

        default: LOG("Prim: %S\n", prim_name->symbol_name); internal_error("unimplemented prim");
    }
    if ((void_context) && (suppress_terminal_semicolon == FALSE)) WRITE(";\n");
}

§3.1. Generate primitive for return3.1 =

    int rboolean = NOT_APPLICABLE;
    inter_tree_node *V = InterTree::first_child(P);
    if (V->W.data[ID_IFLD] == VAL_IST) {
        inter_ti val1 = V->W.data[VAL1_VAL_IFLD];
        inter_ti val2 = V->W.data[VAL2_VAL_IFLD];
        if (val1 == LITERAL_IVAL) {
            if (val2 == 0) rboolean = FALSE;
            if (val2 == 1) rboolean = TRUE;
        }
    }
    switch (rboolean) {
        case FALSE: WRITE("rfalse"); break;
        case TRUE: WRITE("rtrue"); break;
        case NOT_APPLICABLE: WRITE("return "); Vanilla::node(gen, V); break;
    }

§3.2. Here we need some gymnastics. We need to produce a value which the sometimes shaky I6 expression parser will accept, which turns out to be quite a constraint. If we were compiling to C, we might try this:

    (a, b, c)

using the serial comma operator — that is, where the expression (a, b) evaluates a then b and returns the value of b, discarding a. Now I6 does support the comma operator, and this makes a workable scheme, right up to the point where some of the token values themselves include invocations of functions, because I6's syntax analyser won't always allow the serial comma to be mixed in the same expression with the function argument comma, i.e., I6 is unable properly to handle expressions like this one:

    (a(b, c), d)

where the first comma constructs a list and the second is the operator. (Many such expressions work fine, but not all.) That being so, the scheme I actually use is:

    (c) + 0*((b) + (a))

Because I6 evaluates the leaves in an expression tree right-to-left, not left-to-right, the parameter assignments happen first, then the conditions, then the result.

Generate primitive for ternarysequential3.2 =

    WRITE("(\n"); INDENT;
    WRITE("! This value evaluates third (i.e., last)\n"); VNODE_3C;
    OUTDENT; WRITE("+\n"); INDENT;
    WRITE("0*(\n"); INDENT;
    WRITE("! The following condition evaluates second\n");
    WRITE("((\n"); INDENT; VNODE_2C;
    OUTDENT; WRITE("\n))\n");
    OUTDENT; WRITE("+\n"); INDENT;
    WRITE("! The following assignments evaluate first\n");
    WRITE("("); VNODE_1C; WRITE(")");
    OUTDENT; WRITE(")\n");
    OUTDENT; WRITE(")\n");

§3.3. Generate primitive for if3.3 =

    WRITE("if ("); VNODE_1C; WRITE(") {\n"); INDENT; VNODE_2C;
    OUTDENT; WRITE("}\n");
    suppress_terminal_semicolon = TRUE;

§3.4. Generate primitive for ifdebug3.4 =

    WRITE("#ifdef DEBUG;\n"); INDENT; VNODE_1C; OUTDENT; WRITE("#endif;\n");
    suppress_terminal_semicolon = TRUE;

§3.5. Generate primitive for ifstrict3.5 =

    WRITE("#ifdef STRICT_MODE;\n"); INDENT; VNODE_1C; OUTDENT; WRITE("#endif;\n");
    suppress_terminal_semicolon = TRUE;

§3.6. Generate primitive for ifelse3.6 =

    WRITE("if ("); VNODE_1C; WRITE(") {\n"); INDENT; VNODE_2C; OUTDENT;
    WRITE("} else {\n"); INDENT; VNODE_3C; OUTDENT; WRITE("}\n");
    suppress_terminal_semicolon = TRUE;

§3.7. Generate primitive for while3.7 =

    WRITE("while ("); VNODE_1C; WRITE(") {\n"); INDENT; VNODE_2C; OUTDENT; WRITE("}\n");
    suppress_terminal_semicolon = TRUE;

§3.8. Generate primitive for do3.8 =

    WRITE("do {"); VNODE_2C; WRITE("} until (\n"); INDENT; VNODE_1C; OUTDENT; WRITE(")\n");

§3.9. Generate primitive for for3.9 =

    WRITE("for (");
    inter_tree_node *INIT = InterTree::first_child(P);
    if (!((INIT->W.data[ID_IFLD] == VAL_IST) && (INIT->W.data[VAL1_VAL_IFLD] == LITERAL_IVAL) && (INIT->W.data[VAL2_VAL_IFLD] == 1))) VNODE_1C;
    WRITE(":"); VNODE_2C;
    WRITE(":");
    inter_tree_node *U = InterTree::third_child(P);
    if (U->W.data[ID_IFLD] != VAL_IST)
    Vanilla::node(gen, U);
    WRITE(") {\n"); INDENT; VNODE_4C;
    OUTDENT; WRITE("}\n");
    suppress_terminal_semicolon = TRUE;

§3.10. Generate primitive for objectloop3.10 =

    int in_flag = FALSE;
    inter_tree_node *U = InterTree::third_child(P);
    if ((U->W.data[ID_IFLD] == INV_IST) && (U->W.data[METHOD_INV_IFLD] == INVOKED_PRIMITIVE)) {
        inter_symbol *prim = Inter::Inv::invokee(U);
        if ((prim) && (Primitives::to_bip(I, prim) == IN_BIP)) in_flag = TRUE;
    }

    WRITE("objectloop ");
    if (in_flag == FALSE) {
        WRITE("("); VNODE_1C; WRITE(" ofclass "); VNODE_2C;
        WRITE(" && ");
    } VNODE_3C;
    if (in_flag == FALSE) {
        WRITE(")");
    }
    WRITE(" {\n"); INDENT; VNODE_4C;
    OUTDENT; WRITE("}\n");
    suppress_terminal_semicolon = TRUE;

§3.11. Generate primitive for objectloopx3.11 =

    WRITE("objectloop ("); VNODE_1C; WRITE(" ofclass "); VNODE_2C;
    WRITE(") {\n"); INDENT; VNODE_3C; OUTDENT; WRITE("}\n");
    suppress_terminal_semicolon = TRUE;

§3.12. Generate primitive for loop3.12 =

    WRITE("{\n"); INDENT; VNODE_1C; OUTDENT; WRITE("}\n");
    suppress_terminal_semicolon = TRUE;

§3.13. Generate primitive for switch3.13 =

    WRITE("switch ("); VNODE_1C;
    WRITE(") {\n"); INDENT; VNODE_2C; OUTDENT; WRITE("}\n");
    suppress_terminal_semicolon = TRUE;

§3.14. Generate primitive for case3.14 =

    VNODE_1C; WRITE(":\n"); INDENT; VNODE_2C; WRITE(";\n"); OUTDENT;
    suppress_terminal_semicolon = TRUE;

§3.15. Generate primitive for default3.15 =

    WRITE("default:\n"); INDENT; VNODE_1C; WRITE(";\n"); OUTDENT;
    suppress_terminal_semicolon = TRUE;

§4.

void I6Target::compile_dictionary_word(code_generator *cgt, code_generation *gen,
    text_stream *S, int pluralise) {
    text_stream *OUT = CodeGen::current(gen);
    int n = 0;
    WRITE("'");
    LOOP_THROUGH_TEXT(pos, S) {
        wchar_t c = Str::get(pos);
        switch(c) {
            case '/': if (Str::len(S) == 1) WRITE("@{2F}"); else WRITE("/"); break;
            case '\'': WRITE("^"); break;
            case '^': WRITE("@{5E}"); break;
            case '~': WRITE("@{7E}"); break;
            case '@': WRITE("@{40}"); break;
            default: PUT(c);
        }
        if (n++ > 32) break;
    }
    if (pluralise) WRITE("//p");
    else if (Str::len(S) == 1) WRITE("//");
    WRITE("'");
}

§5.

void I6Target::compile_literal_number(code_generator *cgt,
    code_generation *gen, inter_ti val, int hex_mode) {
    text_stream *OUT = CodeGen::current(gen);
    if (hex_mode) WRITE("$%x", val);
    else WRITE("%d", val);
}

void I6Target::compile_literal_real(code_generator *cgt,
    code_generation *gen, text_stream *textual) {
    text_stream *OUT = CodeGen::current(gen);
    WRITE("$%S", textual);
}

§6.

void I6Target::compile_literal_text(code_generator *cgt, code_generation *gen,
    text_stream *S, int escape_mode) {
    text_stream *OUT = CodeGen::current(gen);
    WRITE("\"");
    if (escape_mode == FALSE) {
        WRITE("%S", S);
    } else {
        int esc_char = FALSE;
        LOOP_THROUGH_TEXT(pos, S) {
            wchar_t c = Str::get(pos);
            if (gen->literal_text_mode == BOX_LTM) {
                switch(c) {
                    case '@': WRITE("@{40}"); break;
                    case '"': WRITE("~"); break;
                    case '^': WRITE("@{5E}"); break;
                    case '~': WRITE("@{7E}"); break;
                    case '\\': WRITE("@{5C}"); break;
                    case '\t': WRITE(" "); break;
                    case '\n': WRITE("\"\n\""); break;
                    case NEWLINE_IN_STRING: WRITE("\"\n\""); break;
                    default: PUT(c);
                }
            } else {
                switch(c) {
                    case '@':
                        if (gen->literal_text_mode == PRINTING_LTM) {
                            WRITE("@@64"); esc_char = TRUE; continue;
                        }
                        WRITE("@{40}"); break;
                    case '"': WRITE("~"); break;
                    case '^':
                        if (gen->literal_text_mode == PRINTING_LTM) {
                            WRITE("@@94"); esc_char = TRUE; continue;
                        }
                        WRITE("@{5E}"); break;
                    case '~':
                        if (gen->literal_text_mode == PRINTING_LTM) {
                            WRITE("@@126"); esc_char = TRUE; continue;
                        }
                        WRITE("@{7E}"); break;
                    case '\\': WRITE("@{5C}"); break;
                    case '\t': WRITE(" "); break;
                    case '\n': WRITE("^"); break;
                    case NEWLINE_IN_STRING: WRITE("^"); break;
                    default: {
                        if (esc_char) WRITE("@{%02x}", c);
                        else PUT(c);
                    }
                }
                esc_char = FALSE;
            }
        }
    }
    WRITE("\"");
}

§7. Because in I6 source code some properties aren't declared before use, it follows that if not used by any object then they won't ever be created. This is a problem since it means that I6 code can't refer to them, because it would need to mention an I6 symbol which doesn't exist. To get around this, we create the property names which don't exist as constant symbols with the harmless value 0; we do this right at the end of the compiled I6 code. (This is a standard I6 trick called "stubbing", these being "stub definitions".)

void I6Target::declare_property(code_generator *cgt, code_generation *gen,
    inter_symbol *prop_name, int used) {
    text_stream *name = Inter::Symbols::name(prop_name);
    if (used) {
        segmentation_pos saved = CodeGen::select(gen, predeclarations_I7CGS);
        WRITE_TO(CodeGen::current(gen), "Property %S;\n", prop_name->symbol_name);
        CodeGen::deselect(gen, saved);
    } else {
        segmentation_pos saved = CodeGen::select(gen, code_at_eof_I7CGS);
        WRITE_TO(CodeGen::current(gen), "#ifndef %S; Constant %S = 0; #endif;\n", name, name);
        CodeGen::deselect(gen, saved);
    }
}

void I6Target::declare_attribute(code_generator *cgt, code_generation *gen, text_stream *prop_name) {
    segmentation_pos saved = CodeGen::select(gen, constants_1_I7CGS);
    WRITE_TO(CodeGen::current(gen), "Attribute %S;\n", prop_name);
    CodeGen::deselect(gen, saved);
}

void I6Target::property_offset(code_generator *cgt, code_generation *gen, text_stream *prop, int pos, int as_attr) {
    segmentation_pos saved = CodeGen::select(gen, property_offset_creator_I7CGS);
    text_stream *OUT = CodeGen::current(gen);
    if (I6_GEN_DATA(I6_property_offsets_made)++ == 0) {
        WRITE("[ CreatePropertyOffsets i;\n"); INDENT;
        WRITE("for (i=0: i<attributed_property_offsets_SIZE: i++)\n"); INDENT;
        WRITE("attributed_property_offsets-->i = -1;\n"); OUTDENT;
        WRITE("for (i=0: i<valued_property_offsets_SIZE: i++)\n"); INDENT;
        WRITE("valued_property_offsets-->i = -1;\n"); OUTDENT;
    }
    if (as_attr) WRITE("attributed_property_offsets");
    else WRITE("valued_property_offsets");
    WRITE("-->%S = %d;\n", prop, pos);
    CodeGen::deselect(gen, saved);
}

§8.

void I6Target::declare_variables(code_generator *cgt, code_generation *gen,
    linked_list *L) {
    int k = 0;
    inter_symbol *var_name;
    LOOP_OVER_LINKED_LIST(var_name, inter_symbol, L) {
        inter_tree_node *P = var_name->definition;
        if (Inter::Symbols::read_annotation(var_name, ASSIMILATED_IANN) != 1) {
            text_stream *S = Str::new();
            WRITE_TO(S, "(Global_Vars-->%d)", k);
            Inter::Symbols::set_translate(var_name, S);
            segmentation_pos saved = CodeGen::select(gen, predeclarations_I7CGS);
            text_stream *OUT = CodeGen::current(gen);
            if (k == 0) WRITE("Array Global_Vars -->\n");
            WRITE("  (");
            CodeGen::pair(gen, P, P->W.data[VAL1_VAR_IFLD], P->W.data[VAL2_VAR_IFLD]);
            WRITE(") ! -->%d = %S (%S)\n", k, Inter::Symbols::name(var_name), var_name->symbol_name);
            CodeGen::deselect(gen, saved);
            k++;
        } else {
            segmentation_pos saved = CodeGen::select(gen, main_matter_I7CGS);
            text_stream *OUT = CodeGen::current(gen);
            WRITE("Global %S = ", Inter::Symbols::name(var_name));
            CodeGen::pair(gen, P, P->W.data[VAL1_VAR_IFLD], P->W.data[VAL2_VAR_IFLD]);
            WRITE(";\n");
            CodeGen::deselect(gen, saved);
        }
    }

    if (k > 0) {
        segmentation_pos saved = CodeGen::select(gen, predeclarations_I7CGS);
        text_stream *OUT = CodeGen::current(gen);
        while (k++ < 2) WRITE(" NULL");
        WRITE(";\n");
        CodeGen::deselect(gen, saved);
    }
}

void I6Target::evaluate_variable(code_generator *cgt, code_generation *gen, inter_symbol *var_name, int as_reference) {
    text_stream *OUT = CodeGen::current(gen);
    WRITE("%S", Inter::Symbols::name(var_name));
}

void I6Target::declare_class(code_generator *cgt, code_generation *gen, text_stream *class_name, text_stream *printed_name, text_stream *super_class,
    segmentation_pos *saved) {
    *saved = CodeGen::select(gen, main_matter_I7CGS);
    text_stream *OUT = CodeGen::current(gen);
    WRITE("Class %S\n", class_name);
    if (Str::len(super_class) > 0) WRITE("  class %S\n", super_class);
}

void I6Target::end_class(code_generator *cgt, code_generation *gen, text_stream *class_name, segmentation_pos saved) {
    text_stream *OUT = CodeGen::current(gen);
    WRITE(";\n");
    CodeGen::deselect(gen, saved);
}

void I6Target::declare_value_instance(code_generator *cgt,
    code_generation *gen, text_stream *instance_name, text_stream *printed_name, text_stream *val) {
    Generators::declare_constant(gen, instance_name, NULL, RAW_GDCFORM, NULL, val, FALSE);
}

void I6Target::declare_instance(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, main_matter_I7CGS);
    text_stream *OUT = CodeGen::current(gen);
    WRITE("%S", class_name);
    for (int i=0; i<acount; i++) WRITE(" ->");
    WRITE(" %S", instance_name);
    if (is_dir) WRITE(" Compass");
}

void I6Target::end_instance(code_generator *cgt, code_generation *gen, text_stream *class_name, text_stream *instance_name, segmentation_pos saved) {
    text_stream *OUT = CodeGen::current(gen);
    WRITE(";\n");
    CodeGen::deselect(gen, saved);
}

int I6Target::optimise_property_value(code_generator *cgt, code_generation *gen, inter_symbol *prop_name, inter_tree_node *X) {
    if (Inter::Symbols::is_stored_in_data(X->W.data[DVAL1_PVAL_IFLD], X->W.data[DVAL2_PVAL_IFLD])) {
        inter_symbol *S = InterSymbolsTables::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);
            text_stream *OUT = CodeGen::current(gen);
            for (int i=DATA_CONST_IFLD; i<P->W.extent; i=i+2) {
                if (i>DATA_CONST_IFLD) WRITE(" ");
                CodeGen::pair(gen, P, P->W.data[i], P->W.data[i+1]);
            }
            return TRUE;
        }
    }
    return FALSE;
}

void I6Target::assign_property(code_generator *cgt, code_generation *gen, text_stream *property_name, text_stream *val, int as_att) {
    text_stream *OUT = CodeGen::current(gen);
    if (as_att) {
        if (Str::eq(val, I"0")) WRITE("    has ~%S\n", property_name);
        else WRITE("    has %S\n", property_name);
    } else {
        WRITE("    with %S %S\n", property_name, val);
    }
}

void I6Target::seek_locals(code_generation *gen, inter_tree_node *P) {
    if (P->W.data[ID_IFLD] == LOCAL_IST) {
        inter_package *pack = Inter::Packages::container(P);
        inter_symbol *var_name =
            InterSymbolsTables::local_symbol_from_id(pack, P->W.data[DEFN_LOCAL_IFLD]);
        text_stream *OUT = CodeGen::current(gen);
        WRITE(" %S", var_name->symbol_name);
    }
    LOOP_THROUGH_INTER_CHILDREN(F, P) I6Target::seek_locals(gen, F);
}

void I6Target::declare_constant(code_generator *cgt, code_generation *gen, text_stream *const_name, inter_symbol *const_s, int form, inter_tree_node *P, text_stream *val, int ifndef_me) {
    if ((const_s) && (Inter::Symbols::read_annotation(const_s, INLINE_ARRAY_IANN) == 1)) return;

    if (Str::eq(const_name, I"FLOAT_INFINITY")) return;
    if (Str::eq(const_name, I"FLOAT_NINFINITY")) return;
    if (Str::eq(const_name, I"FLOAT_NAN")) return;
    if (Str::eq(const_name, I"nothing")) return;
    if (Str::eq(const_name, I"#dict_par1")) return;
    if (Str::eq(const_name, I"#dict_par2")) return;

    int depth = 1;
    if (const_s) depth = Inter::Constant::constant_depth(const_s);
    segmentation_pos saved = CodeGen::select_layered(gen, constants_1_I7CGS, depth);
    text_stream *OUT = CodeGen::current(gen);

    if (Str::eq(const_name, I"Release")) {
        inter_ti val1 = P->W.data[DATA_CONST_IFLD];
        inter_ti val2 = P->W.data[DATA_CONST_IFLD + 1];
        WRITE("Release ");
        CodeGen::pair(gen, P, val1, val2);
        WRITE(";\n");
        return;
    }

    if (Str::eq(const_name, I"Story")) {
        inter_ti val1 = P->W.data[DATA_CONST_IFLD];
        inter_ti val2 = P->W.data[DATA_CONST_IFLD + 1];
        WRITE("Global Story = ");
        CodeGen::pair(gen, P, val1, val2);
        WRITE(";\n");
        return;
    }

    if (Str::eq(const_name, I"Serial")) {
        inter_ti val1 = P->W.data[DATA_CONST_IFLD];
        inter_ti val2 = P->W.data[DATA_CONST_IFLD + 1];
        WRITE("Serial ");
        CodeGen::pair(gen, P, val1, val2);
        WRITE(";\n");
        return;
    }

    if (ifndef_me) WRITE("#ifndef %S;\n", const_name);
    WRITE("Constant %S = ", const_name);
    VanillaConstants::definition_value(gen, form, P, const_s, val);
    WRITE(";\n");
    if (ifndef_me) WRITE("#endif;\n");
    CodeGen::deselect(gen, saved);
}

int this_is_I6_Main = 0;
void I6Target::declare_function(code_generator *cgt, code_generation *gen, inter_symbol *fn, inter_tree_node *D) {
    segmentation_pos saved = CodeGen::select(gen, routines_at_eof_I7CGS);
    text_stream *fn_name = Inter::Symbols::name(fn);
    this_is_I6_Main = 0;
    text_stream *OUT = CodeGen::current(gen);
    WRITE("[ %S", fn_name);
    if (Str::eq(fn_name, I"Main")) this_is_I6_Main = 1;
    if (Str::eq(fn_name, I"DebugAction")) this_is_I6_Main = 2;
    if (Str::eq(fn_name, I"DebugAttribute")) { this_is_I6_Main = 3; I6_DebugAttribute_seen = TRUE; }
    if (Str::eq(fn_name, I"DebugProperty")) this_is_I6_Main = 4;
    I6Target::seek_locals(gen, D);
    WRITE(";");
    switch (this_is_I6_Main) {
        case 1:
            WRITE("#ifdef TARGET_ZCODE; max_z_object = #largest_object - 255; #endif;\n");
            break;
        case 2:
            WRITE("#ifdef TARGET_GLULX;\n");
            WRITE("if (a < 4096) {\n");
            WRITE("    if (a < 0 || a >= #identifiers_table-->7) print \"<invalid action \", a, \">\";\n");
            WRITE("    else {\n");
            WRITE("        str = #identifiers_table-->6;\n");
            WRITE("        str = str-->a;\n");
            WRITE("        if (str) print (string) str; else print \"<unnamed action \", a, \">\";\n");
            WRITE("        return;\n");
            WRITE("    }\n");
            WRITE("}\n");
            WRITE("#endif;\n");
            WRITE("#ifdef TARGET_ZCODE;\n");
            WRITE("if (a < 4096) {\n");
            WRITE("    anames = #identifiers_table;\n");
            WRITE("    anames = anames + 2*(anames-->0) + 2*48;\n");
            WRITE("    print (string) anames-->a;\n");
            WRITE("    return;\n");
            WRITE("}\n");
            WRITE("#endif;\n");
            break;
        case 3:
            WRITE("#ifdef TARGET_GLULX;\n");
            WRITE("if (a < 0 || a >= NUM_ATTR_BYTES*8) print \"<invalid attribute \", a, \">\";\n");
            WRITE("else {\n");
            WRITE("    str = #identifiers_table-->4;\n");
            WRITE("    str = str-->a;\n");
            WRITE("    if (str) print (string) str; else print \"<unnamed attribute \", a, \">\";\n");
            WRITE("}\n");
            WRITE("return;\n");
            WRITE("#endif;\n");
            WRITE("#ifdef TARGET_ZCODE;\n");
            WRITE("if (a < 0 || a >= 48) print \"<invalid attribute \", a, \">\";\n");
            WRITE("else {\n");
            WRITE("    anames = #identifiers_table; anames = anames + 2*(anames-->0);\n");
            WRITE("    print (string) anames-->a;\n");
            WRITE("}\n");
            WRITE("return;\n");
            WRITE("#endif;\n");
            break;
        case 4:
            WRITE("print (property) p;\n");
            WRITE("return;\n");
            break;
    }
    Vanilla::node(gen, D);
	text_stream *OUT = CodeGen::current(gen);
	text_stream *fn_name = Inter::Symbols::name(fn);
    if (Str::eq(fn_name, I"FINAL_CODE_STARTUP_R")) {
        WRITE("#ifdef TARGET_GLULX;\n");
        WRITE("@gestalt 9 0 res;\n");
        WRITE("if (res == 0) rfalse;\n");
        WRITE("addr = #classes_table;\n");
        WRITE("@accelparam 0 addr;\n");
        WRITE("@accelparam 1 INDIV_PROP_START;\n");
        WRITE("@accelparam 2 Class;\n");
        WRITE("@accelparam 3 Object;\n");
        WRITE("@accelparam 4 Routine;\n");
        WRITE("@accelparam 5 String;\n");
        WRITE("addr = #globals_array + WORDSIZE * #g$self;\n");
        WRITE("@accelparam 6 addr;\n");
        WRITE("@accelparam 7 NUM_ATTR_BYTES;\n");
        WRITE("addr = #cpv__start;\n");
        WRITE("@accelparam 8 addr;\n");
        WRITE("@accelfunc 1 Z__Region;\n");
        WRITE("@accelfunc 2 CP__Tab;\n");
        WRITE("@accelfunc 3 RA__Pr;\n");
        WRITE("@accelfunc 4 RL__Pr;\n");
        WRITE("@accelfunc 5 OC__Cl;\n");
        WRITE("@accelfunc 6 RV__Pr;\n");
        WRITE("@accelfunc 7 OP__Pr;\n");
        WRITE("#endif;\n");
        WRITE("rfalse;\n");
    }
    WRITE("];\n");
    CodeGen::deselect(gen, saved);
}
void I6Target::place_label(code_generator *cgt, code_generation *gen, text_stream *label_name) {
    text_stream *OUT = CodeGen::current(gen);
    WRITE("%S;\n", label_name);
}
void I6Target::evaluate_label(code_generator *cgt, code_generation *gen, text_stream *label_name) {
    text_stream *OUT = CodeGen::current(gen);
    LOOP_THROUGH_TEXT(pos, label_name)
        if (Str::get(pos) != '.')
            PUT(Str::get(pos));
}

§9. This enables use of March 2009 extension to Glulx which optimises the speed of Inform-compiled story files by moving the work of I6 veneer routines into the interpreter itself. The empty function declaration here is misleading: its actual contents are written out longhand during final code compilation to Glulx, but not during e.g. final code compilation to C. This means that the Inter tree doesn't need to refer to eldritch Glulx-only symbols like #g$self or implement assembly-language operations like @accelparam. (See final.)

void I6Target::invoke_function(code_generator *cgt, code_generation *gen, inter_symbol *fn, inter_tree_node *P, int void_context) {
    text_stream *fn_name = Inter::Symbols::name(fn);
    text_stream *OUT = CodeGen::current(gen);
    WRITE("%S(", fn_name);
    int c = 0;
    LOOP_THROUGH_INTER_CHILDREN(F, P) {
        if (c++ > 0) WRITE(", ");
        Vanilla::node(gen, F);
    }
    WRITE(")");
    if (void_context) WRITE(";\n");
}

void I6Target::invoke_opcode(code_generator *cgt, code_generation *gen,
    text_stream *opcode, int operand_count, inter_tree_node **operands,
    inter_tree_node *label, int label_sense, int void_context) {
    text_stream *OUT = CodeGen::current(gen);
    WRITE("%S", opcode);
    for (int opc = 0; opc < operand_count; opc++) {
        WRITE(" ");
        Vanilla::node(gen, operands[opc]);
    }
    if (label) {
        WRITE(" ?");
        if (label_sense == FALSE) WRITE("~");
        Vanilla::node(gen, label);
    }
    if (void_context) WRITE(";\n");
}

int I6Target::begin_array(code_generator *cgt, code_generation *gen, text_stream *array_name, inter_symbol *array_s, inter_tree_node *P, int format, segmentation_pos *saved) {
    if (saved) {
        int choice = early_matter_I7CGS;
        if (array_s) {
            if (Str::eq(array_s->symbol_name, I"DynamicMemoryAllocation")) choice = very_early_matter_I7CGS;
            if (Inter::Symbols::read_annotation(array_s, LATE_IANN) == 1) choice = code_at_eof_I7CGS;
            if (Inter::Symbols::read_annotation(array_s, BUFFERARRAY_IANN) == 1) choice = arrays_at_eof_I7CGS;
            if (Inter::Symbols::read_annotation(array_s, BYTEARRAY_IANN) == 1) choice = arrays_at_eof_I7CGS;
            if (Inter::Symbols::read_annotation(array_s, TABLEARRAY_IANN) == 1) choice = arrays_at_eof_I7CGS;
            if (Inter::Symbols::read_annotation(array_s, VERBARRAY_IANN) == 1) choice = verbs_at_eof_I7CGS;
        }
        *saved = CodeGen::select(gen, choice);
    }
    text_stream *OUT = CodeGen::current(gen);

    if ((array_s) && (Inter::Symbols::read_annotation(array_s, VERBARRAY_IANN) == 1)) {
        WRITE("Verb ");
        if (Inter::Symbols::read_annotation(array_s, METAVERB_IANN) == 1) WRITE("meta ");
        for (int i=DATA_CONST_IFLD; i<P->W.extent; i=i+2) {
            WRITE(" ");
            inter_ti val1 = P->W.data[i], val2 = P->W.data[i+1];
            if (Inter::Symbols::is_stored_in_data(val1, val2)) {
                inter_symbol *aliased = InterSymbolsTables::symbol_from_data_pair_and_table(val1, val2, Inter::Packages::scope_of(P));
                if (aliased == NULL) internal_error("bad aliased symbol");
                if (Inter::Symbols::read_annotation(aliased, SCOPE_FILTER_IANN) == 1)
                    WRITE("scope=");
                if (Inter::Symbols::read_annotation(aliased, NOUN_FILTER_IANN) == 1)
                    WRITE("noun=");
                text_stream *S = Inter::Symbols::name(aliased);
                if (Str::begins_with_wide_string(S, L"##")) {
                    LOOP_THROUGH_TEXT(pos, S)
                        if (pos.index >= 2)
                            PUT(Str::get(pos));
                } else {
                    if (aliased == verb_directive_divider_symbol) WRITE("\n\t*");
                    else if (aliased == verb_directive_reverse_symbol) WRITE("reverse");
                    else if (aliased == verb_directive_slash_symbol) WRITE("/");
                    else if (aliased == verb_directive_result_symbol) WRITE("->");
                    else if (aliased == verb_directive_special_symbol) WRITE("special");
                    else if (aliased == verb_directive_number_symbol) WRITE("number");
                    else if (aliased == verb_directive_noun_symbol) WRITE("noun");
                    else if (aliased == verb_directive_multi_symbol) WRITE("multi");
                    else if (aliased == verb_directive_multiinside_symbol) WRITE("multiinside");
                    else if (aliased == verb_directive_multiheld_symbol) WRITE("multiheld");
                    else if (aliased == verb_directive_held_symbol) WRITE("held");
                    else if (aliased == verb_directive_creature_symbol) WRITE("creature");
                    else if (aliased == verb_directive_topic_symbol) WRITE("topic");
                    else if (aliased == verb_directive_multiexcept_symbol) WRITE("multiexcept");
                    else I6Target::compile_literal_symbol(cgt, gen, aliased);
                }
            } else {
                CodeGen::pair(gen, P, val1, val2);
            }
        }
        WRITE(";");
        return FALSE;
    }
    WRITE("Array %S ", array_name);
    switch (format) {
        case WORD_ARRAY_FORMAT: WRITE("-->"); break;
        case BYTE_ARRAY_FORMAT: WRITE("->"); break;
        case TABLE_ARRAY_FORMAT: WRITE("table"); break;
        case BUFFER_ARRAY_FORMAT: WRITE("buffer"); break;
    }
    return TRUE;
}

void I6Target::array_entry(code_generator *cgt, code_generation *gen, text_stream *entry, int format) {
    text_stream *OUT = CodeGen::current(gen);
    WRITE(" (%S)", entry);
}

void I6Target::compile_literal_symbol(code_generator *cgt, code_generation *gen, inter_symbol *aliased) {
    text_stream *OUT = CodeGen::current(gen);
    text_stream *S = Inter::Symbols::name(aliased);
    Generators::mangle(gen, OUT, S);
}

§10. Alternatively, we can just specify how many entries there will be: they will then be initialised to 0.

void I6Target::array_entries(code_generator *cgt, code_generation *gen,
    int how_many, int plus_ips, int format) {
    text_stream *OUT = CodeGen::current(gen);
    if (plus_ips) WRITE(" (%d + INDIV_PROP_START)", how_many, plus_ips);
    else WRITE(" (%d)", how_many);
}

void I6Target::end_array(code_generator *cgt, code_generation *gen, int format, segmentation_pos *saved) {
    text_stream *OUT = CodeGen::current(gen);
    WRITE(";\n");
    if (saved) CodeGen::deselect(gen, *saved);
}

void I6Target::new_action(code_generator *cgt, code_generation *gen, text_stream *name, int true_action) {
    if (true_action == FALSE) {
        segmentation_pos saved = CodeGen::select(gen, early_matter_I7CGS);
        text_stream *OUT = CodeGen::current(gen);
        WRITE("Fake_Action %S;\n", name);
        CodeGen::deselect(gen, saved);
    }
}

void I6Target::pseudo_object(code_generator *cgt, code_generation *gen, text_stream *obj_name) {
    segmentation_pos saved = CodeGen::select(gen, main_matter_I7CGS);
    text_stream *OUT = CodeGen::current(gen);
    WRITE("Object %S \"(%S object)\" has concealed;\n", obj_name, obj_name);
    CodeGen::deselect(gen, saved);
}