A plugin providing support for grouping rooms together into named and nestable regions.


§1. Definitions.

§2. The relation "R in S" behaves so differently for regions that we need to define it separately, even though there's no difference in English syntax. So:

    binary_predicate *R_regional_containment = NULL;

§3. "Region" is in fact one of the four top-level kinds in the standard I7 hierarchy, alongside thing, room and direction.

    kind *K_region = NULL;
    inference_subject *infs_region = NULL;
    property *P_map_region = NULL;     a value property giving the region of a room
    property *P_regional_found_in = NULL;     an I6-only property used for implementation

§4. Every inference subject contains a pointer to its own unique copy of the following minimal structure, though it will only be relevant for instances of "room":

    typedef struct regions_data {
        struct instance *in_region;     smallest region containing me (rooms only)
        struct parse_node *in_region_set_at;     where this is decided
        struct inter_name *in_region_iname;     for testing regional containment found-ins
        MEMORY_MANAGEMENT
    } regions_data;

The structure regions_data is private to this section.

§5. Initialising.

    void PL::Regions::start(void) {
        PLUGIN_REGISTER(PLUGIN_NEW_BASE_KIND_NOTIFY, PL::Regions::regions_new_base_kind_notify);
        PLUGIN_REGISTER(PLUGIN_SET_SUBKIND_NOTIFY, PL::Regions::regions_set_subkind_notify);
        PLUGIN_REGISTER(PLUGIN_NEW_SUBJECT_NOTIFY, PL::Regions::regions_new_subject_notify);
        PLUGIN_REGISTER(PLUGIN_NEW_PROPERTY_NOTIFY, PL::Regions::regions_new_property_notify);
        PLUGIN_REGISTER(PLUGIN_COMPLETE_MODEL, PL::Regions::regions_complete_model);
        PLUGIN_REGISTER(PLUGIN_MORE_SPECIFIC, PL::Regions::regions_more_specific);
        PLUGIN_REGISTER(PLUGIN_ESTIMATE_PROPERTY_USAGE, PL::Regions::regions_estimate_property_usage);
        PLUGIN_REGISTER(PLUGIN_INTERVENE_IN_ASSERTION, PL::Regions::regions_intervene_in_assertion);
        PLUGIN_REGISTER(PLUGIN_NAME_TO_EARLY_INFS, PL::Regions::regions_name_to_early_infs);
        PLUGIN_REGISTER(PLUGIN_ADD_TO_WORLD_INDEX, PL::Regions::regions_add_to_World_index);
        infs_region = InferenceSubjects::new(global_constants,
            FUND_SUB, NULL_GENERAL_POINTER, LIKELY_CE);
    }

    regions_data *PL::Regions::new_data(inference_subject *subj) {
        regions_data *rd = CREATE(regions_data);
        rd->in_region = NULL;
        rd->in_region_set_at = NULL;
        rd->in_region_iname = NULL;
        return rd;
    }

    inter_name *PL::Regions::found_in_iname(instance *I) {
        if (PF_I(regions, I)->in_region_iname == NULL)
            PF_I(regions, I)->in_region_iname = Hierarchy::make_iname_in(REGION_FOUND_IN_FN_HL, Instances::package(I));
        return PF_I(regions, I)->in_region_iname;
    }

The function PL::Regions::start appears nowhere else.

The function PL::Regions::new_data is used in §7.

The function PL::Regions::found_in_iname is used in §19.2, §20.

§6. Kinds. This a kind name to do with regions which Inform provides special support for; it recognises the English name when defined by the Standard Rules. (So there is no need to translate this to other languages.)

    <notable-regions-kinds> ::=
        region

§7.

    int PL::Regions::regions_new_base_kind_notify(kind *new_base, char *text_stream, wording W) {
        if (<notable-regions-kinds>(W)) {
            K_region = new_base; return TRUE;
        }
        return FALSE;
    }

    int PL::Regions::regions_new_subject_notify(inference_subject *subj) {
        CREATE_PF_DATA(regions, subj, PL::Regions::new_data);
        return FALSE;
    }

The function PL::Regions::regions_new_base_kind_notify is used in §5.

The function PL::Regions::regions_new_subject_notify is used in §5.

