I6 Template Layer

Inform 7 6M62ContentsIntroductionFunction IndexRules Index

Rulebooks.i6t

Rulebooks contents

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:

(1) RS_SUCCEEDS indicates that the most recent rule or rulebook processed ended in success. Word 2 is false if there's no value, or the kind if there is, in which case word 3 contains the value itself. (2) RS_FAILS is similar, but for a failure. Note that failures can also return values. (3) RS_NEITHER is similar except that it cannot return any value, so that words 2 and 3 are meaningless.

25Constant RS_NEITHER = 0; 26Constant RS_SUCCEEDS = 1; 27Constant RS_FAILS = 2; 28 29Array latest_rule_result --> 3; 30 31[ RecordRuleOutcome usage rule1 rule2; 32    if ((latest_rule_result-->0 == RS_SUCCEEDS or RS_FAILS) && 33        (KOVIsBlockValue(latest_rule_result-->1))) 34        BlkValueFree(latest_rule_result-->2); 35    if ((usage == RS_SUCCEEDS or RS_FAILS) && (KOVIsBlockValue(rule1))) 36        rule2 = BlkValueCopy(BlkValueCreate(rule1), rule2); 37    latest_rule_result-->0 = usage; 38    latest_rule_result-->1 = rule1; 39    latest_rule_result-->2 = rule2; 40];

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:

(a) The rulebook is an I7 value of kind "rule", which means it can be either the ID number of a rulebook – from 0 up to N-1, where N is the number of rulebooks compiled by Inform, typically about 600 – or else the address of a routine representing an individual rule. (b) The parameter supplied to the rulebook. Much as arguments can be supplied to a function in a conventional language's function call, so a parameter can be supplied whenever a rulebook is invoked. (c) no_paragraph_skips is a flag: if explicitly set true, then the rulebook is run with paragraph breaking suppressed. This is the process by which paragraph division points are placed between rules, so that if two rules both print text then a paragraph break appears between. While that is appropriate for rulebooks attached to actions or for "every turn" rules, it is disastrous for rulebooks attached to activities such as "printing the name of something".

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.

89Global process_rulebook_count; ! Depth of processing recursion 90Global debugging_rules = false; ! Are we tracing rule invocations? 91 92[ FollowRulebook rulebook parameter no_paragraph_skips 93    rv ss spv; 94    ss = self; 95    if ((Protect_I7_Arrays-->0 ~= 16339) || (Protect_I7_Arrays-->1 ~= 12345)) { 96        print "^^*** Fatal programming error: I7 arrays corrupted ***^^"; 97        @quit; 98    } 99    if (parameter) { self = parameter; parameter_object = parameter; } 100    spv = parameter_value; parameter_value = parameter; 101    ! we won't need parameter again, so can reuse it 102    parameter = debugging_rules; 103    #ifndef MEMORY_ECONOMY; 104    if (debugging_rules) { 105        DebugRulebooks(rulebook, parameter); 106        process_rulebook_count = process_rulebook_count + debugging_rules; 107    } 108    #endif; 109    if ((rulebook >= 0) && (rulebook < NUMBER_RULEBOOKS_CREATED)) { 110        rv = rulebooks_array-->rulebook; 111        if (rv ~= EMPTY_RULEBOOK) { 112            if (rulebook ~= ACTION_PROCESSING_RB) MStack_CreateRBVars(rulebook); 113            if (say__p) RulebookParBreak(no_paragraph_skips); 114            rv = rv(no_paragraph_skips); 115            if (rulebook ~= ACTION_PROCESSING_RB) MStack_DestroyRBVars(rulebook); 116        } else { 117            rv = 0; 118        } 119    } else { 120        if (say__p) RulebookParBreak(no_paragraph_skips); 121        rv = indirect(rulebook); 122        if (rv == 2) rv = reason_the_action_failed; 123        else if (rv) rv = rulebook; 124    } 125    if (rv) { 126        #ifndef MEMORY_ECONOMY; 127        if (debugging_rules) { 128            process_rulebook_count = process_rulebook_count - debugging_rules; 129            if (process_rulebook_count < 0) process_rulebook_count = 0; 130            spaces(2*process_rulebook_count); 131              if (latest_rule_result-->0 == RS_SUCCEEDS) print "[stopped: success]^"; 132              if (latest_rule_result-->0 == RS_FAILS) print "[stopped: fail]^"; 133        } 134        #endif; 135    } else { 136        if (debugging_rules) 137            process_rulebook_count = process_rulebook_count - debugging_rules; 138        latest_rule_result-->0 = RS_NEITHER; 139    } 140    debugging_rules = parameter; 141    self = ss; parameter_value = spv; 142    return rv; 143]; 144 145[ RulebookParBreak no_paragraph_skips; 146    if ((no_paragraph_skips == false) && (say__pc & PARA_NORULEBOOKBREAKS == 0)) 147        DivideParagraphPoint(); 148];

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.)

