Behaviour specific to copies of the kit genre.


§1. Scanning metadata. Metadata for kits is stored in the following structure. "Attachment" for a kit is the process of taking the Inter code from a binary Inter file in the kit directory and merging it into code already generated by the core module of inform7.

typedef struct inform_kit {
    struct inbuild_copy *as_copy;

    struct text_stream *attachment_point;  where in the Inter hierarchy to attach this
    int priority;  lower kits are attached before higher ones

    struct text_stream *early_source;  additional source text to spool in
    struct linked_list *ittt;  of inform_kit_ittt
    struct linked_list *kind_definitions;  of text_stream
    struct linked_list *extensions;  of inbuild_requirement
    struct linked_list *activations;  of element_activation
    struct linked_list *configurations;  of kit_configuration
    struct text_stream *index_structure;  for indexing projects using this kit
    int defines_Main;  does the Inter code in this kit define the Main routine?
    int supports_nl;  does the Inter code in this kit support a natural language extension?
    CLASS_DEFINITION
} inform_kit;

§2. Kits come with an "if this then that" service for including other kits, and we represent rules with the following:

typedef struct inform_kit_ittt {
    struct text_stream *if_name;
    int if_included;
    struct text_stream *then_name;
    CLASS_DEFINITION
} inform_kit_ittt;

§3. Kits can also enable elements of the Inform programming language: that is, enable compiler support for them. For example, the WorldModelKit enables interactive fiction features of the compiler, but BasicInformKit does not.

typedef struct element_activation {
    struct text_stream *element_name;
    int activate;
    CLASS_DEFINITION
} element_activation;