§8. Region needs to be an abstract object, not a thing or a room, so:

    int PL::Regions::regions_set_subkind_notify(kind *sub, kind *super) {
        if ((sub == K_region) && (super != K_object)) {
            if (problem_count == 0)
                Problems::Issue::sentence_problem(Task::syntax_tree(), _p_(PM_RegionAdrift),
                    "'region' is not allowed to be a kind of anything (other than "
                    "'object')",
                    "because it's too fundamental to the way Inform maps out the "
                    "geography of the physical world.");
            return TRUE;
        }
        return FALSE;
    }

The function PL::Regions::regions_set_subkind_notify is used in §5.

§9.

    int PL::Regions::object_is_a_region(instance *I) {
        if ((Plugins::Manage::plugged_in(regions_plugin)) && (K_region) && (I) &&
            (Instances::of_kind(I, K_region))) return TRUE;
        return FALSE;
    }

The function PL::Regions::object_is_a_region is used in §18, 3/sm (§32.2), 3/em (§11, §22.4.1).

§10. Rooms are always more specific than regions; if region X is within region Y, then X is more specific than Y; otherwise any two regions are equally specific. (This is used in sorting rules by the specificity of their domains.)

    int PL::Regions::regions_more_specific(instance *I1, instance *I2) {
        int r1 = Instances::of_kind(I1, K_room);
        int r2 = Instances::of_kind(I2, K_room);
        int reg1 = Instances::of_kind(I1, K_region);
        int reg2 = Instances::of_kind(I2, K_region);
        if ((r1) && (reg2)) return 1;
        if ((reg1) && (r2)) return -1;
        if ((reg1) && (reg2)) {
            if (PL::Spatial::encloses(I1, I2)) return 1;
            if (PL::Spatial::encloses(I2, I1)) return -1;
        }
        return 0;
    }

The function PL::Regions::regions_more_specific is used in §5.

§11. Properties. This is a property name to do with regions which Inform provides special support for; it recognises the English name when it is defined by the Standard Rules. (So there is no need to translate this to other languages.)

    <notable-regions-properties> ::=
        map region

§12.

    int PL::Regions::regions_new_property_notify(property *prn) {
        if (<notable-regions-properties>(prn->name))
            P_map_region = prn;
        return FALSE;
    }

The function PL::Regions::regions_new_property_notify is used in §5.

§13.

    int PL::Regions::regions_estimate_property_usage(kind *k, int *words_used) {
        if (Kinds::Compare::eq(k, K_region)) *words_used += 2;
        return FALSE;
    }

The function PL::Regions::regions_estimate_property_usage is used in §5.

§14. Assertions. The following doesn't really intervene at all: it simply produces two problem messages which would have been less helpful if core Inform had produced them.

    int PL::Regions::regions_intervene_in_assertion(parse_node *px, parse_node *py) {
        if ((ParseTree::get_type(px) == PROPER_NOUN_NT) &&
            (ParseTree::get_type(py) == COMMON_NOUN_NT)) {
            inference_subject *left_subject = ParseTree::get_subject(px);
            inference_subject *right_kind = ParseTree::get_subject(py);
            if ((InferenceSubjects::is_an_object(left_subject)) &&
                (right_kind == Kinds::Knowledge::as_subject(K_region))) {
                instance *left_object = InferenceSubjects::as_object_instance(left_subject);
                if ((left_object) &&
                    (current_sentence != Instances::get_creating_sentence(left_object)) &&
                    (Instances::of_kind(left_object, K_region) == FALSE)) {
                    Problems::Issue::subject_problem_at_sentence(_p_(PM_ExistingRegion),
                        left_subject,
                        "(which I notice in another sentence) seems now to "
                        "be declared as a new region",
                        "which is suspect. Perhaps I have misinterpreted what was "
                        "meant to be a new name for an old one?");
                    return TRUE;
                }
            }
        }
        if ((ParseTree::get_type(px) == RELATIONSHIP_NT) &&
            (ParseTree::get_subject(py) == Kinds::Knowledge::as_subject(K_region))) {
            Problems::Issue::assertion_problem(Task::syntax_tree(), _p_(PM_RegionRelated),
                "a region cannot be given a specific location",
                "since it contains what may be many rooms, which may not be "
                "contiguous and could be scattered about all over. (Sometimes "
                "this problem arises because an ambiguous sentence like 'East "
                "of Eden is a region.' has been used: Inform reads that as "
                "saying that a nameless region lies to the east of the room "
                "'Eden'. The desired effect can be got using 'called' to stop "
                "Inform taking 'East of' literally: for instance, 'The Land of "
                "Nod is in a region called East of Eden.'");
            return TRUE;
        }
        return FALSE;
    }

