Location and naming rules for resources to be compiled in an Inter hierarchy.


§1. Hierarchy locations. A compiler such as inform7 needs to create many different resources — arrays, functions and so on — and each one needs to be placed somewhere in the hierarchy of an Inter tree, and given a name.

A hierarchy_location is an abstract way to specify the rules for doing that. The compiler creates a mass of these objects, and they are then indexed by unique IDs, their access_numbers. The compiler can then, with essentially no overhead, ask to create the resource with ID JINXED_WIZARDS_HL (say), and the machinery below will work out where it is to go, and what it is to be called — /synoptic/pangrams/jinxed_wizards_fn, say.

This leads to greater consistency, less duplication of book-keeping code, and the ability to have a sort of registry of where everything will go: see Hierarchy (in runtime) for an example.

But it's also helpful when trying to use these resources. If the compiler needs to make a function call to our hypothetical jinxed-wizards function, it can simply use HierarchyLocations::iname(JINXED_WIZARDS_HL) to obtain an inter_name for where that function is (if it already exists) or will be (if not).

typedef struct hierarchy_location {
    int access_number;
    struct text_stream *access_name;
    struct text_stream *function_package_name;
    struct text_stream *datum_package_name;
    struct location_requirement requirements;
    struct inter_name *equates_to_iname;
    struct text_stream *package_type;
    struct name_translation trans;
    CLASS_DEFINITION
} hierarchy_location;

hierarchy_location *HierarchyLocations::new(int id, location_requirement req) {
    hierarchy_location *hl = CREATE(hierarchy_location);
    hl->access_number = id;
    hl->access_name = NULL;
    hl->function_package_name = NULL;
    hl->datum_package_name = NULL;
    hl->equates_to_iname = NULL;
    hl->package_type = NULL;
    hl->trans = Translation::same();
    hl->requirements = req;
    return hl;
}

§2. We provide an API of five creator functions for HLs. First, for a resource like a constant1 (possibly translated in some way: see Translation), and which must be located at position req.

hierarchy_location *HierarchyLocations::ctr(inter_tree *I, int id, text_stream *name,
    name_translation nt, location_requirement req) {
    hierarchy_location *hl = HierarchyLocations::new(id, req);
    hl->access_name = Str::duplicate(name);
    hl->trans = nt;
    HierarchyLocations::index(I, hl);
    return hl;
}

§3. Second, the same thing but with no translation needed:

hierarchy_location *HierarchyLocations::con(inter_tree *I, int id, text_stream *name,
    location_requirement req) {
    return HierarchyLocations::ctr(I, id, name, Translation::same(), req);
}

§4. Third, for a function, possibly translated in some way, again at req:

hierarchy_location *HierarchyLocations::fun(inter_tree *I, int id, text_stream *name,
    name_translation nt, location_requirement req) {
    hierarchy_location *hl = HierarchyLocations::new(id, req);
    hl->access_name = Str::duplicate(nt.translate_to);
    hl->function_package_name = Str::duplicate(name);
    hl->trans = nt;
    HierarchyLocations::index(I, hl);
    return hl;
}

§5. Fourth, for a package of a given type, which must live at req:

hierarchy_location *HierarchyLocations::pkg(inter_tree *I, int id, text_stream *name,
    text_stream *ptype_name, location_requirement req) {
    hierarchy_location *hl = HierarchyLocations::new(id, req);
    hl->access_name = Str::duplicate(name);
    hl->package_type = Str::duplicate(ptype_name);
    HierarchyLocations::index(I, hl);
    return hl;
}

§6. Finally, for a datum package:

hierarchy_location *HierarchyLocations::dat(inter_tree *I, int id, text_stream *name,
    name_translation nt, location_requirement req) {
    hierarchy_location *hl = HierarchyLocations::new(id, req);
    hl->access_name = Str::duplicate(nt.translate_to);
    hl->datum_package_name = Str::duplicate(name);
    hl->trans = nt;
    HierarchyLocations::index(I, hl);
    return hl;
}

