A feature for actions, by which animate characters change the world model.

§1. Support for actions is contained in the "actions" feature, which occupies this entire chapter. The test group :actions may be helpful in trouble-shooting here.

It may be helpful to distinguish these ideas right from the outset:

void ActionsPlugin::start(void) {
    ActionsNodes::nodes_and_annotations();

    PluginCalls::plug(MAKE_SPECIAL_MEANINGS_PLUG, ActionsPlugin::make_special_meanings);
    PluginCalls::plug(NEW_BASE_KIND_NOTIFY_PLUG, ARvalues::new_base_kind_notify);
    PluginCalls::plug(COMPARE_CONSTANT_PLUG, ARvalues::compare_CONSTANT);
    PluginCalls::plug(COMPILE_CONSTANT_PLUG, CompileRvalues::action_kinds);
    PluginCalls::plug(COMPILE_CONDITION_PLUG, AConditions::compile_condition);
    PluginCalls::plug(CREATION_PLUG, ActionsNodes::creation);
    PluginCalls::plug(UNUSUAL_PROPERTY_VALUE_PLUG, ActionsNodes::unusual_property_value_node);
    PluginCalls::plug(OFFERED_PROPERTY_PLUG, ActionVariables::actions_offered_property);
    PluginCalls::plug(OFFERED_SPECIFICATION_PLUG, ActionsPlugin::actions_offered_specification);
    PluginCalls::plug(TYPECHECK_EQUALITY_PLUG, ARvalues::actions_typecheck_equality);
    PluginCalls::plug(PLACE_RULE_PLUG, Actions::place_rule);
    PluginCalls::plug(RULE_PLACEMENT_NOTIFY_PLUG, Actions::rule_placement_notify);
    PluginCalls::plug(PRODUCTION_LINE_PLUG, ActionsPlugin::production_line);
    PluginCalls::plug(COMPLETE_MODEL_PLUG, ActionsPlugin::complete_model);
    PluginCalls::plug(COMPILE_TEST_HEAD_PLUG, RTRules::actions_compile_test_head);
    PluginCalls::plug(COMPILE_TEST_TAIL_PLUG, RTRules::actions_compile_test_tail);
    PluginCalls::plug(NEW_RCD_NOTIFY_PLUG, ActionRules::new_rcd);

    Vocabulary::set_flags(Vocabulary::entry_for_text(U"doing"), ACTION_PARTICIPLE_MC);
    Vocabulary::set_flags(Vocabulary::entry_for_text(U"asking"), ACTION_PARTICIPLE_MC);
}

int ActionsPlugin::production_line(int stage, int debugging, stopwatch_timer *sequence_timer) {
    if (stage == INTER1_CSEQ) {
        BENCH(RTActions::compile);
        BENCH(RTNamedActionPatterns::compile);
    }
    return FALSE;
}

§2. Though K_action_name is very like an enumeration kind, its possible values, which correspond to action_name objects, are not strictly speaking instances in the Inform world model. (Because they do not have properties: see Action Variables for what they have instead.)

The "waiting" action is sacred, because it is the default value for K_action_name values: waiting is the zero of actions.

action_name *waiting_action = NULL;

§3. This is recognised by its English name when defined by the Standard Rules. (So there is no need to translate this to other languages.)

<waiting-action> ::=
    waiting

§4. Because action_name values are not instances, we cannot recognise them when instances are created, and instead have to do it directly when this is called by the function creating them:

void ActionsPlugin::notice_new_action_name(action_name *an) {
    if (<waiting-action>(ActionNameNames::tensed(an, IS_TENSE))) {
        if (waiting_action == NULL) waiting_action = an;
    }
    PluginCalls::new_action_notify(an);
}

action_name *ActionsPlugin::default_action_name(void) {
    if (waiting_action == NULL) internal_error("wait action not ready");
    return waiting_action;
}

§5. And because K_action_name values have no properties, they cannot store a "specification" text as one, and have to make their own arrangements:

