The standard set of primitive invocations available in Inter code as generated by the Inform tool-chain.


§1. The standard Inform 7 instruction set. Metadata on the primitives used by Inter in the Inform tool-set is stored as an array standard_inform7_primitives of the following records:

typedef struct inform7_primitive {
    inter_ti BIP;
    char *name_c;
    char *signature_c;
    struct text_stream *name;
    struct text_stream *signature;
    int term_count;
    int term_categories[8];
    int takes_code_blocks;
} inform7_primitive;

§2. Each different primitive has a unique BIP number. (The origins of the term BIP are now lost, though the I and the P presumably once stood for "Inform" and "primitive".) BIPs count upwards contiguously from 1.

The point of BIPs is simply that it would be too slow to index primitives only by their instruction names, because even with all the hashing in the world, there would have to be string comparisons.

define MAX_BIPS 100
enum PLUS_BIP from 1
enum MINUS_BIP
enum UNARYMINUS_BIP
enum TIMES_BIP
enum DIVIDE_BIP
enum MODULO_BIP
enum BITWISEAND_BIP
enum BITWISEOR_BIP
enum BITWISENOT_BIP
enum SEQUENTIAL_BIP
enum TERNARYSEQUENTIAL_BIP
enum RANDOM_BIP
enum STORE_BIP
enum PREINCREMENT_BIP
enum POSTINCREMENT_BIP
enum PREDECREMENT_BIP
enum POSTDECREMENT_BIP
enum SETBIT_BIP
enum CLEARBIT_BIP
enum PUSH_BIP
enum PULL_BIP
enum LOOKUP_BIP
enum LOOKUPBYTE_BIP
enum PROPERTYARRAY_BIP
enum PROPERTYLENGTH_BIP
enum PROPERTYEXISTS_BIP
enum PROPERTYVALUE_BIP
enum MOVE_BIP
enum REMOVE_BIP
enum CHILD_BIP
enum CHILDREN_BIP
enum SIBLING_BIP
enum PARENT_BIP
enum METACLASS_BIP
enum NOT_BIP
enum AND_BIP
enum OR_BIP
enum EQ_BIP
enum NE_BIP
enum GT_BIP
enum GE_BIP
enum LT_BIP
enum LE_BIP
enum OFCLASS_BIP
enum IN_BIP
enum NOTIN_BIP
enum ALTERNATIVE_BIP
enum ENABLEPRINTING_BIP
enum FONT_BIP
enum STYLE_BIP
enum PRINT_BIP
enum PRINTNL_BIP
enum PRINTCHAR_BIP
enum PRINTOBJ_BIP
enum PRINTNUMBER_BIP
enum PRINTDWORD_BIP
enum PRINTSTRING_BIP
enum BOX_BIP
enum SPACES_BIP
enum IF_BIP
enum IFDEBUG_BIP
enum IFSTRICT_BIP
enum IFELSE_BIP
enum WHILE_BIP
enum DO_BIP
enum FOR_BIP
enum OBJECTLOOP_BIP
enum OBJECTLOOPX_BIP
enum BREAK_BIP
enum CONTINUE_BIP
enum SWITCH_BIP
enum CASE_BIP
enum ALTERNATIVECASE_BIP
enum DEFAULT_BIP
enum RETURN_BIP
enum JUMP_BIP
enum QUIT_BIP
enum RESTORE_BIP
enum INDIRECT0V_BIP
enum INDIRECT1V_BIP
enum INDIRECT2V_BIP
enum INDIRECT3V_BIP
enum INDIRECT4V_BIP
enum INDIRECT5V_BIP
enum INDIRECT0_BIP
enum INDIRECT1_BIP
enum INDIRECT2_BIP
enum INDIRECT3_BIP
enum INDIRECT4_BIP
enum INDIRECT5_BIP
enum MESSAGE0_BIP
enum MESSAGE1_BIP
enum MESSAGE2_BIP
enum MESSAGE3_BIP
enum EXTERNALCALL_BIP

