To store the circumstances in which a rule phrase should fire.


§1. Introduction. Runtime context data (RCD) is a set of restrictions on when a body of code can run. It's intended for rules, but in principle available for any imperative definition. For example,

Before taking a container when the player is in the Box Room: ...

can take effect only if the action is "taking a container" and the condition about the location applies. Those two restrictions are both stored in its RCD.

typedef struct id_runtime_context_data {
    struct wording activity_context;  text used to parse...
    struct activity_list *avl;  happens only while these activities go on
    struct action_pattern *ap;  happens only if the action or parameter matches this
    void *feature_rcd[MAX_COMPILER_FEATURES];  storage for features to attach, if they want to
} id_runtime_context_data;

id_runtime_context_data RuntimeContextData::new(void) {
    id_runtime_context_data phrcd;
    phrcd.activity_context = EMPTY_WORDING;
    phrcd.avl = NULL;
    phrcd.ap = NULL;
    for (int i=0; i<MAX_COMPILER_FEATURES; i++) phrcd.feature_rcd[i] = NULL;
    PluginCalls::new_rcd_notify(&phrcd);
    return phrcd;
}

id_runtime_context_data *RuntimeContextData::of(imperative_defn *id) {
    if (id == NULL) return NULL;
    return &(id->body_of_defn->runtime_context_data);
}

§2. For the more interesting clauses, see Scenes (in if) and Rules Predicated on Actions (in if), where the scenes and actions features make use of the following extensibility:

define RCD_FEATURE_DATA(id, rcd)
    ((id##_rcd_data *) rcd->feature_rcd[id##_feature->allocation_id])
define CREATE_RCD_FEATURE_DATA(id, rcd, creator)
    (rcd)->feature_rcd[id##_feature->allocation_id] = (void *) (creator(rcd));

§3. Specificity. The following is one of Inform's standardised comparison routines, which takes a pair of objects A, B and returns 1 if A makes a more specific description than B, 0 if they seem equally specific, or \(-1\) if B makes a more specific description than A. This is transitive, and intended to be used in sorting algorithms.

In this case, laws I to V are applied in turn until one is decisive. If all of them fail to decide, we return 0.

int RuntimeContextData::compare_specificity(id_runtime_context_data *rcd1,
    id_runtime_context_data *rcd2) {
    action_pattern *ap1 = NULL, *ap2 = NULL;
    parse_node *sc1 = NULL, *sc2 = NULL;
    wording AL1W = EMPTY_WORDING, AL2W = EMPTY_WORDING;
    Extract these from the PHRCDs under comparison3.1;
    Apply comparison law I3.2;
    Apply comparison law II3.3;
    Apply comparison law III3.4;
    Apply comparison law IV3.5;
    Apply comparison law V3.6;
    return 0;
}

§3.1. Extract these from the PHRCDs under comparison3.1 =

    if (rcd1) {
        sc1 = Scenes::get_rcd_spec(rcd1); ap1 = ActionRules::get_ap(rcd1);
        AL1W = rcd1->activity_context;
    }
    if (rcd2) {
        sc2 = Scenes::get_rcd_spec(rcd2); ap2 = ActionRules::get_ap(rcd2);
        AL2W = rcd2->activity_context;
    }

§3.2. More constraints beats fewer.

Apply comparison law I3.2 =

    Specifications::law(I"I - Number of aspects constrained");
    int rct1 = APClauses::count_aspects(ap1);
    int rct2 = APClauses::count_aspects(ap2);
    if (sc1) rct1++; if (sc2) rct2++;
    if (Wordings::nonempty(AL1W)) rct1++; if (Wordings::nonempty(AL2W)) rct2++;
    if (rct1 > rct2) return 1;
    if (rct1 < rct2) return -1;

§3.3. If both have scene requirements, a narrow requirement beats a broad one.

Apply comparison law II3.3 =

    if ((sc1) && (sc2)) {
        int rv = Specifications::compare_specificity(sc1, sc2, NULL);
        if (rv != 0) return rv;
    }

§3.4. More when/while conditions beats fewer.

Apply comparison law III3.4 =

    Specifications::law(I"III - When/while requirement");
    if ((Wordings::nonempty(AL1W)) && (Wordings::empty(AL2W))) return 1;
    if ((Wordings::empty(AL1W)) && (Wordings::nonempty(AL2W))) return -1;
    if (Wordings::nonempty(AL1W)) {
        int n1 = RuntimeContextData::activity_list_count(rcd1->avl);
        int n2 = RuntimeContextData::activity_list_count(rcd2->avl);
        if (n1 > n2) return 1;
        if (n2 > n1) return -1;
    }

§3.5. A more specific action (or parameter) beats a less specific one.

Apply comparison law IV3.5 =

    Specifications::law(I"IV - Action requirement");
    int rv = ActionPatterns::compare_specificity(ap1, ap2);
    if (rv != 0) return rv;

§3.6. A rule with a scene requirement beats one without.

Apply comparison law V3.6 =

    Specifications::law(I"V - Scene requirement");
    if ((sc1 != NULL) && (sc2 == NULL)) return 1;
    if ((sc1 == NULL) && (sc2 != NULL)) return -1;

§4. Activity lists. The activity list part of a RCD is a list of activities, one of which must be currently running for the rule to fire. For example, in:

Rule for printing the name of the lemon sherbet while listing contents: ...

the activity list is just "listing contents". These are like action patterns, but much simpler to parse — an or-divided list of activities can be given, with or without operands; "not" can be used to negate the list; and ordinary conditions are also allowed, as here:

Rule for printing the name of the sack while the sack is not carried: ...

where "the sack is not carried" is also a <run-time-context> even though it mentions no activities.

typedef struct activity_list {
    struct activity *activity;  what activity
    struct parse_node *acting_on;  the parameter
    struct parse_node *only_when;  condition for when this applies
    int ACL_parity;  +1 if meant positively, -1 if negatively
    struct activity_list *next;  next in activity list
} activity_list;

§5. The "count" of an activity list is a measure of its complexity:

int RuntimeContextData::activity_list_count(activity_list *avl) {
    int n = 0;
    while (avl) {
        n += 10;
        if (avl->only_when) n += Conditions::count(avl->only_when);
        avl = avl->next;
    }
    return n;
}

§6. There's a tricky race condition here: the activity list has to be parsed with the correct rulebook variables or it won't parse; but the rulebook variables won't be known until the rule is booked; and in order to book the rule, Inform needs to sort it into logical sequence with others already in the same rulebook; and that requires knowledge of the conditions of usage; which in turn requires the activity list. So the following function is called at the last possible moment in the booking process.

void RuntimeContextData::ensure_avl(rule *R) {
    imperative_defn *id = Rules::get_imperative_definition(R);
    if (id) {
        id_body *idb = id->body_of_defn;
        id_runtime_context_data *rcd = &(idb->runtime_context_data);
        if (Wordings::nonempty(rcd->activity_context)) {
            parse_node *save_cs = current_sentence;
            current_sentence = id->at;

            stack_frame *phsf = &(idb->compilation_data.id_stack_frame);
            Frames::make_current(phsf);

            Frames::set_shared_variable_access_list(phsf, R->variables_visible_in_definition);
            rcd->avl = RuntimeContextData::parse_avl(rcd->activity_context);
            current_sentence = save_cs;
        }
    }
}

§7. Which calls down to:

int parsing_al_conditions = TRUE;

activity_list *RuntimeContextData::parse_avl(wording W) {
    int save_pac = parsing_al_conditions;
    parsing_al_conditions = TRUE;
    int rv = <run-time-context>(W);
    parsing_al_conditions = save_pac;
    if (rv) return <<rp>>;
    return NULL;
}

§8. Which in turn uses the following Preform grammar.

The direct handling of "something" below is to avoid Inform from reading it as "some thing", with the implication that a K_thing value is meant. When people talk about "factorising something", where "factorising" is an activity on numbers, for example, they mean "something" to stand for "any number", not for "any physical object". Kind-checking means that only a number is possible anyway, so we can safely just ignore the word "something".

<run-time-context> ::=
    not <activity-list-unnegated> |              ==> { 0, RP[1] }; Flip the activity list parities8.1;
    <activity-list-unnegated>                    ==> { 0, RP[1] }

<activity-list-unnegated> ::=
    ... |                                        ==> { lookahead }
    <activity-list-entry> <activity-tail> |      ==> Join the activity lists8.2;
    <activity-list-entry>                        ==> { 0, RP[1] }

<activity-tail> ::=
    , _or <run-time-context> |                   ==> { 0, RP[1] }
    _,/or <run-time-context>                     ==> { 0, RP[1] }

<activity-list-entry> ::=
    <activity-name> |                            ==> Make one-entry AL without operand8.3
    <activity-name> of/for <activity-operand> |  ==> Make one-entry AL with operand8.4
    <activity-name> <activity-operand> |         ==> Make one-entry AL with operand8.4
    ^<if-parsing-al-conditions> ... |            ==> Make one-entry AL with unparsed text8.5
    <if-parsing-al-conditions> <s-condition>     ==> Make one-entry AL with condition8.6

<activity-operand> ::=
    something/anything |                         ==> { FALSE, Specifications::new_UNKNOWN(W) }
    something/anything else |                    ==> { FALSE, Specifications::new_UNKNOWN(W) }
    <s-type-expression-or-value>                 ==> { TRUE, RP[1] }

<if-parsing-al-conditions> internal 0 {
    if (parsing_al_conditions) return TRUE;
    ==> { fail nonterminal };
}

§8.1. Flip the activity list parities8.1 =

    activity_list *al = *XP;
    for (; al; al=al->next) {
        al->ACL_parity = (al->ACL_parity)?FALSE:TRUE;
    }

§8.2. Join the activity lists8.2 =

    activity_list *al1 = RP[1], *al2 = RP[2];
    al1->next = al2;
    ==> { -, al1 };

§8.3. Make one-entry AL without operand8.3 =

    activity_list *al;
    Make one-entry AL8.3.1;
    al->activity = RP[1];

§8.4. Make one-entry AL with operand8.4 =

    activity *an = RP[1];
    if (an->activity_on_what_kind == NULL) return FALSE;
    if ((R[2]) && (Dash::validate_parameter(RP[2], an->activity_on_what_kind) == FALSE))
        return FALSE;
    activity_list *al;
    Make one-entry AL8.3.1;
    al->activity = an;
    al->acting_on = RP[2];

§8.5. Make one-entry AL with unparsed text8.5 =

    parse_node *cond = Specifications::new_UNKNOWN(EMPTY_WORDING);
    activity_list *al;
    Make one-entry AL8.3.1;
    al->only_when = cond;

§8.6. Make one-entry AL with condition8.6 =

    parse_node *cond = RP[2];
    if (Dash::validate_conditional_clause(cond) == FALSE) return FALSE;
    activity_list *al;
    Make one-entry AL8.3.1;
    al->only_when = cond;

§8.3.1. Make one-entry AL8.3.1 =

    al = CREATE(activity_list);
    al->acting_on = NULL;
    al->only_when = NULL;
    al->next = NULL;
    al->ACL_parity = TRUE;
    al->activity = NULL;
    ==> { -, al };