int ActionsPlugin::actions_offered_specification(parse_node *owner, wording W) {
    if (Rvalues::is_CONSTANT_of_kind(owner, K_action_name)) {
        RTActions::actions_set_specification_text(
            ARvalues::to_action_name(owner), Wordings::first_wn(W));
        return TRUE;
    }
    return FALSE;
}

§6. The action bitmap is an array of bits attached to each object, one for each action, which records whether that action has yet applied successfully to that object. This is used at run-time to handle past tense conditions such as "the jewels have been taken".

property *P_action_bitmap = NULL;

int ActionsPlugin::complete_model(int stage) {
    if (stage == WORLD_STAGE_V) {
        P_action_bitmap = ValueProperties::new_nameless(I"action_bitmap", K_value);
        Hierarchy::make_available(RTProperties::iname(P_action_bitmap));

        instance *I;
        LOOP_OVER_INSTANCES(I, K_object) {
            inference_subject *subj = Instances::as_subject(I);
            Assert the Inter action-bitmap property6.1;
        }
        inference_subject *subj = KindSubjects::from_kind(K_thing);
        Assert the Inter action-bitmap property6.1;
    }
    return FALSE;
}

§6.1. Assert the Inter action-bitmap property6.1 =

    if ((K_room == NULL) ||
        (InferenceSubjects::is_within(subj, KindSubjects::from_kind(K_room)) == FALSE)) {
        parse_node *S = RTActionBitmaps::compile_action_bitmap_property(subj);
        ValueProperties::assert(P_action_bitmap, subj, S, CERTAIN_CE);
    }

§7. The rest of this section is given over to the Preform grammar for dealing with the special meaning "X is an action...", which creates new action names. These can be quite complicated:

Inserting it into is an action applying to two things.

Verifying the story file is an action out of world and applying to nothing.

int ActionsPlugin::make_special_meanings(void) {
    SpecialMeanings::declare(ActionsPlugin::new_action_SMF, I"new-action", 2);
    return FALSE;
}

action_name *an_being_parsed = NULL;
int ActionsPlugin::new_action_SMF(int task, parse_node *V, wording *NPs) {
    wording SW = (NPs)?(NPs[0]):EMPTY_WORDING;
    wording OW = (NPs)?(NPs[1]):EMPTY_WORDING;
    switch (task) {  "Taking something is an action."
        case ACCEPT_SMFT: Check that this validly declares an action7.1; break;
        case PASS_1_SMFT: Parse the subject and object phrases7.2; break;
    }
    return FALSE;
}

§7.1. Check that this validly declares an action7.1 =

    if (<new-action-sentence-object>(OW)) {
        if (<<r>> == FALSE) return FALSE;
        parse_node *O = <<rp>>;
        <np-unparsed>(SW);
        V->next = <<rp>>;
        V->next->next = O;
        return TRUE;
    }

§8. <nounphrase-actionable> here is an awkward necessity, designed to prevent the regular sentence "The impulse is an action name that varies" from being parsed as an instance of "... is an action ...", creating a new action.

<new-action-sentence-object> ::=
    <indefinite-article> <new-action-sentence-object-unarticled> |  ==> { pass 2 }
    <new-action-sentence-object-unarticled>                         ==> { pass 1 }

<new-action-sentence-object-unarticled> ::=
    action based ... |                  ==> { FALSE, NULL }
    action <nounphrase-actionable> |    ==> { TRUE, RP[1] }
    action                              ==> Issue PM_BadActionDeclaration problem8.1

<nounphrase-actionable> ::=
    ^<variable-creation-tail>           ==> { 0, Diagrams::new_UNPARSED_NOUN(W) }

<variable-creation-tail> ::=
    *** that/which vary/varies |
    *** variable

