An overview of the building module's role and abilities.


§1. Prerequisites. The building module is a part of the Inform compiler toolset. It is presented as a literate program or "web". Before diving in:

§2. Introduction. This module is essentially middleware. It acts as a bridge to the low-level functions in the bytecode module, allowing them to be used with much greater ease and consistency.

This module needs plenty of working data, and stashes that data inside the inter_tree structure it is working on: in a component of that structure called a building_site. Whereas the main data in an inter_tree affects the meaning of the tree, i.e., makes a difference as to what program the tree represents, the contents of the building_site component are only used to make it, and are ignored by the final code-generator.

§3. Large-scale architecture. An inter tree is fundamentally a set of resources stored in a nested set of inter_package boxes.

See Large-Scale Structure for the code which builds all of the above packages (though not their contents).

§4. Inter code is a nested tree of boxes, inter_packages, which contain Inter code defining various resources, cross-referenced by inter_symbols.

But this tree cannot be magically made all at once. For much of the run of a tool like inform7, a partly-built tree will exist, and this introduces many potential race conditions — where, for example, a call to function F cannot be made until F itself has been made, and so on.

We also want to avoid bugs where one part of the compiler thinks that F will live in one place, and another part thinks it is somewhere else.

To that end, we use a flexible way to describe naming and positioning conventions for Inter resources (such as our hypothetical F). In this system, a package_request stands for a package which may or may not already exist; and an inter_name, similarly, is a symbol which may or may not exist yet. This enables tools like inform7 to build up elaborate if shadowy worlds of references to tree positions which will be filled in later.

                DEFINITELY MADE     PERHAPS NOT YET MADE
    PACKAGE     inter_package       //package_request//
    SYMBOL      inter_symbol        //inter_name//

So, for example, a package_request can represent /main/synoptic/kinds either before or after that package has been built. At some point the package ceases to be virtual and comes into being: this is called "incarnation". But code in inform7 using package requests never needs to know when this takes place, and will function equally well before or after — so, no race conditions.

And similarly for inter_name, which it would perhaps be more consistent to call a symbol_request. But "iname" is now a term used almost ubiquitously across inform7 and inter, and it doesn't seem worth renaming it now.

§5. Medium-scale blueprints. The above systems make nested packages and symbols within them, but not the actual content of these boxes, or the definitions which the symbols refer to. In short, the actual Inter code.

The straightforward way to compile some Inter code is to make calls to functions in Producing Inter, which provide a straightforward if low-level API. For example:

    inter_name *iname = HierarchyLocations::iname(I, CCOUNT_PROPERTY_HL);
    Produce::numeric_constant(I, iname, K_value, x);

Note that we do not need to say where this code will go. Producing Inter looks at the iname, works out what package request it should go into, incarnates that into a real inter_package if necessary, then incarnates the iname into a real inter_symbol if necessary; and finally emits a CONSTANT_IST in the relevant package, an instruction which defines the symbol.

And similarly for emitting code inside a function body, though then it is necessary first to say what function (which can be done by calling Produce::function_body with the iname for that function). For example:

    Produce::inv_primitive(I, RETURN_BIP);
    Produce::down(I);
        Produce::val(I, K_value, InterValuePairs::number(1));
    Produce::up(I);

§6. But that is a laborious sort of notation for what, in a C-like language, would be written just as return 1. It would be very painful to have to implement kits such as BasicInformKit that way. Instead, we write them in a notation which is very close indeed2 to Inform 6 syntax.3

This means we need to provide what amounts to a pocket Inform-6-to-Inter compiler, and we do that in this module, using a data structure called an inter_schema — in effect, an annotated syntax tree — to represent the results of parsing Inform 6 notation. For example, this:

    inter_schema *sch = ParsingSchemas::from_text(I"return true;", where);
    EmitInterSchemas::emit(I, ..., sch, ...);

generates Inter code equivalent to the example above.4 But the real power of the system comes from:

As an example of (b), an inter_schema is how inform7 compiles so-called inline phrase definitions such as:

    To say (L - a list of values) in brace notation:
        (- LIST_OF_TY_Say({-by-reference:L}, 1); -).

Here, the text LIST_OF_TY_Say({-by-reference:L}, 1); is passed through to ParsingSchemas::from_text to make a schema. When the phrase is invoked, EmitInterSchemas::emit is used to generate Inter code from it; and a reference to the list passed to the invocation as the token L is substituted for the braced clause {-by-reference:L}.5 Schemas are also used as convenient shorthand in the compiler to express how to, for example, post-increment a property value.

§7. Small-scale masonry. Finally, there are also times when we want to compile explicit code, one Inter instruction at a time, and for this the Produce API is provided.

This API keeps track of the current write position inside each tree (using the code_insertion_point system), and then provides functions which call down into bytecode for us, making use of that write position. So, for example, we can write:

    Produce::inv_primitive(I, RETURN_BIP);
    Produce::down(I);
        Produce::val(I, K_value, InterValuePairs::number(17));
    Produce::up(I);

to produce the Inter code:

    inv !return
        val K_unchecked 17

Note the use of Produce::down and Produce::up to step up and down the hierarchy: these functions are always called in matching ways.

§8. The pipeline module makes heavy use of the Produce API. Surprising, inform7 calls it in only a few places — but in fact that is because it provides still another middleware layer on top. See Emit (in runtime). But it's really only a very thin layer, allowing the caller not to have to pass the I argument to every call (because it will always be the Inter tree being compiled by inform7). Despite appearances, then, Produce makes all of the Inter instructions generated inside either inter or inform7.