§3. And here is the array of metadata. It's tiresome to have to include the null fields here, which is done only because text_stream literals I"like this" cannot be compiled in a constant context by the clang C compiler. So the names and signatures of the primitives are compiled as char * literals instead, and converted on first use at runtime.

It is essential that the sequence of rows below is the same as the sequence of enumerations above; but Primitives::prepare_standard_set_array checks this at runtime and throws an error if not.

define SIP_RECORD_END
    NULL, NULL, 0,
    { VAL_PRIM_CAT, VAL_PRIM_CAT, VAL_PRIM_CAT, VAL_PRIM_CAT, VAL_PRIM_CAT, VAL_PRIM_CAT, VAL_PRIM_CAT, VAL_PRIM_CAT },
    FALSE
inform7_primitive standard_inform7_primitives[] = {
    { PLUS_BIP,              "!plus",              "val val -> val",                  SIP_RECORD_END },
    { MINUS_BIP,             "!minus",             "val val -> val",                  SIP_RECORD_END },
    { UNARYMINUS_BIP,        "!unaryminus",        "val -> val",                      SIP_RECORD_END },
    { TIMES_BIP,             "!times",             "val val -> val",                  SIP_RECORD_END },
    { DIVIDE_BIP,            "!divide",            "val val -> val",                  SIP_RECORD_END },
    { MODULO_BIP,            "!modulo",            "val val -> val",                  SIP_RECORD_END },
    { BITWISEAND_BIP,        "!bitwiseand",        "val val -> val",                  SIP_RECORD_END },
    { BITWISEOR_BIP,         "!bitwiseor",         "val val -> val",                  SIP_RECORD_END },
    { BITWISENOT_BIP,        "!bitwisenot",        "val -> val",                      SIP_RECORD_END },
    { SEQUENTIAL_BIP,        "!sequential",        "val val -> val",                  SIP_RECORD_END },
    { TERNARYSEQUENTIAL_BIP, "!ternarysequential", "val val val -> val",              SIP_RECORD_END },
    { RANDOM_BIP,            "!random",            "val -> val",                      SIP_RECORD_END },

    { STORE_BIP,             "!store",             "ref val -> val",                  SIP_RECORD_END },
    { PREINCREMENT_BIP,      "!preincrement",      "ref -> val",                      SIP_RECORD_END },
    { POSTINCREMENT_BIP,     "!postincrement",     "ref -> val",                      SIP_RECORD_END },
    { PREDECREMENT_BIP,      "!predecrement",      "ref -> val",                      SIP_RECORD_END },
    { POSTDECREMENT_BIP,     "!postdecrement",     "ref -> val",                      SIP_RECORD_END },
    { SETBIT_BIP,            "!setbit",            "ref val -> void",                 SIP_RECORD_END },
    { CLEARBIT_BIP,          "!clearbit",          "ref val -> void",                 SIP_RECORD_END },

    { PUSH_BIP,              "!push",              "val -> void",                     SIP_RECORD_END },
    { PULL_BIP,              "!pull",              "ref -> void",                     SIP_RECORD_END },
    { LOOKUP_BIP,            "!lookup",            "val val -> val",                  SIP_RECORD_END },
    { LOOKUPBYTE_BIP,        "!lookupbyte",        "val val -> val",                  SIP_RECORD_END },
    { PROPERTYARRAY_BIP,     "!propertyarray",     "val val val -> val",              SIP_RECORD_END },
    { PROPERTYLENGTH_BIP,    "!propertylength",    "val val val -> val",              SIP_RECORD_END },
    { PROPERTYEXISTS_BIP,    "!propertyexists",    "val val val -> val",              SIP_RECORD_END },
    { PROPERTYVALUE_BIP,     "!propertyvalue",     "val val val -> val",              SIP_RECORD_END },

    { MOVE_BIP,              "!move",              "val val -> void",                 SIP_RECORD_END },
    { REMOVE_BIP,            "!remove",            "val -> void",                     SIP_RECORD_END },
    { CHILD_BIP,             "!child",             "val -> val",                      SIP_RECORD_END },
    { CHILDREN_BIP,          "!children",          "val -> val",                      SIP_RECORD_END },
    { SIBLING_BIP,           "!sibling",           "val -> val",                      SIP_RECORD_END },
    { PARENT_BIP,            "!parent",            "val -> val",                      SIP_RECORD_END },
    { METACLASS_BIP,         "!metaclass",         "val -> val",                      SIP_RECORD_END },

    { NOT_BIP,               "!not",               "val -> val",                      SIP_RECORD_END },
    { AND_BIP,               "!and",               "val val -> val",                  SIP_RECORD_END },
    { OR_BIP,                "!or",                "val val -> val",                  SIP_RECORD_END },
    { EQ_BIP,                "!eq",                "val val -> val",                  SIP_RECORD_END },
    { NE_BIP,                "!ne",                "val val -> val",                  SIP_RECORD_END },
    { GT_BIP,                "!gt",                "val val -> val",                  SIP_RECORD_END },
    { GE_BIP,                "!ge",                "val val -> val",                  SIP_RECORD_END },
    { LT_BIP,                "!lt",                "val val -> val",                  SIP_RECORD_END },
    { LE_BIP,                "!le",                "val val -> val",                  SIP_RECORD_END },
    { OFCLASS_BIP,           "!ofclass",           "val val -> val",                  SIP_RECORD_END },
    { IN_BIP,                "!in",                "val val -> val",                  SIP_RECORD_END },
    { NOTIN_BIP,             "!notin",             "val val -> val",                  SIP_RECORD_END },
    { ALTERNATIVE_BIP,       "!alternative",       "val val -> val",                  SIP_RECORD_END },

    { ENABLEPRINTING_BIP,    "!enableprinting",    "void -> void",                    SIP_RECORD_END },
    { FONT_BIP,              "!font",              "val -> void",                     SIP_RECORD_END },
    { STYLE_BIP,             "!style",             "val -> void",                     SIP_RECORD_END },
    { PRINT_BIP,             "!print",             "val -> void",                     SIP_RECORD_END },
    { PRINTNL_BIP,           "!printnl",           "void -> void",                    SIP_RECORD_END },
    { PRINTCHAR_BIP,         "!printchar",         "val -> void",                     SIP_RECORD_END },
    { PRINTOBJ_BIP,          "!printobj",          "val -> void",                     SIP_RECORD_END },
    { PRINTNUMBER_BIP,       "!printnumber",       "val -> void",                     SIP_RECORD_END },
    { PRINTDWORD_BIP,        "!printdword",        "val -> void",                     SIP_RECORD_END },
    { PRINTSTRING_BIP,       "!printstring",       "val -> void",                     SIP_RECORD_END },
    { BOX_BIP,               "!box",               "val -> void",                     SIP_RECORD_END },
    { SPACES_BIP,            "!spaces",            "val -> void",                     SIP_RECORD_END },

    { IF_BIP,                "!if",                "val code -> void",                SIP_RECORD_END },
    { IFDEBUG_BIP,           "!ifdebug",           "code -> void",                    SIP_RECORD_END },
    { IFSTRICT_BIP,          "!ifstrict",          "code -> void",                    SIP_RECORD_END },
    { IFELSE_BIP,            "!ifelse",            "val code code -> void",           SIP_RECORD_END },
    { WHILE_BIP,             "!while",             "val code -> void",                SIP_RECORD_END },
    { DO_BIP,                "!do",                "val code -> void",                SIP_RECORD_END },
    { FOR_BIP,               "!for",               "val val val code -> void",        SIP_RECORD_END },
    { OBJECTLOOP_BIP,        "!objectloop",        "ref val val code -> void",        SIP_RECORD_END },
    { OBJECTLOOPX_BIP,       "!objectloopx",       "ref val code -> void",            SIP_RECORD_END },
    { BREAK_BIP,             "!break",             "void -> void",                    SIP_RECORD_END },
    { CONTINUE_BIP,          "!continue",          "void -> void",                    SIP_RECORD_END },
    { SWITCH_BIP,            "!switch",            "val code -> void",                SIP_RECORD_END },
    { CASE_BIP,              "!case",              "val code -> void",                SIP_RECORD_END },
    { ALTERNATIVECASE_BIP,   "!alternativecase",   "val val -> val",                  SIP_RECORD_END },
    { DEFAULT_BIP,           "!default",           "code -> void",                    SIP_RECORD_END },
    { RETURN_BIP,            "!return",            "val -> void",                     SIP_RECORD_END },
    { JUMP_BIP,              "!jump",              "lab -> void",                     SIP_RECORD_END },
    { QUIT_BIP,              "!quit",              "void -> void",                    SIP_RECORD_END },
    { RESTORE_BIP,           "!restore",           "lab -> void",                     SIP_RECORD_END },

    { INDIRECT0V_BIP,        "!indirect0v",        "val -> void",                     SIP_RECORD_END },
    { INDIRECT1V_BIP,        "!indirect1v",        "val val -> void",                 SIP_RECORD_END },
    { INDIRECT2V_BIP,        "!indirect2v",        "val val val -> void",             SIP_RECORD_END },
    { INDIRECT3V_BIP,        "!indirect3v",        "val val val val -> void",         SIP_RECORD_END },
    { INDIRECT4V_BIP,        "!indirect4v",        "val val val val val -> void",     SIP_RECORD_END },
    { INDIRECT5V_BIP,        "!indirect5v",        "val val val val val val -> void", SIP_RECORD_END },
    { INDIRECT0_BIP,         "!indirect0",         "val -> val",                      SIP_RECORD_END },
    { INDIRECT1_BIP,         "!indirect1",         "val val -> val",                  SIP_RECORD_END },
    { INDIRECT2_BIP,         "!indirect2",         "val val val -> val",              SIP_RECORD_END },
    { INDIRECT3_BIP,         "!indirect3",         "val val val val -> val",          SIP_RECORD_END },
    { INDIRECT4_BIP,         "!indirect4",         "val val val val val -> val",      SIP_RECORD_END },
    { INDIRECT5_BIP,         "!indirect5",         "val val val val val val -> val",  SIP_RECORD_END },
    { MESSAGE0_BIP,          "!message0",          "val val -> val",                  SIP_RECORD_END },
    { MESSAGE1_BIP,          "!message1",          "val val val -> val",              SIP_RECORD_END },
    { MESSAGE2_BIP,          "!message2",          "val val val val -> val",          SIP_RECORD_END },
    { MESSAGE3_BIP,          "!message3",          "val val val val val -> val",      SIP_RECORD_END },
    { EXTERNALCALL_BIP,      "!externalcall",      "val val -> val",                  SIP_RECORD_END },

    { 0,                     "",                   "",                                SIP_RECORD_END }
};