§8.1. Issue PM_BadActionDeclaration problem8.1 =

    UsingProblems::assertion_problem(Task::syntax_tree(), _p_(PM_BadActionDeclaration),
        "it is not sufficient to say that something is an 'action'",
        "without giving the necessary details: for example, 'Unclamping "
        "is an action applying to one thing.'");
    ==> { FALSE, NULL };

§7.2. Supposing that all that worked, the SP of the sentence is the name for the new action, and the OP can include a wide range of details about it.

Parse the subject and object phrases7.2 =

    if ((V->next) && (V->next->next))
        if (<action-sentence-subject>(Node::get_text(V->next))) {
            an_being_parsed = <<rp>>;
            an_being_parsed->compilation_data.designers_specification = V->next->next;
            ActionsPlugin::clear_clauses();
            <action-sentence-object>(Node::get_text(V->next->next));
        }

§9. The subject noun phrase needs little further parsing — it's the name of the action-to-be. A successful match here causes the new action_name structure to be created.

<action-sentence-subject> ::=
    <action-name> |  ==> Issue PM_ActionAlreadyExists problem9.1
    ...              ==> { 0, Actions::act_new(W) }

§9.1. Issue PM_ActionAlreadyExists problem9.1 =

    StandardProblems::sentence_problem(Task::syntax_tree(),
        _p_(PM_ActionAlreadyExists),
        "that seems to be an action already existing",
        "so it cannot be redefined now. If you would like to reconfigure "
        "an action in the standard set - for instance if you prefer "
        "'unlocking' to apply to only one thing, not two - create a new "
        "action for what you need ('keyless unlocking', perhaps) and then "
        "change the grammar to use the new action rather than the old "
        "('Understand \"unlock [something]\" as keyless unlocking.').");
    ==> { fail nonterminal };

§10. The object NP is a sequence of "action clauses" which can occur in any order, which are allowed but not required to be delimited as a list, and which can inconveniently contain the word "and"; not only that, but note that in

applying to one thing and one number

the initial text "applying to one thing" would be valid as it stands.

<action-sentence-object> ::=
    <action-clauses> |                   ==> { 0, - }
    ...                                  ==> Issue PM_ActionClauseUnknown problem10.9

<action-clauses> ::=
    ... |                                         ==> { lookahead }
    <action-clauses> <action-clause-terminated> | ==> { R[2], - }; ActionsPlugin::clause(R[2]);
    <action-clause-terminated>                    ==> { R[1], - }; ActionsPlugin::clause(R[1]);

<action-clause-terminated> ::=
    <action-clause> , and |              ==> { pass 1 }
    <action-clause> and |                ==> { pass 1 }
    <action-clause> , |                  ==> { pass 1 }
    <action-clause>                      ==> { pass 1 }

<action-clause> ::=
    out of world |                       ==> { OOW_ACT_CLAUSE, - }; Make out of world10.1
    abbreviable |                        ==> { ABBREV_ACT_CLAUSE, - }; Make abbreviable10.2
    with past participle ... |           ==> { PP_ACT_CLAUSE, - }; Give irregular participle10.3
    applying to <action-applications> |  ==> { APPLYING_ACT_CLAUSE, - }
    requiring light                      ==> { LIGHT_ACT_CLAUSE, - }; Require light10.4

<action-applications> ::=
    nothing |
    one <act-req> and one <act-req> |    ==> Set kind and access for two10.7
    one <act-req> and <act-req> |        ==> Set kind and access for two10.7
    <act-req> and one <act-req> |        ==> Set kind and access for two10.7
    <act-req> and <act-req> |            ==> Set kind and access for two10.7
    nothing or one <act-req> |           ==> Set kind and access for an optional one10.5
    one <act-req> |                      ==> Set kind and access for one10.6
    two <act-req>   |                    ==> Set kind and access for one, doubling up10.8
    <act-req> |                          ==> Set kind and access for one10.6
    ...                                  ==> Issue PM_ActionMisapplied problem10.10;

<act-req> ::=
    <act-req-inner>                      ==> Check action kind10.11;