The function PL::Regions::regions_intervene_in_assertion is used in §5.

§15. Relations.

    int PL::Regions::regions_name_to_early_infs(wording W, inference_subject **infs) {
        if ((<notable-regions-kinds>(W)) && (K_region == NULL)) *infs = infs_region;
        return FALSE;
    }

The function PL::Regions::regions_name_to_early_infs is used in §5.

§16.

    void PL::Regions::create_relations(void) {
        R_regional_containment =
            BinaryPredicates::make_pair(SPATIAL_KBP,
                BinaryPredicates::new_term(infs_region),
                BinaryPredicates::new_term(Kinds::Knowledge::as_subject(K_object)),
                I"region-contains", I"in-region",
                NULL, NULL, Calculus::Schemas::new("TestRegionalContainment(*2,*1)"),
                Preform::Nonparsing::wording(<relation-names>,
                    REGIONAL_CONTAINMENT_RELATION_NAME));
        BinaryPredicates::set_index_details(R_regional_containment, NULL, "room/region");
    }

The function PL::Regions::create_relations is used in 3/sr (§3).

§17. We intervene only in limited cases: X contains Y, X regionally contains Y, or X incorporates Y; and only when X is a region. (This of course only applies to the built-in spatial relationships; regions are entirely free to participate in nonspatial relations.)

    int PL::Regions::assert_relations(binary_predicate *relation,
        instance *I0, instance *I1) {
        int I0_is_region = FALSE;
        if (Instances::of_kind(I0, K_region)) I0_is_region = TRUE;
        int I1_is_region = FALSE;
        if (Instances::of_kind(I1, K_region)) I1_is_region = TRUE;

        if (I0_is_region) {
            if ((relation == R_incorporation) ||
                (relation == R_containment) ||
                (relation == R_regional_containment)) {
                <You can only be declared as in one region 17.1>;
                if (I1_is_region) <A region is being put inside a region 17.2>
                else <A room is being put inside a region 17.3>;
            } else {
                Problems::Issue::sentence_problem(Task::syntax_tree(), _p_(PM_RegionRelation),
                    "regions can only contain rooms",
                    "and have no other relationships.");
            }
            return TRUE;
        } else if (I1_is_region) {
            if ((relation == R_incorporation) ||
                (relation == R_containment) ||
                (relation == R_regional_containment)) {
                Problems::Issue::sentence_problem(Task::syntax_tree(), _p_(PM_RegionRelation2),
                    "regions can only be contained in other regions",
                    "and not for example in rooms.");
            }
        }

        return FALSE;
    }

The function PL::Regions::assert_relations is used in 3/sr (§6.1).

§17.1. <You can only be declared as in one region 17.1> =

        if ((PF_I(regions, I1)->in_region) &&
            (PF_I(regions, I1)->in_region != I0)) {
            Problems::Issue::sentence_problem(Task::syntax_tree(), _p_(PM_RegionInTwoRegions),
                "each region can only be declared to be inside a single "
                "other region",
                "since although regions can be placed inside each other, "
                "they are not permitted to overlap.");
            return TRUE;
        }
        PF_I(regions, I1)->in_region = I0;
        PF_I(regions, I1)->in_region_set_at = current_sentence;

This code is used in §17.

§17.2. <A region is being put inside a region 17.2> =

        inference_subject *inner = Instances::as_subject(I1);
        inference_subject *outer = Instances::as_subject(I0);
        World::Inferences::draw(PARENTAGE_INF, inner, CERTAIN_CE, outer, NULL);

This code is used in §17.

§17.3. Anything in or part of a region is necessarily a room, if it isn't known to be a region already:

<A room is being put inside a region 17.3> =

        inference_subject *rm = Instances::as_subject(I1);
        parse_node *spec = Rvalues::from_instance(I0);
        Calculus::Propositions::Abstract::assert_kind_of_object(I1, K_room);
        World::Inferences::draw_property(rm, P_map_region, spec);

This code is used in §17.

§18. Note that because we use regular PARENTAGE_INF inferences to remember that one region is inside another, it follows that the progenitor of a region is either the next broadest region containing it, or else NULL.

    instance *PL::Regions::enclosing(instance *reg) {
        instance *P = NULL;
        if (PL::Spatial::object_is_a_room(reg)) P = PF_I(regions, reg)->in_region;
        if (PL::Regions::object_is_a_region(reg)) P = PL::Spatial::progenitor(reg);
        if (PL::Regions::object_is_a_region(P) == FALSE) return NULL;
        return P;
    }

The function PL::Regions::enclosing is used in §21, 3/sm2 (§38, §42.1, §43.2), 3/hm (§6.2, §13), 3/em (§11).

§19. Model completion.

    int PL::Regions::regions_complete_model(int stage) {
        if (stage == 2) <Assert map-region properties of rooms and regions 19.1>;
        if (stage == 3) <Assert regional-found-in properties of regions 19.2>;
        return FALSE;
    }

The function PL::Regions::regions_complete_model is used in §5.

§19.1. The following is needed in case somebody does something like this:

A desert room is a kind of room. The map region of a desert room is usually Sahara.

<Assert map-region properties of rooms and regions 19.1> =

        instance *I;
        LOOP_OVER_OBJECT_INSTANCES(I)
            if ((Instances::of_kind(I, K_room)) ||
                (Instances::of_kind(I, K_region))) {
                parse_node *where = NULL;
                parse_node *val = World::Inferences::get_prop_state_at(
                    Instances::as_subject(I), P_map_region, &where);
                if (val) {
                    instance *reg =
                        Rvalues::to_object_instance(val);
                    PF_I(regions, I)->in_region = reg;
                    PF_I(regions, I)->in_region_set_at = where;
                }
            }

This code is used in §19.

§19.2. <Assert regional-found-in properties of regions 19.2> =

        P_regional_found_in = Properties::Valued::new_nameless(
            I"regional_found_in", K_text);
        instance *I;
        LOOP_OVER_OBJECT_INSTANCES(I)
            if (Instances::of_kind(I, K_region)) {
                inter_name *iname = PL::Regions::found_in_iname(I);
                Properties::Valued::assert(P_regional_found_in, Instances::as_subject(I),
                    Rvalues::from_iname(iname), CERTAIN_CE);
            }

This code is used in §19.

§20.

    void PL::Regions::write_regional_found_in_routines(void) {
        instance *I;
        LOOP_OVER_OBJECT_INSTANCES(I)
            if (Instances::of_kind(I, K_region)) {
                inter_name *iname = PL::Regions::found_in_iname(I);
                packaging_state save = Routines::begin(iname);
                Produce::inv_primitive(Emit::tree(), IF_BIP);
                Produce::down(Emit::tree());
                        Produce::inv_call_iname(Emit::tree(), Hierarchy::find(TESTREGIONALCONTAINMENT_HL));
                        Produce::down(Emit::tree());
                            Produce::val_iname(Emit::tree(), K_object, Hierarchy::find(LOCATION_HL));
                            Produce::val_iname(Emit::tree(), K_object, Instances::iname(I));
                        Produce::up(Emit::tree());
                    Produce::code(Emit::tree());
                    Produce::down(Emit::tree());
                        Produce::rtrue(Emit::tree());
                    Produce::up(Emit::tree());
                Produce::up(Emit::tree());
                Produce::rfalse(Emit::tree());
                Routines::end(save);
            }
    }

The function PL::Regions::write_regional_found_in_routines appears nowhere else.

§21.

    int PL::Regions::regions_add_to_World_index(OUTPUT_STREAM, instance *O) {
        if ((O) && (Instances::of_kind(O, K_room))) {
            instance *R = PL::Regions::enclosing(O);
            if (R) PL::HTMLMap::colour_chip(OUT, O, R, PF_I(regions, O)->in_region_set_at);
        }
        return FALSE;
    }

The function PL::Regions::regions_add_to_World_index is used in §5.