§4. The following must be called before the above array can be used. It checks that the numbering is right, and converts the names and signatures from char * to text_stream *.

int standard_inform7_primitives_prepared = FALSE;
inter_ti standard_inform7_primitives_extent = 0;

void Primitives::prepare_standard_set_array(void) {
    if (standard_inform7_primitives_prepared == FALSE) {
        standard_inform7_primitives_prepared = TRUE;
        for (inter_ti i=0; ; i++) {
            if (standard_inform7_primitives[i].BIP == 0) break;
            if (i >= MAX_BIPS) internal_error("MAX_BIPS set too low");
            if (standard_inform7_primitives[i].BIP != i+1)
                internal_error("primitives table disordered");
            standard_inform7_primitives[i].name = Str::new();
            WRITE_TO(standard_inform7_primitives[i].name, "%s",
                standard_inform7_primitives[i].name_c);
            standard_inform7_primitives[i].signature = Str::new();
            WRITE_TO(standard_inform7_primitives[i].signature, "%s",
                standard_inform7_primitives[i].signature_c);
            Parse and sanity-check the signature text4.1;
            standard_inform7_primitives_extent++;
        }
    }
}

§4.1. We only care here abput the part of the signature before the ->, but we go ahead and perform a full sanity check on it anyway, just in case somebody some day amends the above table but gets it wrong. The time consumed by these checks is trivial, since this happens only once per run.