§7. Finding HLs by ID or name. As noted above, HLs are identified with ID numbers for speed, and this means that an array must be maintained so that the hierarchy_location for a given ID can be found quickly. We also sometimes want to look them up by name, which is slower, but the hashing used by a dictionary makes even that tolerable.

Both of these forms of lookup need an index to be kept, so HLs must be registered for use with any tree which will need them, using the following.

Note that HLs for plugs all have the ID -1, so those are indexed only by name.

void HierarchyLocations::index(inter_tree *I, hierarchy_location *hl) {
    int id = hl->access_number;
    if ((id >= 0) && (id < NO_DEFINED_HL_VALUES))
        I->site.shdata.HLs_indexed_by_id[id] = hl;
    if (hl->requirements.any_package_of_this_type == NULL) {
        Dictionaries::create(I->site.shdata.HLs_indexed_by_name, hl->access_name);
        Dictionaries::write_value(I->site.shdata.HLs_indexed_by_name,
            hl->access_name, (void *) hl);
    }
}

hierarchy_location *HierarchyLocations::id_to_HL(inter_tree *I, int id) {
    if ((id < 0) || (id >= NO_DEFINED_HL_VALUES)) internal_error("HL ID out of range");
    if (I->site.shdata.HLs_indexed_by_id[id] == NULL) internal_error("undeclared HL ID");
    return I->site.shdata.HLs_indexed_by_id[id];
}

hierarchy_location *HierarchyLocations::name_to_HL(inter_tree *I, text_stream *name) {
    if (Str::len(name) == 0) internal_error("empty HL name");
    if (Dictionaries::find(I->site.shdata.HLs_indexed_by_name, name) == NULL)
        return NULL;
    return (hierarchy_location *)
        Dictionaries::read_value(I->site.shdata.HLs_indexed_by_name, name);
}

§8. Finding HLs representing one-off global resources. The two functions here allow the client to get an iname for a resource which occurs just once in the whole repository.

For example, HierarchyLocations::iname(I, UN_BUILDING_HL) might return the iname /main/generic/UN_building. The package location, here /main/generic, and the name, here UN_building, would both be specified explicitly in the HL. So this is the simplest case.

In response to these requests, we actually never return the hierarchy_location itself; our users don't care about that. We return the inter_name for the resource in question.

inter_name *HierarchyLocations::iname(inter_tree *I, int id) {
    hierarchy_location *hl = HierarchyLocations::id_to_HL(I, id);
    Work out the iname for this HL8.1;
}

inter_name *HierarchyLocations::name_to_iname(inter_tree *I, text_stream *name) {
    hierarchy_location *hl = HierarchyLocations::name_to_HL(I, name);
    if (hl == NULL) return NULL;
    Work out the iname for this HL8.1;
}

§8.1. The result of the following is cached in hl->equates_to_iname, so that it only needs to be worked out once.

Work out the iname for this HL8.1 =

    if (hl->requirements.any_package_of_this_type)
        internal_error("this must be found with HierarchyLocations::iip");

    if (hl->equates_to_iname) return hl->equates_to_iname;

    if (hl->requirements.must_be_plug) Then make it a plug8.1.1;

    package_request *pack;
    Work out and request the package to make this in8.1.2;
    Make the iname inside this package8.1.3;

§8.1.1. Then make it a plug8.1.1 =

    hl->equates_to_iname = InterNames::explicitly_named_plug(I, hl->access_name);
    return hl->equates_to_iname;

§8.1.2. Work out and request the package to make this in8.1.2 =

    pack = hl->requirements.this_exact_package;
    if (hl->requirements.this_exact_package_not_yet_created >= 0)
        Choose an exotic package instead8.1.2.1;
    if (pack == NULL) internal_error("package can't be found");

§8.1.2.1. Choose an exotic package instead8.1.2.1 =

    #ifdef CORE_MODULE
    pack = Hierarchy::exotic_package(hl->requirements.this_exact_package_not_yet_created);
    if (pack == NULL) internal_error("unable to determine package and therefore iname");
    #endif
    #ifndef CORE_MODULE
    internal_error("exotic packages are not available in inter");
    #endif

