Contemplating improvements to the built-in testing commands

Yes. A rule “doesn’t know” what rulebook(s) it’s in or whether a given invocation of it is due to a rulebook being followed.

Here’s the bit in FollowRulebook in Rulebooks.i6t that checks whether it’s handling a whole rulebook or just a rule:

        if ((rulebook >= 0) && (rulebook < NUMBER_RULEBOOKS_CREATED)) {

If this is true, rulebook is a valid index into the rulebook array, so it’s really a rulebook, not an individual rule.

                rv = rulebooks_array-->rulebook;

We lookup the individual rulebook. Entries in rulebooks_array are either the EMPTY_RULEBOOK constant or a function corresponding to the whole rulebook. If the latter, this function hard-codes the invocation of each rule in the rulebook, in order. There isn’t an array of them anywhere.

                if (rv ~= EMPTY_RULEBOOK) { 
                        if (rulebook ~= ACTION_PROCESSING_RB) MStack_CreateRBVars(rulebook);

Actions need some infrastructure, so if these are the action-processing rules we build it.

                        if (say__p) RulebookParBreak(no_paragraph_skips);

Setup for either magically doing the right thing in regard to line breaks or making the author cry.

                        rv = rv(no_paragraph_skips);

Ta da, we invoke the rulebook.

                        if (rulebook ~= ACTION_PROCESSING_RB) MStack_DestroyRBVars(rulebook);

Destructor for action-processing overhead.

                } else {
                        rv = 0;
                }

(the EMPTY_RULEBOOK case)

        } else {

Now we’re in the individual rule case.

                if (say__p) RulebookParBreak(no_paragraph_skips);
                rv = rulebook();

The rule is itself a function, so we call it.

                if (rv == 2) rv = reason_the_action_failed;

We have stopped, either in success or failure; we don’t care. (Actual success or failure is stored in the latest_rule_result array.) The compiler hard-codes reason_the_action_failed manipulation into action-related stuff; that’s why I had to re-implement its equivalent from scratch to make reason the rule stopped

                else if (rv) rv = rulebook;

for any other non-zero result, we set rv to the rulebook. If rv is 0, the rule neither failed nor succeeded.

        }

As an example, here’s the top of the Startup Rules function…

[ call_U134 forbid_breaks rv original_deadflag;
    (original_deadflag = deadflag);

And below is its first rule. All this boilerplate is repeated over and over for each rule.

    if (debug_rules) {
        DB_Rule(INITIALISE_MEMORY_R, 0, 0);
    }
    (rv = (INITIALISE_MEMORY_R)());
    if (rv) {
        if ((rv == 2)) {
            return reason_the_action_failed;
        }
        return INITIALISE_MEMORY_R;
    }
    ((latest_rule_result-->(0)) = 0);
    if ((original_deadflag ~= deadflag)) {
        rfalse;
    }
    if (say__p) {
        RulebookParBreak(forbid_breaks);
    }

So we know when we’ve started a rulebook… but we can’t tell when we’ve finished one. Suppose we want to maintain a global of what rulebook we’re in that we’re going to push to and pop from the stack. So at the moment we know we’re in the Foo rulebook… or at least we think we know that. But there’s this Foo rule:

Foo (this is the doomed rule): follow the always successful rule.

And that rule ends in success. There isn’t a way within FollowRulebook to know that the doomed rule is in Foo or that the always successful rule isn’t, so we can’t conclude we’ve left Foo because we’ve encountered a rule that finished.

So we can contrive turning debug on or off for some individual author-created rulebook, but without changing the Inform compiler’s representation of rulebooks, there can’t be a way to include/exclude rulebooks in the general case.