Parse and sanity-check the signature text4.1 =

    int p = 0, before_arrow = TRUE, pre_void = FALSE, inputs = 0, outputs = 0;
    text_stream *S = standard_inform7_primitives[i].signature;
    TEMPORARY_TEXT(term)
    while (p <= Str::len(S)) {
        inchar32_t c = Str::get_at(S, p);
        if ((Characters::is_whitespace(c)) || (c == 0)) {
            if ((Str::eq(term, I"->")) && (before_arrow)) before_arrow = FALSE;
            else {
                int this = -1;
                if (Str::eq(term, I"void")) {
                    this = -2;
                    if (before_arrow) pre_void = TRUE;
                }
                if (Str::eq(term, I"val"))  this = VAL_PRIM_CAT;
                if (Str::eq(term, I"ref"))  this = REF_PRIM_CAT;
                if (Str::eq(term, I"lab"))  this = LAB_PRIM_CAT;
                if (Str::eq(term, I"code")) this = CODE_PRIM_CAT;
                if (this == -1) internal_error("unknown term category in primitive");
                if (before_arrow) {
                    if (this != -2) {
                        if (inputs >= 8) internal_error("too many terms in primitive");
                        standard_inform7_primitives[i].term_categories[inputs] = this;
                        inputs++;
                    }
                } else {
                    if ((this != -2) && (this != VAL_PRIM_CAT))
                        internal_error("bad term after -> in primitive");
                    outputs++;
                }
            }
            Str::clear(term);
        } else {
            PUT_TO(term, c);
        }
        p++;
    }
    DISCARD_TEXT(term)
    if ((outputs > 1) || ((pre_void) && (inputs > 0)))
        internal_error("malformed signature in primitive");
    standard_inform7_primitives[i].term_count = inputs;
    standard_inform7_primitives[i].takes_code_blocks = FALSE;
    for (int t=0; t<inputs; t++)
        if (standard_inform7_primitives[i].term_categories[t] == CODE_PRIM_CAT)
            standard_inform7_primitives[i].takes_code_blocks = TRUE;