160[ ActRulebookSucceeds rule_id; 161    if (rule_id) reason_the_action_failed = rule_id; 162    RulebookSucceeds(); 163]; 164 165[ ActRulebookFails rule_id; 166    if (rule_id) reason_the_action_failed = rule_id; 167    RulebookFails(); 168]; 169 170[ RulebookSucceeds weak_kind value; 171    RecordRuleOutcome(RS_SUCCEEDS, weak_kind, value); 172]; 173 174[ RulebookFails weak_kind value; 175    RecordRuleOutcome(RS_FAILS, weak_kind, value); 176]; 177 178[ RuleHasNoOutcome; 179    RecordRuleOutcome(RS_NEITHER, 0, 0); 180]; 181 182[ SetRulebookOutcome a; 183    latest_rule_result-->0 = a; 184];

Discovering Outcomes.

And here is how to tell what the results were.

190[ RulebookOutcome a; 191    a = latest_rule_result-->0; 192    if ((a == RS_FAILS) || (a == RS_SUCCEEDS)) return a; 193    return RS_NEITHER; 194]; 195 196[ RulebookFailed; 197    if (latest_rule_result-->0 == RS_FAILS) rtrue; rfalse; 198]; 199 200[ RulebookSucceeded; 201    if (latest_rule_result-->0 == RS_SUCCEEDS) rtrue; rfalse; 202]; 203 204[ ResultOfRule RB V F K a; 205    if (RB) FollowRulebook(RB, V, F); 206    a = latest_rule_result-->0; 207    if ((a == RS_FAILS) || (a == RS_SUCCEEDS)) { 208        a = latest_rule_result-->1; 209        if (a) return latest_rule_result-->2; 210    } 211    if (K) return DefaultValueOfKOV(K); 212    return 0; 213];

Printing Rule Names.

This is the I6 printing rule used for a value of kind "rule", which as noted above can either be rulebook ID numbers in the range 0 to N-1 or are addresses of individual rules.

Names of rules and rulebooks take up a fair amount of space, and one of the main memory economies enforced by the "Use memory economy" option is to omit the necessary arrays. (It's not the text which is the problem so much as the table of addresses pointing to that text, which has to live in precious readable memory on the Z-machine.)

227#IFNDEF MEMORY_ECONOMY; 228{-array:Phrases::Manager::RulebookNames} 229#ENDIF; ! MEMORY_ECONOMY 230 231[ RulePrintingRule R p1; 232#ifndef MEMORY_ECONOMY; 233    if ((R>=0) && (R<NUMBER_RULEBOOKS_CREATED)) { 234        print (string) (RulebookNames-->R); 235    } else { 236{-call:Phrases::Manager::compile_rule_printing_switch} 237        print "(nameless rule at address ", R, ")"; 238    } 239#ifnot; 240    if ((R>=0) && (R<NUMBER_RULEBOOKS_CREATED)) { 241        print "(rulebook ", R, ")"; 242    } else { 243        print "(rule at address ", R, ")"; 244    } 245#endif; 246];

Casting.

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

252[ RULEBOOK_TY_to_RULE_TY r; 253    return r; 254];

Debugging.

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

261[ DebugRulebooks subs parameter i; 262    spaces(2*process_rulebook_count); 263    print "[", (RulePrintingRule) subs; 264    if (parameter) print " / on O", parameter; 265    print "]^"; 266]; 267 268[ DB_Rule R N blocked; 269    if (R==0) return; 270    print "[Rule ~", (RulePrintingRule) R, "~ "; 271    #ifdef NUMBERED_RULES; print "(", N, ") "; #endif; 272    if (blocked == false) "applies.]"; 273    print "does not apply (wrong ", (address) blocked, ").]^"; 274];