§4. And kits can be configured with constants linked into them: for example, the constant BasicInformKit`AMERICAN_DIALECT_CFGF. These are mostly set by use options.

typedef struct kit_configuration {
    struct inform_kit *owner;
    struct text_stream *symbol_name;
    int is_flag;
    CLASS_DEFINITION
} kit_configuration;

§5. Here goes, then:

void Kits::scan(inbuild_copy *C) {
    inform_kit *K = CREATE(inform_kit);
    K->as_copy = C;
    if (C == NULL) internal_error("no copy to scan");
    Copies::set_metadata(C, STORE_POINTER_inform_kit(K));

    K->attachment_point = Str::new();
    WRITE_TO(K->attachment_point, "/main/%S", C->edition->work->title);
    K->priority = 10;

    K->early_source = NULL;
    K->ittt = NEW_LINKED_LIST(inform_kit_ittt);
    K->kind_definitions = NEW_LINKED_LIST(text_stream);
    K->extensions = NEW_LINKED_LIST(inbuild_requirement);
    K->activations = NEW_LINKED_LIST(element_activation);
    K->configurations = NEW_LINKED_LIST(kit_configuration);
    K->index_structure = NULL;
    K->defines_Main = FALSE;
    K->supports_nl = FALSE;

    filename *F = Filenames::in(C->location_if_path, I"kit_metadata.json");
    if (TextFiles::exists(F) == FALSE)
        SVEXPLAIN(2, "(no JSON metadata file found at %f)\n", F);
    JSONMetadata::read_metadata_file(C, F, NULL, NULL);

    if (C->metadata_record) {
        Extract activations5.1;
        JSON_value *kit_details =
            JSON::look_up_object(C->metadata_record, I"kit-details");
        if (kit_details) Extract the kit details5.2;
        JSON_value *needs = JSON::look_up_object(C->metadata_record, I"needs");
        if (needs) {
            JSON_value *E;
            LOOP_OVER_LINKED_LIST(E, JSON_value, needs->if_list)
                Extract this possibly conditional requirement5.3;
        }
    }
}

§5.1. Extract activations5.1 =

    JSON_value *activates = JSON::look_up_object(C->metadata_record, I"activates");
    if (activates) {
        JSON_value *E;
        LOOP_OVER_LINKED_LIST(E, JSON_value, activates->if_list)
            Kits::activation(K, E->if_string, TRUE);
    }
    JSON_value *deactivates = JSON::look_up_object(C->metadata_record, I"deactivates");
    if (deactivates) {
        JSON_value *E;
        LOOP_OVER_LINKED_LIST(E, JSON_value, deactivates->if_list)
            Kits::activation(K, E->if_string, FALSE);
    }

§5.2. Extract the kit details5.2 =

    JSON_value *has_priority = JSON::look_up_object(kit_details, I"has-priority");
    if (has_priority) K->priority = has_priority->if_integer;
    JSON_value *defines_Main = JSON::look_up_object(kit_details, I"defines-Main");
    if (defines_Main) K->defines_Main = defines_Main->if_boolean;
    JSON_value *is_language_kit = JSON::look_up_object(kit_details, I"is-language-kit");
    if (is_language_kit) K->supports_nl = is_language_kit->if_boolean;
    JSON_value *indexes_with_structure =
        JSON::look_up_object(kit_details, I"indexes-with-structure");
    if (indexes_with_structure) K->index_structure = indexes_with_structure->if_string;
    JSON_value *provides_kinds = JSON::look_up_object(kit_details, I"provides-kinds");
    if (provides_kinds) {
        JSON_value *E;
        LOOP_OVER_LINKED_LIST(E, JSON_value, provides_kinds->if_list)
            ADD_TO_LINKED_LIST(E->if_string, text_stream, K->kind_definitions);
    }
    JSON_value *inserts_source_text = JSON::look_up_object(kit_details, I"inserts-source-text");
    if (inserts_source_text) {
        K->early_source = Str::duplicate(inserts_source_text->if_string);
        WRITE_TO(K->early_source, "\n\n");
    }
    JSON_value *configs = JSON::look_up_object(kit_details, I"configuration-flags");
    if (configs) {
        int f = TRUE;
        Extract the configuration symbols5.2.1;
    }
    configs = JSON::look_up_object(kit_details, I"configuration-values");
    if (configs) {
        int f = FALSE;
        Extract the configuration symbols5.2.1;
    }

§5.3. Extract this possibly conditional requirement5.3 =

    int parity = TRUE;
    JSON_value *if_clause = JSON::look_up_object(E, I"if");
    JSON_value *unless_clause = JSON::look_up_object(E, I"unless");
    if (unless_clause) {
        if_clause = unless_clause; parity = FALSE;
    }
    JSON_value *need_clause = JSON::look_up_object(E, I"need");
    if (need_clause) {
        JSON_value *need_type = JSON::look_up_object(need_clause, I"type");
        JSON_value *need_title = JSON::look_up_object(need_clause, I"title");
        JSON_value *need_author = JSON::look_up_object(need_clause, I"author");
        JSON_value *need_version = JSON::look_up_object(need_clause, I"version");
        if (Str::eq(need_type->if_string, I"extension"))
            Deal with an extension dependency5.3.1
        else if (Str::eq(need_type->if_string, I"kit"))
            Deal with a kit dependency5.3.2
        else {
            TEMPORARY_TEXT(err)
            WRITE_TO(err, "a kit can only have extensions and kits as dependencies");
            Copies::attach_error(C, CopyErrors::new_T(METADATA_MALFORMED_CE, -1, err));
            DISCARD_TEXT(err)
        }
    }

§5.3.1. Deal with an extension dependency5.3.1 =

    if (if_clause) {
        TEMPORARY_TEXT(err)
        WRITE_TO(err, "a kit can only have an extension as a dependency unconditionally");
        Copies::attach_error(C, CopyErrors::new_T(METADATA_MALFORMED_CE, -1, err));
        DISCARD_TEXT(err)
    }
    text_stream *extension_title = need_title->if_string;
    text_stream *extension_author = need_author?(need_author->if_string):NULL;
    inbuild_work *work = Works::new(extension_genre, extension_title, extension_author);
    if (need_version) Add versioned extension5.3.1.1
    else Add unversioned extension5.3.1.2;

§5.3.1.1. Add versioned extension5.3.1.1 =

    semantic_version_number V = VersionNumbers::from_text(need_version->if_string);
    if (VersionNumbers::is_null(V)) {
        TEMPORARY_TEXT(err)
        WRITE_TO(err, "cannot read version number '%S'", need_version->if_string);
        Copies::attach_error(C, CopyErrors::new_T(METADATA_MALFORMED_CE, -1, err));
        DISCARD_TEXT(err)
    } else {
        inbuild_requirement *req = Requirements::new(work,
            VersionNumberRanges::compatibility_range(V));
        ADD_TO_LINKED_LIST(req, inbuild_requirement, K->extensions);
    }

§5.3.1.2. Add unversioned extension5.3.1.2 =

    inbuild_requirement *req = Requirements::any_version_of(work);
    ADD_TO_LINKED_LIST(req, inbuild_requirement, K->extensions);

§5.3.2. Deal with a kit dependency5.3.2 =

    text_stream *if_kit = C->edition->work->title;
    if (if_clause) {
        JSON_value *if_type = JSON::look_up_object(if_clause, I"type");
        if (Str::eq(if_type->if_string, I"kit") == FALSE) {
            TEMPORARY_TEXT(err)
            WRITE_TO(err, "a kit dependency can only be conditional on other kits");
            Copies::attach_error(C, CopyErrors::new_T(METADATA_MALFORMED_CE, -1, err));
            DISCARD_TEXT(err)
        } else {
            JSON_value *if_title = JSON::look_up_object(if_clause, I"title");
            if (if_title) if_kit = if_title->if_string;  a line for IF fans
        }
    }
    Kits::dependency(K, if_kit, parity, need_title->if_string);

§5.2.1. Extract the configuration symbols5.2.1 =

    JSON_value *E;
    LOOP_OVER_LINKED_LIST(E, JSON_value, configs->if_list) {
        kit_configuration *kc = CREATE(kit_configuration);
        kc->owner = K;
        kc->symbol_name = Str::duplicate(E->if_string);
        kc->is_flag = f;
        ADD_TO_LINKED_LIST(kc, kit_configuration, K->configurations);
    }

§6. We provide if this then that, where inc is true, and if this then not that, where it's false.

void Kits::dependency(inform_kit *K, text_stream *if_text, int inc, text_stream *then_text) {
    inform_kit_ittt *ITTT = CREATE(inform_kit_ittt);
    ITTT->if_name = Str::duplicate(if_text);
    ITTT->if_included = inc;
    ITTT->then_name = Str::duplicate(then_text);
    ADD_TO_LINKED_LIST(ITTT, inform_kit_ittt, K->ittt);
}

§7. Language elements can similarly be activated or deactivated, though the latter may not be useful in practice:

void Kits::activation(inform_kit *K, text_stream *name, int act) {
    element_activation *EA = CREATE(element_activation);
    EA->element_name = Str::duplicate(name);
    EA->activate = act;
    ADD_TO_LINKED_LIST(EA, element_activation, K->activations);
}

§8. The kits included by a project. A project can call this to obtain the inform_kit structure for the copy of a kit, going only on a name such as BasicInformKit:

inform_kit *Kits::find_by_name(text_stream *name, linked_list *nest_list,
    inbuild_requirement *req) {
    if (req == NULL) req = Requirements::any_version_of(Works::new(kit_genre, name, I""));
    inbuild_search_result *R = Nests::search_for_best(req, nest_list);
    if (R == NULL) return NULL;
    inbuild_copy *C = R->copy;
    return KitManager::from_copy(C);
}

§9. The ITTT process for a project calls this to see if the ITTT rules for a K require further kit dependencies to be added to the project: if they do, then the dependencies are added and we return TRUE. If there was nothing to do, we return FALSE.

int Kits::perform_ittt(inform_kit *K, inform_project *project, int parity) {
    int changes_made = FALSE;
    inform_kit_ittt *ITTT;
    LOOP_OVER_LINKED_LIST(ITTT, inform_kit_ittt, K->ittt)
        if ((ITTT->if_included == parity) &&
            (Projects::uses_kit(project, ITTT->then_name) == FALSE) &&
            (Projects::uses_kit(project, ITTT->if_name) == ITTT->if_included)) {
            Projects::add_kit_dependency(project, ITTT->then_name, NULL, K, NULL, NULL);
            changes_made = TRUE;
        }
    return changes_made;
}

§10. Kind definitions. The base kinds for the Inform language, such as "real number" or "text", are not defined in high-level source text, nor by Inter, but by special configuration files held in the kinds subdirectory of the kits used. The following function loads the base kinds in a kit K:

#ifdef CORE_MODULE
void Kits::load_built_in_kind_constructors(inform_kit *K) {
    text_stream *segment;
    LOOP_OVER_LINKED_LIST(segment, text_stream, K->kind_definitions) {
        pathname *P = Pathnames::down(K->as_copy->location_if_path, I"kinds");
        filename *F = Filenames::in(P, segment);
        LOG("Loading kinds definitions from %f\n", F);
        Kits::interpret_neptune(F);
    }
}
#endif

§11. Using this rudimentary interpreter:

#ifdef CORE_MODULE
void Kits::interpret_neptune(filename *neptune_file) {
    FILE *Input_File = NULL;
    int col = 1, cr, lc = 0;
    TEMPORARY_TEXT(heading_name)
    Open a file for input, if necessary11.1;
    TEMPORARY_TEXT(command)
    TEMPORARY_TEXT(argument)
    do {
        Str::clear(command);
        Str::clear(argument);
        Read next character from I6T stream11.2;
        if (cr == EOF) break;
        lc++;
        if ((cr == 10) || (cr == 13)) continue;  skip blank lines here
        Read rest of line as argument11.3;
        if ((Str::get_first_char(argument) == '!') ||
            (Str::get_first_char(argument) == 0)) continue;  skip blanks and comments
        text_file_position tfp = TextFiles::at(neptune_file, lc);
        parse_node *cs = current_sentence;
        current_sentence = NULL;
        NeptuneFiles::read_command(argument, &tfp);
        current_sentence = cs;
    } while (cr != EOF);
    DISCARD_TEXT(command)
    DISCARD_TEXT(argument)
    if (Input_File) { if (DL) STREAM_FLUSH(DL); fclose(Input_File); }
    DISCARD_TEXT(heading_name)
}
#endif

§11.1. Open a file for input, if necessary11.1 =

    if (neptune_file) {
        Input_File = Filenames::fopen(neptune_file, "r");
        if (Input_File == NULL) {
            LOG("Filename was %f\n", neptune_file);
            StandardProblems::unlocated_problem(Task::syntax_tree(),
                _p_(BelievedImpossible),  or anyway not usefully testable
                "I couldn't open a Neptune file for defining built-in kinds.");
        }
    }

§11.2. I6 template files are encoded as ISO Latin-1, not as Unicode UTF-8, so ordinary fgetc is used, and no BOM marker is parsed. Lines are assumed to be terminated with either 0x0a or 0x0d. (Since blank lines are harmless, we take no trouble over 0a0d or 0d0a combinations.) The built-in template files, almost always the only ones used, are line terminated 0x0a in Unix fashion.

Read next character from I6T stream11.2 =

    if (Input_File) cr = fgetc(Input_File);
    else cr = EOF;
    col++; if ((cr == 10) || (cr == 13)) col = 0;

§11.3. We get here when reading a kinds template file. Note that initial and trailing white space on the line is deleted: this makes it easier to lay out I6T template files tidily.

Read rest of line as argument11.3 =

    Str::clear(argument);
    if (Characters::is_space_or_tab((inchar32_t) cr) == FALSE) PUT_TO(argument, (inchar32_t) cr);
    int at_start = TRUE;
    while (TRUE) {
        Read next character from I6T stream11.2;
        if ((cr == 10) || (cr == 13) || (cr == EOF)) break;
        if ((at_start) && (Characters::is_space_or_tab((inchar32_t) cr))) continue;
        PUT_TO(argument, (inchar32_t) cr); at_start = FALSE;
    }
    while (Characters::is_space_or_tab(Str::get_last_char(argument)))
        Str::delete_last_character(argument);

§12. Language element activation. Note that this function is meaningful only when this module is part of the inform7 executable, and it invites us to activate or deactivate language features as K would like.

void Kits::activate_elements(inform_kit *K) {
    element_activation *EA;
    LOOP_OVER_LINKED_LIST(EA, element_activation, K->activations) {
        compiler_feature *P = Features::from_name(EA->element_name);
        if (P == NULL) {
            TEMPORARY_TEXT(err)
            WRITE_TO(err, "kit metadata refers to unknown compiler feature '%S'", EA->element_name);
            Copies::attach_error(K->as_copy, CopyErrors::new_T(METADATA_MALFORMED_CE, -1, err));
            DISCARD_TEXT(err)
        } else {
            if (EA->activate) Features::activate(P);
            else if (Features::deactivate(P) == FALSE) {
                TEMPORARY_TEXT(err)
                WRITE_TO(err, "kit metadata asks to deactivate mandatory compiler feature '%S'",
                    EA->element_name);
                Copies::attach_error(K->as_copy, CopyErrors::new_T(METADATA_MALFORMED_CE, -1, err));
                DISCARD_TEXT(err)
            }
        }
    }
}

§13. Early source. As we have seen, kits can ask for extensions to be included.

As a last resort, a kit can also ask for a sentence or two to be mandatorily included in the source text for any project using it. This text appears very early on, and can't do much, but could for example set use options.

This function simply writes out such sentences, so that they can be fed into the lexer by our caller.

void Kits::early_source_text(OUTPUT_STREAM, inform_kit *K) {
    inbuild_requirement *req;
    LOOP_OVER_LINKED_LIST(req, inbuild_requirement, K->extensions) {
        WRITE("Include ");
        if (VersionNumberRanges::is_any_range(req->version_range) == FALSE) {
            semantic_version_number V = req->version_range->lower.end_value;
            WRITE("version %v of ", &V);
        }
        WRITE("%S by %S.\n\n", req->work->title, req->work->author_name);
    }
    if (K->early_source) WRITE("%S\n\n", K->early_source);
}

linked_list *Kits::inter_paths(linked_list *L) {
    linked_list *inter_paths = NEW_LINKED_LIST(pathname);
    inbuild_nest *N;
    LOOP_OVER_LINKED_LIST(N, inbuild_nest, L)
        ADD_TO_LINKED_LIST(KitManager::path_within_nest(N), pathname, inter_paths);
    return inter_paths;
}

§14. Build graph. The build graph for a kit is quite extensive, since a kit contains Inter binaries for four different architectures; and each of those has a dependency on every section file of the web of Inform 6 source for the kit. If there are \(S\) sections then the graph has \(S+5\) vertices and \(4(S+1)\) edges.

Note that ITTT rules do not affect the build graph; they affect only how a project uses the kit, and therefore they affect the project's build graph but not ours.

void Kits::construct_graph(inform_kit *K) {
    RUN_ONLY_FROM_PHASE(GRAPH_CONSTRUCTION_INBUILD_PHASE)
    if (K == NULL) return;
    inbuild_copy *C = K->as_copy;
    pathname *P = C->location_if_path;
    build_vertex *KV = C->vertex;  the kit vertex
    linked_list *BVL = NEW_LINKED_LIST(build_vertex);  list of vertices for the binaries
    Add build edges to the binaries for each architecture14.1;

    web_md *Wm = WebMetadata::get_without_modules(C->location_if_path, NULL);

    build_vertex *CV = Graphs::file_vertex(Wm->contents_filename);  the contents page vertex
    Add build edges from the binary vertices to the contents vertex14.2;

    Add build edges from the binary vertices to each section vertex14.3;

    inbuild_requirement *req;
    LOOP_OVER_LINKED_LIST(req, inbuild_requirement, K->extensions)
        Kits::add_extension_dependency(KV, req);
}

§14.1. The test for nest protection here ensures that a kit in an internal nest will never be incrementally rebuilt in a normal Inform build process, even if the timestamps on the files look as if it should be. This is important because of the way Linux apps are sandboxed, because a security feature on Linux means that timestamps are deliberately reported incorrectly. In any case, the internal nest shouldn't be written to even on other platforms.

Add build edges to the binaries for each architecture14.1 =

    inter_architecture *A;
    LOOP_OVER(A, inter_architecture)
        if (Compatibility::test_architecture(K->as_copy->edition->compatibility, A)) {
            filename *F = Architectures::canonical_binary(P, A);
            build_vertex *BV = Graphs::file_vertex(F);
            if ((C->nest_of_origin) && (Nests::is_protected(C->nest_of_origin)))
                BV->never_build_this = TRUE;
            else Check the Inter version used any already-existing binary file14.1.1;
            Graphs::need_this_to_build(KV, BV);
            BuildSteps::attach(BV, build_kit_using_inter_skill, FALSE, NULL, A, K->as_copy);
            ADD_TO_LINKED_LIST(BV, build_vertex, BVL);
        }

§14.2. Add build edges from the binary vertices to the contents vertex14.2 =

    build_vertex *BV;
    LOOP_OVER_LINKED_LIST(BV, build_vertex, BVL)
        Graphs::need_this_to_build(BV, CV);

§14.3. Add build edges from the binary vertices to each section vertex14.3 =

    chapter_md *Cm;
    LOOP_OVER_LINKED_LIST(Cm, chapter_md, Wm->chapters_md) {
        section_md *Sm;
        LOOP_OVER_LINKED_LIST(Sm, section_md, Cm->sections_md) {
            filename *SF = Sm->source_file_for_section;
            build_vertex *SV = Graphs::file_vertex(SF);
            build_vertex *BV;
            LOOP_OVER_LINKED_LIST(BV, build_vertex, BVL)
                Graphs::need_this_to_build(BV, SV);
        }
    }

§14.1.1. Suppose the user has an old kit lying around, and it has been pre-built but back in the days of Inter version 17.1 when we're now in the brave new world of Inter 19.3. Because the source files for the kit are even older than its old binary form, the datestamps will not cause the kit to be rebuilt. Inform will then try to load the binary, and fail because its version is out of date. We want to avoid all that by forcing a rebuild, regardless of the source datestamps, if (a) the binary exists but (b) it uses an obsolete Inter version.

Check the Inter version used any already-existing binary file14.1.1 =

    #ifdef BYTECODE_MODULE
    semantic_version_number V = BinaryInter::test_file_version(F);
    if ((VersionNumbers::is_null(V) == FALSE) &&
        (InterVersion::check_readable(V) == FALSE)) {
        semantic_version_number current_version = InterVersion::current();
        LOG("Forcing rebuild of '%f' because it uses Inter v%v, and we want %v\n",
            F, &V, &current_version);
        BV->always_build_this = TRUE;
    }
    #endif

§15. Suppose our kit wants to include Locksmith by Emily Short. If that's an extension we have already read in, we can place a use edge to its existing build vertex. If not, the best we can do is a use edge to a requirement vertex, i.e., to a vertex meaning "we would like Locksmith but can't find it".

void Kits::add_extension_dependency(build_vertex *KV, inbuild_requirement *req) {
    build_vertex *RV = NULL;
    inform_extension *E;
    LOOP_OVER(E, inform_extension)
        if (Requirements::meets(E->as_copy->edition, req)) {
            RV = E->as_copy->vertex;
            build_vertex *V;
            int N = 0;
            LOOP_OVER_LINKED_LIST(V, build_vertex, KV->use_edges) {
                if ((V->type == REQUIREMENT_VERTEX) &&
                    (Requirements::meets(E->as_copy->edition, V->as_requirement))) {
                    LinkedLists::delete(N, KV->use_edges);
                    break;
                }
                N++;
            }
            break;
        }
    if (RV == NULL) {
        build_vertex *V;
        int N = 0;
        LOOP_OVER_LINKED_LIST(V, build_vertex, KV->use_edges) {
            if ((V->type == REQUIREMENT_VERTEX) &&
                (Requirements::trumps(req, V->as_requirement))) {
                LinkedLists::delete(N, KV->use_edges);
                break;
            }
            N++;
        }
        RV = Graphs::req_vertex(req);
    }
    Graphs::need_this_to_use(KV, RV);
}

§16. We can find a configuration symbol as used by a kit, returning TRUE if it is a flag, FALSE if a value, and NOT_APPLICABLE if it doesn't exist for the kit:

int Kits::configuration_is_a_flag(inform_kit *kit, text_stream *name) {
    kit_configuration *kc;
    LOOP_OVER_LINKED_LIST(kc, kit_configuration, kit->configurations)
        if (Str::eq_insensitive(kc->symbol_name, name))
            return kc->is_flag;
    return NOT_APPLICABLE;
}