§8.1.3. Make the iname inside this package8.1.3 =

    if (Str::len(hl->function_package_name) > 0) {
        hl->equates_to_iname = Packaging::function(I,
            InterNames::explicitly_named(hl->function_package_name, pack),
            hl->access_name);
    } else if (Str::len(hl->datum_package_name) > 0) {
        hl->equates_to_iname = Packaging::datum_text(I,
            InterNames::explicitly_named(hl->datum_package_name, pack),
            hl->access_name);
    } else {
        hl->equates_to_iname = InterNames::explicitly_named(hl->access_name, pack);
    }

    if (hl->trans.translate_to)
        InterNames::set_translation(hl->equates_to_iname, hl->trans.translate_to);

    return hl->equates_to_iname;

§9. Finding HLs representing resources in families of packages. Suppose we want to have packages representing, say, South American countries, and the compiler wants to create a constant capital_city in each of these packages.

There will be a single HL supplying the rules to do this, but multiple inames will be produced as the compiler makes multiple calls:

    ... HierarchyLocations::make_iname_in(I, CAPITAL_CITY_HL, uruguay_package) ...
    ... HierarchyLocations::make_iname_in(I, CAPITAL_CITY_HL, peru_package) ...
    ... HierarchyLocations::make_iname_in(I, CAPITAL_CITY_HL, chile_package) ...
inter_name *HierarchyLocations::make_iname_in(inter_tree *I, int id, package_request *P) {
    return HierarchyLocations::iip(I, id, P, EMPTY_WORDING, NULL, -1, NULL,
        DEFAULT_INAME_TRUNCATION);
}

§10. That might, say, produce constants with the Inter symbols like so:

    /main/south_america/uruguay/capital_city
    /main/south_america/peru/capital_city
    /main/south_america/chile/capital_city

When final code is generated from these, the constants will probably end up with bland identifiers like capital_city_U1, capital_city_U2, and so on. If we don't want that, we can make an exception like so:

    ... HierarchyLocations::make_iname_in(I, CAPITAL_CITY_HL, uruguay_package) ...
    ... HierarchyLocations::make_iname_with_specific_translation(I, CAPITAL_CITY_HL,
        I"Lima", peru_package) ...
    ... HierarchyLocations::make_iname_in(I, CAPITAL_CITY_HL, chile_package) ...

Our three capitals would then translate to capital_city_U1, Lima, and capital_city_U2.

inter_name *HierarchyLocations::make_iname_with_specific_translation(inter_tree *I, int id,
    text_stream *translation, package_request *P) {
    return HierarchyLocations::iip(I, id, P, EMPTY_WORDING, NULL, -1, translation,
        DEFAULT_INAME_TRUNCATION);
}

§11. Sometimes we want the name itself to be more meaningful, or at least, more legible when Inter code is printed out. We can do that by attaching a "memo" of wording to its name. For example, if W is the wording "Uruguay", then calling:

    HierarchyLocations::make_iname_with_memo(I, COUNTRY_HL, uruguay_package, W)

might produce the iname /main/south_america/uruguay/C3_uruguay; a subsequent call in a different package, with a different wording, might then produce /main/south_america/uruguay/C4_trinidad_and_tobago, and so on. (The choice of C as the prefix would be made in the HL, which specifies naming conventions.)

inter_name *HierarchyLocations::make_iname_with_memo(inter_tree *I, int id,
    package_request *P, wording W) {
    return HierarchyLocations::iip(I, id, P, W, NULL, -1, NULL,
        DEFAULT_INAME_TRUNCATION);
}
inter_name *HierarchyLocations::make_iname_with_shorter_memo(inter_tree *I, int id,
    package_request *P, wording W) {
    return HierarchyLocations::iip(I, id, P, W, NULL, -1, NULL,
        DEFAULT_INAME_TRUNCATION - 5);
}

§12. Note that the HL, in this example COUNTRY_HL, keeps track of how many of these inames it has made, so that it can increment the index number — in those two cases, 3 and then 4. If we need to override this with a specific number x for some reason, we can use this variant:

inter_name *HierarchyLocations::make_iname_with_memo_and_value(inter_tree *I,
    int id, package_request *P, wording W, int x) {
    return HierarchyLocations::iip(I, id, P, W, NULL, x, NULL,
        DEFAULT_INAME_TRUNCATION);
}

§13. Finally, it's often useful to "derive" a name: to say that a resource in a given package P should have a name based on the name of an existing iname. For example, the HL POPULATION_HL might have the rule that names are made by suffixing _POP to an existing iname. Calling HierarchyLocations::derive_iname_in might then produce a name like C3_uruguay_POP, derived from the existing iname C3_uruguay.

inter_name *HierarchyLocations::derive_iname_in(inter_tree *I, int id, inter_name *from,
    package_request *P) {
    return HierarchyLocations::iip(I, id, P, EMPTY_WORDING, from, -1, NULL,
        DEFAULT_INAME_TRUNCATION);
}

§14. And this variant form ensures that any translation already made to from is transferred to a similarly derived translation name for the result.

inter_name *HierarchyLocations::derive_iname_in_translating(inter_tree *I, int id,
    inter_name *from, package_request *P) {
    inter_name *iname = HierarchyLocations::iip(I, id, P, EMPTY_WORDING, from, -1, NULL,
        DEFAULT_INAME_TRUNCATION);
    TEMPORARY_TEXT(F)
    WRITE_TO(F, "%n", from);
    if (Str::ne(F, InterNames::get_translation(from))) {
        hierarchy_location *hl = HierarchyLocations::id_to_HL(I, id);
        if ((hl->trans.name_generator) && (from)) {
            TEMPORARY_TEXT(T)
            WRITE_TO(T, "%S", InterNames::get_translation(from));
            TEMPORARY_TEXT(TT)
            Str::truncate(T,
                30  - Str::len(hl->trans.name_generator->derived_prefix)
                    - Str::len(hl->trans.name_generator->derived_suffix));
            WRITE_TO(TT, "%S%S%S",
                hl->trans.name_generator->derived_prefix,
                T,
                hl->trans.name_generator->derived_suffix);
            InterNames::set_translation(iname, Str::duplicate(TT));
            DISCARD_TEXT(T)
            DISCARD_TEXT(TT)
        }
    }
    DISCARD_TEXT(F)
    return iname;
}

§15. All of the above use this command back-end:

inter_name *HierarchyLocations::iip(inter_tree *I, int id, package_request *P,
    wording W, inter_name *derive_from, int fix, text_stream *imposed_name,
    int truncation) {
    hierarchy_location *hl = HierarchyLocations::id_to_HL(I, id);

    Verify that the proposed package P meets requirements15.1;

    inter_name *iname = NULL;
    if (hl->trans.translate_to)  {
        text_stream *T = hl->trans.translate_to;
        Make the actual iname15.2;
    } else if (hl->trans.by_imposition) {
        text_stream *T = NULL;
        Make the actual iname15.2;
    } else if (hl->trans.name_generator) {
        TEMPORARY_TEXT(T)
        inter_name *temp_iname =
            (derive_from) ? InterNames::derived(hl->trans.name_generator, derive_from, W)
                          : InterNames::generated(hl->trans.name_generator, fix, W);
        W = EMPTY_WORDING;
        WRITE_TO(T, "%n", temp_iname);
        Make the actual iname15.2;
        DISCARD_TEXT(T)
    } else {
        text_stream *T = NULL;
        Make the actual iname15.2;
    }

    if (hl->trans.then_make_unique) InterNames::set_flag(iname, MAKE_NAME_UNIQUE_ISYMF);
    return iname;
}

§15.1. We do nothing to change matters here. But an HL can specify that it may only be used to generate inames in, say, a package of type _country: and an internal error is thrown if this is violated. So compliance is not automatic, but it is at least policed.

