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); }
- The structure id_runtime_context_data is accessed in 5/rf and here.
§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; }
- This code is used in §3.
§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;
- This code is used in §3.
§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; }
- This code is used in §3.
§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; }
- This code is used in §3.
§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;
- This code is used in §3.
§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;
- This code is used in §3.
§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;
- The structure activity_list is accessed in 2/ptmn, 2/cs, 2/ps, 2/is, 3/dlr, 3/pr, 3/tr, 3/nuor, 3/uor, 3/tr2, 3/dbtr, 3/rpr, 3/nar, 3/nlpr, 3/nrr, 3/npr, 3/nvr, 3/nar2, 3/ldr, 4/rpt, 4/tc, 4/ass, 4/npa, 4/rk, 4/ass2, 4/imp, 5/id, 5/adf, 6/rlb, 6/act, 7/tbl, 7/eqt, 8/tcp, 8/abp, 8/cu and here.
§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; } } }
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 }; }
- This is Preform grammar, not regular C code.
§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; }
- This code is used in §8.
§8.2. Join the activity lists8.2 =
activity_list *al1 = RP[1], *al2 = RP[2]; al1->next = al2; ==> { -, al1 };
- This code is used in §8.
§8.3. Make one-entry AL without operand8.3 =
activity_list *al; Make one-entry AL8.3.1; al->activity = RP[1];
- This code is used in §8.
§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];
- This code is used in §8 (twice).
§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;
- This code is used in §8.
§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;
- This code is used in §8.
§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 };