<act-req-inner> ::=
    <action-access> <k-kind> |           ==> { R[1], RP[2] }
    <k-kind>                             ==> { UNRESTRICTED_ACCESS, RP[1] }

<action-access> ::=
    visible |                            ==> { DOESNT_REQUIRE_ACCESS, - }
    touchable |                          ==> { REQUIRES_ACCESS, - }
    carried                              ==> { REQUIRES_POSSESSION, - }

§10.1. Make out of world10.1 =

    ActionSemantics::make_action_out_of_world(an_being_parsed);

§10.2. Make abbreviable10.2 =

    ActionNameNames::make_abbreviable(an_being_parsed);

§10.3. Give irregular participle10.3 =

    ActionNameNames::set_irregular_past(an_being_parsed, GET_RW(<action-clause>, 1));

§10.4. Require light10.4 =

    ActionSemantics::make_action_require_light(an_being_parsed);

§10.5. Set kind and access for an optional one10.5 =

    ActionSemantics::give_action_an_optional_noun(an_being_parsed, R[1], RP[1]);

§10.6. Set kind and access for one10.6 =

    ActionSemantics::give_action_one_noun(an_being_parsed, R[1], RP[1]);

§10.7. Set kind and access for two10.7 =

    ActionSemantics::give_action_two_nouns(an_being_parsed, R[1], RP[1], R[2], RP[2]);

§10.8. Set kind and access for one, doubling up10.8 =

    ActionSemantics::give_action_two_nouns(an_being_parsed, R[1], RP[1], R[1], RP[1]);

§10.9. Issue PM_ActionClauseUnknown problem10.9 =

    StandardProblems::sentence_problem(Task::syntax_tree(),
        _p_(PM_ActionClauseUnknown),
        "the action definition contained text I couldn't follow",
        "and may be too complicated.");

§10.10. Issue PM_ActionMisapplied problem10.10 =

    StandardProblems::sentence_problem(Task::syntax_tree(),
        _p_(PM_ActionMisapplied),
        "an action cannot apply to specific kinds of object such as 'room' or 'vehicle'",
        "and if it is going to apply to objects at all then it should talk about them "
        "as 'things'. For example, 'Driving is an action applying to one touchable thing' "
        "might be the way to set up an action for driving, even if it makes sense only "
        "for a 'vehicle'. A rule like 'Check driving: if the noun is not a vehicle, ...' "
        "would then be a good idea.");
    ==> { REQUIRES_ACCESS, K_thing };

§10.11. Check action kind10.11 =

    int A = R[1]; kind *K = RP[1];
    if (Kinds::eq(K, K_thing)) {
        if (A == UNRESTRICTED_ACCESS) A = REQUIRES_ACCESS;
        ==> { A, K_object };
    } else if ((Kinds::Behaviour::is_subkind_of_object(K)) &&
         (Latticework::super(K) != K_object)) {
        Issue PM_ActionMisapplied problem10.10;
    } else if (A != UNRESTRICTED_ACCESS) {
        Issue PM_ActionMisapplied problem10.10;
    } else {
        ==> { A, K };
    }

§11. For years this was not erroneous, but you now can't write, say, "X is an action applying to nothing, applying to nothing, requiring light and applying to nothing".

define OOW_ACT_CLAUSE 1
define PP_ACT_CLAUSE 2
define APPLYING_ACT_CLAUSE 3
define LIGHT_ACT_CLAUSE 4
define ABBREV_ACT_CLAUSE 5
int an_clauses_filled[6];
void ActionsPlugin::clear_clauses(void) {
    for (int i=1; i<=5; i++) an_clauses_filled[i] = FALSE;
}
void ActionsPlugin::clause(int N) {
    if (an_clauses_filled[N])
        StandardProblems::sentence_problem(Task::syntax_tree(),
            _p_(PM_ActionClauseRepeated),
            "that seems to repeat a clause",
            "or to specify something twice over.");
    an_clauses_filled[N] = TRUE;
}