Verify that the proposed package P meets requirements15.1 =

    if ((hl->requirements.any_package_of_this_type == NULL) &&
        (hl->requirements.any_enclosure == FALSE))
        internal_error("this must be found with HierarchyLocations::iname");

    if (hl->requirements.any_enclosure) {
        if (LargeScale::package_type_enclosing(P->eventual_type) == FALSE)
            internal_error("subpackage not in enclosing superpackage");
    } else if (P == NULL) {
        internal_error("iname in null package");
    } else if (P->eventual_type !=
        LargeScale::package_type(I, hl->requirements.any_package_of_this_type)) {
        LOG("Access name: %S, function: %S\n",
            hl->access_name, hl->function_package_name);
        LOG("Have type: $3, required: %S\n",
            P->eventual_type, hl->requirements.any_package_of_this_type);
        internal_error("iname in superpackage of the wrong type");
    }

§15.2. Make the actual iname15.2 =

    if (Str::len(hl->function_package_name) > 0) {
        iname = Packaging::function(I,
            InterNames::explicitly_named(hl->function_package_name, P), NULL);
    } else if (hl->trans.by_imposition) {
        iname = InterNames::explicitly_named_with_memo(imposed_name, P, W, truncation);
    } else if (Str::len(hl->access_name) == 0) {
        iname = InterNames::explicitly_named_with_memo(T, P, W, truncation);
    } else {
        iname = InterNames::explicitly_named_with_memo(hl->access_name, P, W, truncation);
    }
    if ((Str::len(T) > 0) && (hl->access_name)) InterNames::set_translation(iname, T);

§16. Making one-off subpackages. This is used very little. (In inform7, currently only for making the packages holding built-in activity or action rulebooks.)

package_request *HierarchyLocations::subpackage(inter_tree *I, int id, package_request *P) {
    hierarchy_location *hl = HierarchyLocations::id_to_HL(I, id);

    if (P == NULL) internal_error("no superpackage");
    if (hl->package_type == NULL) internal_error("HL does not specify a type");
    if (hl->requirements.any_package_of_this_type) {
        if (P->eventual_type !=
            LargeScale::package_type(I, hl->requirements.any_package_of_this_type))
            internal_error("subpackage in superpackage of wrong type");
    } else if (hl->requirements.any_enclosure) {
        if (LargeScale::package_type_enclosing(P->eventual_type) == FALSE)
            internal_error("subpackage not in enclosing superpackage");
    } else internal_error("HL does not call for a package");

    return Packaging::request(I,
        InterNames::explicitly_named(hl->access_name, P),
        LargeScale::package_type(I, hl->package_type));
}

§17. Making packages systematically at attachment points. This is used a great deal. Instead of making a single iname, or a single package, we want to make a family of packages, sequentially numbered in some way, at a given position in the hierarchy. Such families are created at "hierarchy attachment points", and the process of adding another package to the family is called "attachment".

Like HLs, HAPs are identified by number, but this is a different and independent numbering system.

typedef struct hierarchy_attachment_point {
    int hap_id;
    struct text_stream *name_stem;
    struct text_stream *type;
    struct location_requirement requirements;
    CLASS_DEFINITION
} hierarchy_attachment_point;

§18. Once again, these are indexed for speedy retrieval by ID number:

hierarchy_attachment_point *HierarchyLocations::att(inter_tree *I, int id,
    text_stream *stem, text_stream *ptype_name, location_requirement req) {
    if ((id < 0) || (id >= NO_DEFINED_HAP_VALUES)) internal_error("HAP ID out of range");
    hierarchy_attachment_point *hap = CREATE(hierarchy_attachment_point);
    hap->hap_id = id;
    hap->requirements = req;
    hap->name_stem = Str::duplicate(stem);
    hap->type = Str::duplicate(ptype_name);
    I->site.shdata.HAPs_indexed_by_id[hap->hap_id] = hap;
    return hap;
}

hierarchy_attachment_point *HierarchyLocations::id_to_HAP(inter_tree *I, int id) {
    if ((id < 0) || (id >= NO_DEFINED_HAP_VALUES)) internal_error("HAP ID out of range");
    if (I->site.shdata.HAPs_indexed_by_id[id] == NULL) internal_error("undeclared HAP ID");
    return I->site.shdata.HAPs_indexed_by_id[id];
}