§5. We can now convert between BIP and name. Note that Primitives::name_to_BIP is relatively slow: but this doesn't matter because we will always cache the results (see below).

text_stream *Primitives::BIP_to_name(inter_ti bip) {
    Primitives::prepare_standard_set_array();
    if ((bip >= 1) && (bip <= standard_inform7_primitives_extent))
        return standard_inform7_primitives[bip - 1].name;
    return I"<none>";
}

inter_ti Primitives::name_to_BIP(text_stream *name) {
    Primitives::prepare_standard_set_array();
    for (inter_ti i=0; i<standard_inform7_primitives_extent; i++)
        if (Str::eq(name, standard_inform7_primitives[i].name))
            return i+1;
    return 0;
}

§6. This is printed when inter is run with the -primitives switch.

void Primitives::show_primitives(OUTPUT_STREAM) {
    WRITE("  Code     Primitive           Signature\n");
    Primitives::prepare_standard_set_array();
    for (inter_ti i=0; i<standard_inform7_primitives_extent; i++) {
        inform7_primitive *prim = &(standard_inform7_primitives[i]);
        WRITE("  %4x     %S", prim->BIP, prim->name);
        for (int j = Str::len(prim->name); j<20; j++) PUT(' ');
        WRITE("%S\n", prim->signature);
    }
}

§7. In general the standard set is a miscellany, but with one systematic family of primitives for making indirect function calls (that is, calling a function whose identity is not known at compile time). These 12 primitives all do the same thing, but vary in their signatures, according to how many arguments the function call has, and whether its return value is to be used or discarded.

The following functions allow us to ask for the right primitive for the job:

inter_ti Primitives::BIP_for_indirect_call_returning_value(int arity) {
    switch (arity) {
        case 0: return INDIRECT0_BIP;
        case 1: return INDIRECT1_BIP;
        case 2: return INDIRECT2_BIP;
        case 3: return INDIRECT3_BIP;
        case 4: return INDIRECT4_BIP;
        case 5: return INDIRECT5_BIP;
        default: internal_error("indirect function call with too many arguments");
    }
    return 0;
}

inter_ti Primitives::BIP_for_void_indirect_call(int arity) {
    switch (arity) {
        case 0: return INDIRECT0V_BIP;
        case 1: return INDIRECT1V_BIP;
        case 2: return INDIRECT2V_BIP;
        case 3: return INDIRECT3V_BIP;
        case 4: return INDIRECT4V_BIP;
        case 5: return INDIRECT5V_BIP;
        default: internal_error("indirectv function call with too many arguments");
    }
    return 0;
}

inter_ti Primitives::BIP_for_message_send(int arity) {
    switch (arity) {
        case 2: return MESSAGE0_BIP;
        case 3: return MESSAGE1_BIP;
        case 4: return MESSAGE2_BIP;
        case 5: return MESSAGE3_BIP;
        default: internal_error("message call must have arity 2 to 5");
    }
    return 0;
}

§8. And these functions say whether or not a BIP belongs to the family:

int Primitives::is_BIP_for_indirect_call_returning_value(inter_ti s) {
    if (s == INDIRECT0_BIP) return TRUE;
    if (s == INDIRECT1_BIP) return TRUE;
    if (s == INDIRECT2_BIP) return TRUE;
    if (s == INDIRECT3_BIP) return TRUE;
    if (s == INDIRECT4_BIP) return TRUE;
    if (s == INDIRECT5_BIP) return TRUE;
    return FALSE;
}

int Primitives::is_BIP_for_void_indirect_call(inter_ti s) {
    if (s == INDIRECT0V_BIP) return TRUE;
    if (s == INDIRECT1V_BIP) return TRUE;
    if (s == INDIRECT2V_BIP) return TRUE;
    if (s == INDIRECT3V_BIP) return TRUE;
    if (s == INDIRECT4V_BIP) return TRUE;
    if (s == INDIRECT5V_BIP) return TRUE;
    return FALSE;
}

§9. About the terms. For example, 0 for the signature void -> val, or 2 for ref val -> val.

The *_XBIP operations are treated as if they had the signature val val -> void.

int Primitives::term_count(inter_ti BIP) {
    Primitives::prepare_standard_set_array();
    if (BIP >= LOWEST_XBIP_VALUE) return 2;
    return standard_inform7_primitives[BIP - 1].term_count;
}

§10. And this returns the primitive category for each term, counting from 0: this will be VAL_PRIM_CAT, CODE_PRIM_CAT, REF_PRIM_CAT or LAB_PRIM_CAT.

Again, the *_XBIP operations are treated as if val val -> void.

int Primitives::term_category(inter_ti BIP, int i) {
    Primitives::prepare_standard_set_array();
    if ((i < 0) || (i >= 8)) internal_error("term out of range");
    if (BIP >= LOWEST_XBIP_VALUE) return VAL_PRIM_CAT;
    return standard_inform7_primitives[BIP - 1].term_categories[i];
}

§11. Returns TRUE if any of those categories is a CODE_PRIM_CAT; note that this is cached for speed.

int Primitives::takes_code_blocks(inter_ti BIP) {
    Primitives::prepare_standard_set_array();
    if (BIP >= LOWEST_XBIP_VALUE) return FALSE;
    return standard_inform7_primitives[BIP - 1].takes_code_blocks;
}

§12. Primitives within a specific tree. So much for discussing the instruction set in the abstract: now we need code to handle its declaration in each Inter tree we make. Note that, for speed, each inter_tree structure contains the following index array inside it:

typedef struct site_primitives_data {
    struct inter_symbol *primitives_by_BIP[MAX_BIPS];
} site_primitives_data;

