To work through the rules in a rulebook until a decision is made.


§1. Latest Rule Result. This used to be a large data structure which kept track of the effect of procedural rules, but in January 2011 procedurals were abolished. It retains only one purpose: as a place to record the result of the most recently completed rule. This used to sit on the top of the stack, and is now the only thing which ever sits on it. So the "stack" has just one 3-word record now. The meanings of these are as follows. The first word is one of the following:

Constant RS_NEITHER     = 0;
Constant RS_SUCCEEDS    = 1;
Constant RS_FAILS       = 2;

Array latest_rule_result --> 3;

[ RecordRuleOutcome usage strong_kind val;
    if ((latest_rule_result-->0 == RS_SUCCEEDS or RS_FAILS) &&
        (KindConformsTo_POINTER_VALUE_TY(latest_rule_result-->1)))
        DestroyPV(latest_rule_result-->2);
    if ((usage == RS_SUCCEEDS or RS_FAILS) && (KindConformsTo_POINTER_VALUE_TY(strong_kind)))
        val = CopyPV(CreatePV(strong_kind), val);
    latest_rule_result-->0 = usage;
    latest_rule_result-->1 = strong_kind;
    latest_rule_result-->2 = val;
];

§2. Following. Until January 2011, there were two ways to invoke a rulebook: to "follow" it or simply "process" it. With the demise of procedural rules, these became equivalent.

In the early days of Inform 7, stack usage became a serious issue since some forms of the Frotz Z-machine interpreter provided only 4K of stack by default. ("Only" 4K. In the mid-1980s, one of the obstacles facing IF authors at Infocom was the need to get the stack usage down to fewer than 600 bytes in order that the story file could be run on the smaller home computers of the day.) FollowRulebook was the major consumer of stack space, on average, because of its frequent recursion. Now that the process is simpler, this has become less problematic, since the routine now has fewer local variables.

FollowRulebook takes three arguments, of which only the first is compulsory:

FollowRulebook returns R if rule R in the rulebook (or rule) chose to "succeed" or "fail", and false if it made no choice. (To repeat: if the rule explicitly fails, then FollowRulebook returns true. It's easy to write plausible-looking code which goes wrong because it assumes that the return value is success vs. failure.) The outcome of FollowRulebook is stored as described above: thus the most recent rule or rulebook succeeded or failed if —

    (latest_rule_result-->0 == RS_SUCCEEDS)
    (latest_rule_result-->0 == RS_FAILS)

and otherwise there was no decision.

Global process_rulebook_count; Depth of processing recursion
Global debugging_rules = false; Are we tracing rule invocations?
Global rulebook_without_variables = -1; WorldModelKit sets this to ACTION_PROCESSING_RB

[ FollowRulebook rulebook parameter no_paragraph_skips
    rv ss spv;
    ss = self;
    if ((Protect_I7_Arrays-->0 ~= 16339) || (Protect_I7_Arrays-->1 ~= 12345)) {
        print "^^*** Fatal programming error: I7 arrays corrupted ***^^";
        @quit;
    }
    if (parameter) { self = parameter; parameter_object = parameter; }
    spv = parameter_value; parameter_value = parameter;
    we won't need parameter again, so can reuse it
    parameter = debugging_rules;
    if (debugging_rules) {
        DebugRulebooks(rulebook, parameter);
        process_rulebook_count = process_rulebook_count + debugging_rules;
    }
    if ((rulebook >= 0) && (rulebook < NUMBER_RULEBOOKS_CREATED)) {
        rv = rulebooks_array-->rulebook;
        if (rv ~= EMPTY_RULEBOOK) {
            if (rulebook ~= rulebook_without_variables) MStack_CreateRBVars(rulebook);
            if (say__p) RulebookParBreak(no_paragraph_skips);
            rv = rv(no_paragraph_skips);
            if (rulebook ~= rulebook_without_variables) MStack_DestroyRBVars(rulebook);
        } else {
            rv = 0;
        }
    } else {
        if (say__p) RulebookParBreak(no_paragraph_skips);
        rv = rulebook();
        if (rv == 2) rv = reason_the_action_failed;
        else if (rv) rv = rulebook;
    }
    if (rv) {
        if (debugging_rules) {
            process_rulebook_count = process_rulebook_count - debugging_rules;
            if (process_rulebook_count < 0) process_rulebook_count = 0;
            spaces(2*process_rulebook_count);
            if (latest_rule_result-->0 == RS_SUCCEEDS) print "[stopped: success]^";
            if (latest_rule_result-->0 == RS_FAILS) print "[stopped: fail]^";
        }
    } else {
        if (debugging_rules)
            process_rulebook_count = process_rulebook_count - debugging_rules;
        latest_rule_result-->0 = RS_NEITHER;
    }
    debugging_rules = parameter;
    self = ss; parameter_value = spv;
    return rv;
];

[ RulebookParBreak no_paragraph_skips;
    if ((no_paragraph_skips == false) && (say__pc & PARA_NORULEBOOKBREAKS == 0))
        DivideParagraphPoint();
];

§3. Specifying Outcomes. The following provide ways for rules to succeed, fail or decline to do either.

SetRulebookOutcome is a little different: it changes the outcome state of the most recent rule completed, not the current one. (It's used only when saving and restoring this in the actions machinery: rules should not call it.)

[ ActRulebookSucceeds rule_id;
    if (rule_id) reason_the_action_failed = rule_id;
    RulebookSucceeds();
];

[ ActRulebookFails rule_id;
    if (rule_id) reason_the_action_failed = rule_id;
    RulebookFails();
];

[ RulebookSucceeds strong_kind value;
    RecordRuleOutcome(RS_SUCCEEDS, strong_kind, value);
];

[ RulebookFails strong_kind value;
    RecordRuleOutcome(RS_FAILS, strong_kind, value);
];

[ RuleHasNoOutcome;
    RecordRuleOutcome(RS_NEITHER, 0, 0);
];

[ SetRulebookOutcome a;
    latest_rule_result-->0 = a;
];

§4. Discovering Outcomes. And here is how to tell what the results were.

[ RulebookOutcome a;
    a = latest_rule_result-->0;
    if ((a == RS_FAILS) || (a == RS_SUCCEEDS)) return a;
    return RS_NEITHER;
];

[ RulebookFailed;
    if (latest_rule_result-->0 == RS_FAILS) rtrue; rfalse;
];

[ RulebookSucceeded;
    if (latest_rule_result-->0 == RS_SUCCEEDS) rtrue; rfalse;
];

[ ResultOfRule RB V F K a;
    if (RB) FollowRulebook(RB, V, F);
    a = latest_rule_result-->0;
    if ((a == RS_FAILS) || (a == RS_SUCCEEDS)) {
        a = latest_rule_result-->1;
        if (a) return latest_rule_result-->2;
    }
    if (K) return KindDefaultValue(K);
    return 0;
];

[ RulebookOutcomePrintingRule nro;
    if (nro == 0) print "(no outcome)";
    else print (string) nro;
];

§5. Casting. Nothing needs to be done to a rulebook value to make it a rule value.

[ RULEBOOK_TY_to_RULE_TY r;
    return r;
];

§6. Debugging. Two modest routines to print out the names of rules and rulebooks when they occur, in so far as memory economy allows this.

[ DebugRulebooks subs parameter i;
    spaces(2*process_rulebook_count);
    print "[", (RulePrintingRule) subs;
    if (parameter) print " / on O", parameter;
    print "]^";
];

[ DB_Rule R N blocked;
    if (R==0) return;
    print "[Rule ~", (RulePrintingRule) R, "~ ";
    if (BasicInformKit`NUMBERED_RULES_CFGF) print "(", N, ") ";
    if (blocked == false) "applies.]";
    print "does not apply (wrong ";
    if (blocked == 1) print "scene";
    if (blocked == 2) print "action";
    if (blocked == 3) print "actor";
    if (blocked == 4) print "context";
    print ").]^";
];

§7. The Default Rule and Rulebook.

[ LITTLE_USED_DO_NOTHING_R; rfalse; ];

[ EMPTY_RULEBOOK forbid_breaks; rfalse; ];