§19. The API is now very simple. HierarchyLocations::attach_new_package(I, M, R, id) attaches another package. R and M need only be specified if the location requirements of the HAL do not imply a definite position already; M is meaningful only if the requirements are to put everything in a submodule of a given module, and then M is that module. For example:

    vf_req = Hierarchy::package_within(I, NULL, R, VERB_FORMS_HAP);

attaches a new verb form package inside R. VERB_FORMS_HAP has already been declared with HierarchyLocations::att, and has stem "form" and type "_verb_form". So the outcome might be a package called, say, form_16 of type _verb_form inside of R; and then on the next call form_17, and so on.

package_request *HierarchyLocations::attach_new_package(inter_tree *I,
    module_request *M, package_request *R, int hap_id) {
    hierarchy_attachment_point *hap = HierarchyLocations::id_to_HAP(I, hap_id);
    if (hap->requirements.any_submodule_package_of_this_identity) {
        if (M) R = LargeScale::request_submodule_of(I, M,
                       hap->requirements.any_submodule_package_of_this_identity);
        else   R = LargeScale::generic_submodule(I,
                       hap->requirements.any_submodule_package_of_this_identity);
    } else if (hap->requirements.this_exact_package) {
        R = hap->requirements.this_exact_package;
    } else if (hap->requirements.this_exact_package_not_yet_created >= 0) {
        #ifdef CORE_MODULE
        R = Hierarchy::exotic_package(hap->requirements.this_exact_package_not_yet_created);
        #endif
        #ifndef CORE_MODULE
        internal_error("exotic packages are not available in inter");
        #endif
    } else if (hap->requirements.any_package_of_this_type) {
        if ((R == NULL) ||
            (R->eventual_type != LargeScale::package_type(I,
                hap->requirements.any_package_of_this_type)))
            internal_error("subpackage in wrong superpackage");
    }

    return Packaging::request(I,
        Packaging::make_iname_within(R, hap->name_stem),
        LargeScale::package_type(I, hap->type));
}

§20. Bookkeeping. The following is a little clumsily defined to allow for the possibility that this code is being compiled within a tool which defines no HLs or HAPs.

typedef struct site_hierarchy_data {
    struct dictionary *HLs_indexed_by_name;
    #ifndef NO_DEFINED_HL_VALUES
    #define NO_DEFINED_HL_VALUES 1
    #endif
    struct hierarchy_location *HLs_indexed_by_id[NO_DEFINED_HL_VALUES];
    #ifndef NO_DEFINED_HAP_VALUES
    #define NO_DEFINED_HAP_VALUES 1
    #endif
    struct hierarchy_attachment_point *HAPs_indexed_by_id[NO_DEFINED_HAP_VALUES];
} site_hierarchy_data;

void HierarchyLocations::clear_site_data(inter_tree *I) {
    building_site *B = &(I->site);
    B->shdata.HLs_indexed_by_name = Dictionaries::new(512, FALSE);
    for (int i=0; i<NO_DEFINED_HL_VALUES; i++) B->shdata.HLs_indexed_by_id[i] = NULL;
    for (int i=0; i<NO_DEFINED_HAP_VALUES; i++) B->shdata.HAPs_indexed_by_id[i] = NULL;
}

§21. Finding inames by name.

inter_name *HierarchyLocations::find_by_name(inter_tree *I, text_stream *name) {
    if (Str::len(name) == 0) internal_error("empty extern");
    inter_name *try = HierarchyLocations::name_to_iname(I, name);
    if (try == NULL) {
        HierarchyLocations::con(I, -1, name, LocationRequirements::plug());
        try = HierarchyLocations::name_to_iname(I, name);
    }
    return try;
}

inter_name *HierarchyLocations::find_by_implied_name(inter_tree *I, text_stream *name,
    text_stream *from_namespace) {
    if (Str::len(name) == 0) internal_error("empty extern");
    inter_name *try = HierarchyLocations::name_to_iname(I, name);
    if (try == NULL) {
        TEMPORARY_TEXT(N)
        WRITE_TO(N, "implied`%S`%S", from_namespace, name);
        HierarchyLocations::con(I, -1, N, LocationRequirements::plug());
        try = HierarchyLocations::name_to_iname(I, N);
        DISCARD_TEXT(N)
    }
    return try;
}