void Primitives::clear_site_data(inter_tree *I) {
    building_site *B = &(I->site);
    for (int i=0; i<MAX_BIPS; i++) B->spridata.primitives_by_BIP[i] = NULL;
}

§13. That array will allow us to obtain almost instantly the Inter symbol for the primitive in I having any given BIP. We need to remember that primitives can come into being in two ways, though: either by us creating them here (see below), or by Inter code being read in from an external file. If the latter, the following function must be run to make sure the index is built:

void Primitives::index_primitives_in_tree(inter_tree *I) {
    InterTree::traverse_root_only(I, Primitives::scan_visitor, NULL, PRIMITIVE_IST);
}

void Primitives::scan_visitor(inter_tree *I, inter_tree_node *P, void *v_state) {
    inter_symbol *prim = PrimitiveInstruction::primitive(P);
    inter_ti bip = Primitives::to_BIP(I, prim);
    if (bip) I->site.spridata.primitives_by_BIP[bip] = prim;
}

§14. Here is where we declare primitives. Since there are only around 100 of these, it's fine for the actual primitive declarations to be made a little slowly: so we do it by writing the declarations out in textual Inter and then parsing them. We then make various paranoid consistency checks.

void Primitives::declare_standard_set(inter_tree *I, inter_bookmark *IBM) {
    Primitives::prepare_standard_set_array();
    for (inter_ti i=0; i<standard_inform7_primitives_extent; i++)
        Primitives::declare_one(I, IBM, &(standard_inform7_primitives[i]));
}

inter_symbol *Primitives::declare_one(inter_tree *I, inter_bookmark *IBM, inform7_primitive *prim) {
    text_stream *name = prim->name;
    text_stream *signature = prim->signature;
    TEMPORARY_TEXT(prim_command)
    WRITE_TO(prim_command, "primitive %S %S", name, signature);
    Produce::guard(TextualInter::parse_single_line(prim_command, NULL, IBM));
    inter_error_message *E = NULL;
    inter_symbol *S = TextualInter::find_global_symbol(IBM, NULL, name, PRIMITIVE_IST, &E);
    inter_ti bip = Primitives::to_BIP(I, S);
    if (bip == 0) internal_error("missing bip");
    if (bip != prim->BIP) internal_error("wrong BIP");
    if (bip >= MAX_BIPS) internal_error("unsafely high bip");
    I->site.spridata.primitives_by_BIP[bip] = S;
    Produce::guard(E);
    DISCARD_TEXT(prim_command)
    return S;
}

§15. Used when parsing textual Inter:

inter_symbol *Primitives::declare_one_named(inter_tree *I, inter_bookmark *IBM,
    text_stream *name) {
    Primitives::prepare_standard_set_array();
    for (inter_ti i=0; i<standard_inform7_primitives_extent; i++) {
        inform7_primitive *prim = &(standard_inform7_primitives[i]);
        if (Str::eq(prim->name, name)) return Primitives::declare_one(I, IBM, prim);
    }
    return NULL;
}

§16. Finally, then, we provide functions to convert between BIPs and local primitive symbols.

inter_symbol *Primitives::from_BIP(inter_tree *I, inter_ti bip) {
    if (I == NULL) internal_error("no tree");
    if ((bip < 1) || (bip >= MAX_BIPS)) internal_error("bip out of range");
    inter_symbol *prim = I->site.spridata.primitives_by_BIP[bip];
    if (prim == NULL) {
        WRITE_TO(STDERR, "BIP = %d\n", bip);
        internal_error("undefined primitive");
    }
    return prim;
}

inter_ti Primitives::to_BIP(inter_tree *I, inter_symbol *symb) {
    if (symb == NULL) return 0;
    inter_ti B = PrimitiveInstruction::get_BIP(symb);
    if (B == 0) {
        B = Primitives::name_to_BIP(InterSymbol::identifier(symb));
        if (B != 0) PrimitiveInstruction::set_BIP(symb, B);
    }
    return B;
}