To create and manipulate grammar, primarily by parsing and acting upon Understand... sentences in the source text.


§1. We cache grammar occurring in the source text in conditions, and so forth:

typedef struct cached_understanding {
    struct wording understanding_text;  word range of the understanding text
    struct inter_name *cu_iname;  the runtime name for this Consult_Grammar_N routine
    CLASS_DEFINITION
} cached_understanding;

§2. And this will help with parsing:

typedef struct understanding_item {
    struct wording quoted_text;
    struct property *quoted_property;
    struct understanding_item *next;
} understanding_item;

typedef struct understanding_reference {
    struct wording reference_text;
    int gv_result;
    int mword;
    int mistaken;
    int pluralised_reference;
    int reversed_reference;
    action_name *an_reference;
    parse_node *spec_reference;
    struct understanding_reference *next;
} understanding_reference;

§3. New grammar arrives in the system in two ways: primarily by means of explicit Understand sentences in the source text, but also secondarily in the form of table entries or other values used to match against snippets. For example:

Understand "drill [something]" as drilling.

if the player's command matches "room [number]", ...

§4. Understand sentences can also revoke existing grammar, in some cases, as we shall see. They are not read in the main assertion traverse, since they depend on too much not known then: they have a traverse of their own, and so do not use the sentence handler system adopted by the main assertion traverse.

enum TRAVERSE_FOR_GRAMMAR_SMFT

§5.

int base_problem_count = 0;

int PL::Parsing::understand_as_SMF(int task, parse_node *V, wording *NPs) {
    wording OW = (NPs)?(NPs[1]):EMPTY_WORDING;
    wording O2W = (NPs)?(NPs[2]):EMPTY_WORDING;
    switch (task) {  "Understand... as..."
        case ACCEPT_SMFT:
            <np-unparsed>(O2W);
            V->next = <<rp>>;
            <np-unparsed>(OW);
            V->next->next = <<rp>>;
            return TRUE;
        case TRAVERSE_FOR_GRAMMAR_SMFT:
            base_problem_count = problem_count;
            PL::Parsing::understand_sentence(Node::get_text(V->next), Node::get_text(V->next->next));
            break;
    }
    return FALSE;
}

§6.

void PL::Parsing::traverse(void) {
    SyntaxTree::traverse(Task::syntax_tree(), PL::Parsing::visit);
}
void PL::Parsing::visit(parse_node *p) {
    if ((Node::get_type(p) == SENTENCE_NT) && (p->down))
        MajorNodes::try_special_meaning(TRAVERSE_FOR_GRAMMAR_SMFT, p->down);
}

§7. The secondary means of acquiring new grammar is used when compiling type specifications of type VALUE/UNDERSTANDING and when compiling the entries of "topic" columns in tables. These will usually be simple constructions of individual grammar lines, but they need to belong to a grammar verb (GV) nevertheless, even if they are the only thing on that GV. Such GVs compile to routines for parsing snippets, and no pointers exist to them in other Inform data structures: the result of the routine below, assuming no problems are issued, is simply that the name of a snippet-parsing routine is printed.

void PL::Parsing::compile_understanding(inter_ti *val1, inter_ti *val2, wording W, int table_entry) {
    if (<subject-pronoun>(W)) { *val1 = LITERAL_IVAL; *val2 = 0; }
    else {
        cached_understanding *cu;
        LOOP_OVER(cu, cached_understanding)
            if (Wordings::match(cu->understanding_text, W)) {
                Emit::to_ival(val1, val2, cu->cu_iname);
                return;
            }
        base_problem_count = problem_count;
        PL::Parsing::Tokens::General::prepare_consultation_gv();
        if (table_entry) {
            LOOP_THROUGH_WORDING(k, W) {
                if (<quoted-text>(Wordings::one_word(k))) {
                    PL::Parsing::understand_block(Wordings::one_word(k), NULL, EMPTY_WORDING, TRUE);
                }
            }
        } else {
            PL::Parsing::understand_block(W, NULL, EMPTY_WORDING, FALSE);
        }
        inter_name *iname = PL::Parsing::Tokens::General::print_consultation_gv_name();
        if (iname) {
            cu = CREATE(cached_understanding);
            cu->understanding_text = W;
            cu->cu_iname = iname;
            Emit::to_ival(val1, val2, iname);
        }
    }
}

§8. Dividing Understand into cases. We will need some context variables.

define COMMAND_UNDERSTAND_FORM 1
define PROPERTY_UNDERSTAND_FORM 2
define GRAMMAR_UNDERSTAND_FORM 3
define NOTHING_UNDERSTAND_FORM 4
define NO_UNDERSTAND_FORM 5

§9. Understand sentences take three different forms — so different, in fact, that we will parse the subject NP to see which form we have, and only then parse the object NP (using a different grammar for each of the three forms). As examples:

Understand "photograph [something]" as photographing.

Understand the command "access" as "open".

Understand the unbroken property as describing the pot.

<understand-sentence-subject> ::=
    nothing |                                         ==> { NOTHING_UNDERSTAND_FORM, NULL }
    <understand-property-list> |                      ==> { PROPERTY_UNDERSTAND_FORM, RP[1] }
    the command/commands <understand-regular-list> |  ==> { COMMAND_UNDERSTAND_FORM, RP[1] }
    the verb/verbs ... |                              ==> Issue PM_OldVerbUsage problem9.1
    <understand-regular-list>                         ==> { GRAMMAR_UNDERSTAND_FORM, RP[1] }

§9.1. Issue PM_OldVerbUsage problem9.1 =

    StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_OldVerbUsage),
        "this is an outdated form of words",
        "and Inform now prefers 'Understand the command ...' "
        "rather than 'Understand the verb ...'. (Since this "
        "change was made in beta-testing, quite a few old "
        "source texts still use the old form: the authors "
        "of Inform apologise for any nuisance incurred.)");
    ==> { NO_UNDERSTAND_FORM, - };

§10. In the first two cases, a list of quoted text appears:

<understand-regular-list> ::=
    ... |                                                   ==> { lookahead }
    <understand-regular-entry> <understand-regular-tail> |  ==> Compose understand item list10.1
    <understand-regular-entry>                              ==> { 0, RP[1] }

<understand-regular-tail> ::=
    , _and/or <understand-regular-list> |                   ==> { 0, RP[1] }
    _,/and/or <understand-regular-list>                     ==> { 0, RP[1] }

<understand-regular-entry> ::=
    ...                                                     ==> Make understand item10.2

§11. In the third case, the subject NP is a list of property names written in the formal way (with "property").

<understand-property-list> ::=
    ... |                                                     ==> { lookahead }
    <understand-property-entry> <understand-property-tail> |  ==> Compose understand item list10.1
    <understand-property-entry>                               ==> { 0, RP[1] }

<understand-property-tail> ::=
    , _and/or <understand-property-list> |                    ==> { 0, RP[1] }
    _,/and/or <understand-property-list>                      ==> { 0, RP[1] }

<understand-property-entry> ::=
    <property-name> property |                                ==> Make understand property item11.2
    ... property                                              ==> Issue PM_UnknownUnderstandProperty problem11.1

§11.1. Issue PM_UnknownUnderstandProperty problem11.1 =

    if (!preform_lookahead_mode)
    StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_UnknownUnderstandProperty),
        "I don't understand what property that refers to",
        "but it doesn't seem to be a property I know. An example of "
        "correct usage is 'understand the transparent property as "
        "describing a container.'");

§10.1. Compose understand item list10.1 =

    understanding_item *comp;
    understanding_item *ui1 = RP[1];
    understanding_item *ui2 = RP[2];
    if (ui1 == NULL) { comp = ui2; }
    else if (ui2 == NULL) { comp = ui1; }
    else {
        ui1->next = ui2;
        comp = ui1;
    }
    ==> { -, comp };

§10.2. Make understand item10.2 =

    if (!preform_lookahead_mode) {
        understanding_item *ui = CREATE(understanding_item);
        ui->quoted_text = W;
        ui->quoted_property = NULL;
        ui->next = NULL;
        ==> { -, ui };
    } else {
        ==> { -, NULL };
    }

§11.2. Make understand property item11.2 =

    if (!preform_lookahead_mode) {
        understanding_item *ui = CREATE(understanding_item);
        ui->quoted_text = EMPTY_WORDING;
        ui->quoted_property = RP[1];
        ui->next = NULL;
        ==> { -, ui };
    } else {
        ==> { -, NULL };
    }

§12.

understanding_reference ur_being_parsed;

§13. Now we turn to the object phrase. As noted above, we use three different grammars for this; one for each of the possible subject phrase forms. The first is the most popularly used:

Understand "take [something]" as taking.

It's not widely known, but the object phrase here can be a list.

<understand-sentence-object> ::=
    <understand-sentence-object-uncond> when/while ... |  ==> { 2, RP[1] }
    <understand-sentence-object-uncond>                   ==> { 1, RP[1] }

<understand-sentence-object-uncond> ::=
    ... |                                                            ==> { lookahead }
    <understand-sentence-entry> <understand-sentence-object-tail> |  ==> Compose understand reference list13.1
    <understand-sentence-entry>                                      ==> { 0, RP[1] }

<understand-sentence-object-tail> ::=
    , _and/or <understand-sentence-object-uncond> |  ==> { 0, RP[1] }
    _,/and/or <understand-sentence-object-uncond>    ==> { 0, RP[1] }

<understand-sentence-entry> ::=
    <understand-as-this>  ==> { 0, - }; if (!preform_lookahead_mode) Deal with UT vars13.2;

§13.1. Compose understand reference list13.1 =

    understanding_reference *comp;
    understanding_reference *ui1 = RP[1];
    understanding_reference *ui2 = RP[2];
    if (ui1 == NULL) { comp = ui2; }
    else if (ui2 == NULL) { comp = ui1; }
    else {
        ui1->next = ui2;
        comp = ui1;
    }
    ==> { -, comp };

§13.2. Deal with UT vars13.2 =

    if (R[1] == -1) {
        ==> { -, NULL };
    } else {
        understanding_reference *ur = CREATE(understanding_reference);
        *ur = ur_being_parsed;
        ==> { -, ur };
    }

§14. Each of the items in the object phrase list is matched against:

<understand-as-this> ::=
    ... |                                       ==> { 0, - }; Clear UT vars14.1; return preform_lookahead_mode;  match only when looking ahead
    a mistake |                                 ==> { 0, - }; ur_being_parsed.gv_result = GV_IS_COMMAND; ur_being_parsed.mistaken = TRUE;
    a mistake ( <quoted-text> ) |               ==> { 0, - }; ur_being_parsed.gv_result = GV_IS_COMMAND; ur_being_parsed.mistaken = TRUE; ur_being_parsed.mword = R[1]
    a mistake ... |                             ==> Issue PM_TextlessMistake problem14.2
    the plural of <understand-ref> |            ==> { R[1], - }; ur_being_parsed.pluralised_reference = TRUE;
    plural of <understand-ref> |                ==> { R[1], - }; ur_being_parsed.pluralised_reference = TRUE;
    <quoted-text> |                             ==> { 0, - }; ur_being_parsed.gv_result = GV_IS_TOKEN;
    <understand-ref> ( with nouns reversed ) |  ==> { R[1], - }; ur_being_parsed.reversed_reference = TRUE;
    <understand-ref>                            ==> { pass 1 }

<understand-ref> ::=
    <action-name> |                             ==> { 0, - }; ur_being_parsed.an_reference = RP[1];
    <s-descriptive-type-expression> |           ==> { 0, - }; ur_being_parsed.spec_reference = RP[1];
    <s-variable> |                              ==> Issue PM_UnderstandVariable problem14.3
    ...                                         ==> Issue PM_UnderstandVague problem14.4

§14.1. Clear UT vars14.1 =

    ur_being_parsed.reference_text = W;
    ur_being_parsed.mword = -1;
    ur_being_parsed.mistaken = FALSE;
    ur_being_parsed.pluralised_reference = FALSE;
    ur_being_parsed.reversed_reference = FALSE;
    ur_being_parsed.an_reference = NULL;
    ur_being_parsed.spec_reference = NULL;
    ur_being_parsed.next = NULL;
    ur_being_parsed.gv_result = GV_IS_OBJECT;

§14.2. Issue PM_TextlessMistake problem14.2 =

    StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_TextlessMistake),
        "when 'understand' results in a mistake it can only be "
        "followed by a textual message in brackets",
        "so for instance 'understand \"take\" as a mistake "
        "(\"In this sort of game, a noun is required there.\").'");
    ==> { -1, - };

§14.3. Issue PM_UnderstandVariable problem14.3 =

    LOG("Offending pseudo-meaning is: %W\n", W);
    StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_UnderstandVariable),
        "this meaning is a value that varies",
        "whereas I need something fixed. "
        "(The most common case of this is saying that something should be "
        "understood as 'the player', which is actually a variable, because "
        "the perspective of play can change. Writing 'yourself' instead will "
        "usually do.)");
    ==> { -1, - };

§14.4. Issue PM_UnderstandVague problem14.4 =

    LOG("Offending pseudo-meaning is: %W\n", W);
    Actually issue PM_UnderstandVague problem14.4.1;
    ==> { -1, - };

§14.4.1. Actually issue PM_UnderstandVague problem14.4.1 =

    StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_UnderstandVague),
        "'understand ... as ...' should be followed "
        "by a meaning",
        "which might be an action (e.g., "
        "'understand \"take [something]\" as taking'), a "
        "thing ('understand \"stove\" as the oven') or more "
        "generally a value ('understand \"huitante\" as 80'), "
        "or a named token for use in further grammar "
        "('understand \"near [something]\" as \"[location "
        "phrase]\"'). Also, the meaning needs to be precise, "
        "so 'understand \"x\" as a number' is not "
        "allowed - it does not say which number.");

§15. The second form of the sentence has an object phrase like so:

Understand the command "snatch" as "take".

Here the grammar is very simple, and the object can't be a list.

<understand-command-sentence-object> ::=
    ... when/while ... |  ==> Issue PM_UnderstandCommandWhen problem15.1
    something new |       ==> { 0, - }
    <quoted-text> |       ==> { Wordings::first_wn(W), - }
    ...                   ==> Issue PM_NotOldCommand problem15.2

§15.1. Issue PM_UnderstandCommandWhen problem15.1 =

    StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_UnderstandCommandWhen),
        "'understand the command ... as ...' is not allowed to have a "
        "'... when ...' clause",
        "for the moment at any rate.");
    ==> { -1, - };

§15.2. Issue PM_NotOldCommand problem15.2 =

    Actually issue PM_NotOldCommand problem15.2.2;
    ==> { -1, - };

§16. The third and final form of the sentence has an object phrase like so:

Understand the unbroken property as describing the pot.

Once again, the object can't be a list. Syntactically the item(s) referred to or described can be of any kind, but in fact we restrict to kinds of object.

<understand-property-sentence-object> ::=
    <understand-property-sentence-object-unconditional> when/while ... |  ==> { 2, RP[1], <<level>> = R[1] }
    <understand-property-sentence-object-unconditional>                   ==> { 1, RP[1], <<level>> = R[1] }

<understand-property-sentence-object-unconditional> ::=
    referring to <understand-property-reference> |  ==> { 1, RP[1] }
    describing <understand-property-reference> |    ==> { 2, RP[1] }
    ...                                             ==> Issue PM_BadUnderstandProperty problem16.2

<understand-property-reference> ::=
    <k-kind> |              ==> Make reference from kind, if a kind of object16.1
    <instance-of-object> |  ==> { 0, Instances::as_subject(RP[1]) }
    ...                     ==> Issue PM_BadUnderstandPropertyAs problem16.3

§16.1. Make reference from kind, if a kind of object16.1 =

    kind *K = RP[1];
    if (Kinds::Behaviour::is_subkind_of_object(K)) {
        ==> { -, KindSubjects::from_kind(K) };
    } else {
        ==> { fail production };
    }

§16.2. Issue PM_BadUnderstandProperty problem16.2 =

    StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_BadUnderstandProperty),
        "'understand the ... property as ...' is only allowed if "
        "followed by 'describing ...' or 'referring to ...'",
        "so for instance 'understand the transparent property as "
        "describing a container.'");
    ==> { 0, - };

§16.3. Issue PM_BadUnderstandPropertyAs problem16.3 =

    StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_BadUnderstandPropertyAs),
        "I don't understand what single thing or kind of thing that refers to",
        "but it does need to be an object (or kind of object) and not "
        "some other sort of value. For instance, 'understand the transparent "
        "property as describing a container.' is okay because 'a container' "
        "is a kind of object.");
    ==> { -, NULL };

§17.

void PL::Parsing::understand_sentence(wording W, wording ASW) {
    LOGIF(GRAMMAR, "Parsing understand <%W> as <%W>\n", W, ASW);
    if (problem_count > base_problem_count) return;
    <understand-sentence-subject>(W);
    if (problem_count > base_problem_count) return;
    understanding_item *ui_list = <<rp>>;
    int form = <<r>>;
    switch (form) {
        case COMMAND_UNDERSTAND_FORM: Process Understand command17.1; break;
        case PROPERTY_UNDERSTAND_FORM: Process Understand property17.2; break;
        case GRAMMAR_UNDERSTAND_FORM:  and
        case NOTHING_UNDERSTAND_FORM: Process Understand grammar17.3; break;
    }
}

§17.1. Process Understand command17.1 =

    <understand-command-sentence-object>(ASW);
    if (problem_count > base_problem_count) return;
    wording W = (<<r>> != 0) ? (Wordings::one_word(<<r>>)) : EMPTY_WORDING;
    for (; ui_list; ui_list = ui_list->next) {
        if (problem_count > base_problem_count) break;
        PL::Parsing::understand_the_command(ui_list->quoted_text, W);
    }

§17.2. Process Understand property17.2 =

    <understand-property-sentence-object>(ASW);
    if (problem_count > base_problem_count) return;
    wording UW = EMPTY_WORDING;
    inference_subject *subj = <<rp>>;
    if (<<r>> == 2) UW = GET_RW(<understand-property-sentence-object>, 1);
    for (; ui_list; ui_list = ui_list->next) {
        if (problem_count > base_problem_count) break;
        PL::Parsing::understand_property_block(ui_list->quoted_property, <<level>>, subj, UW);
    }

§17.3. Process Understand grammar17.3 =

    <understand-sentence-object>(ASW);
    if (problem_count > base_problem_count) return;
    understanding_reference *ur_list_from = <<rp>>;
    wording UW = EMPTY_WORDING;
    if (<<r>> == 2) UW = GET_RW(<understand-sentence-object>, 1);
    if (form == NOTHING_UNDERSTAND_FORM) {
        understanding_reference *ur_list;
        for (ur_list = ur_list_from; ur_list; ur_list = ur_list->next) {
            if (problem_count > base_problem_count) break;
            PL::Parsing::understand_nothing(ur_list, UW);
        }
    } else {
        for (; ui_list; ui_list = ui_list->next) {
            understanding_reference *ur_list;
            for (ur_list = ur_list_from; ur_list; ur_list = ur_list->next) {
                if (problem_count > base_problem_count) break;
                PL::Parsing::understand_block(ui_list->quoted_text, ur_list, UW, FALSE);
            }
        }
    }

§15.2.1. Understand command verbs. These sentences allow us to control the assignment of command verbs such as TAKE or EXAMINE to grammars, which will normally be an automatic process based on grammar lines (see below). We can make one command verb an alias for another, or revoke this by making it "something new".

After some debate, we decided that it ought to be legal to declare "Understand the command "wibble" as something new" even in cases where no "wibble" command existed already: extensions might want this to assure that they have exclusive use of a command, for instance. So the problem message for this case is now commented out.

void PL::Parsing::understand_the_command(wording W, wording ASW) {
    W = Wordings::last_word(W);
    Word::dequote(Wordings::first_wn(W));
    wchar_t *p = Lexer::word_text(Wordings::first_wn(W));
    for (int i=0; p[i]; i++)
        if (p[i] == ' ') {
            StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_SpacyCommand),
                "'understand the command ... as ...' is only allowed when "
                "the old command is a single word",
                "so for instance 'understand the command \"capture\" as \"get\"' "
                "is okay, but 'understand the command \"capture the flag\" as "
                "\"get\"' is not.");
            break;
        }
    grammar_verb *gv = PL::Parsing::Verbs::find_command(W);

    if (Wordings::empty(ASW)) {
        if (gv) PL::Parsing::Verbs::remove_command(gv, W);
    } else {
        if (gv) {
            if (PL::Parsing::Verbs::is_empty(gv)) {
                DESTROY(gv, grammar_verb);
                gv = NULL;
            } else {
                StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_NotNewCommand),
                    "'understand the command ... as ...' is only allowed when "
                    "the new command has no meaning already",
                    "so for instance 'understand \"drop\" as \"throw\"' is not "
                    "allowed because \"drop\" already has a meaning.");
                return;
            }
        }
        Word::dequote(Wordings::first_wn(ASW));
        gv = PL::Parsing::Verbs::find_command(ASW);
        if (gv == NULL) {
            Actually issue PM_NotOldCommand problem15.2.2;
        } else {
            PL::Parsing::Verbs::add_command(gv, W);
        }
    }
}

§15.2.2. Actually issue PM_NotOldCommand problem15.2.2 =

    StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_NotOldCommand),
        "'understand the command ... as ...' should end with a command "
        "already defined",
        "as in 'understand the command \"steal\" as \"take\"'. (This "
        "problem is sometimes seen when the wrong sort of Understand... "
        "sentence has been used: 'Understand the command \"steal\" as "
        "\"take\".' tells me to treat the command STEAL as a "
        "synonym for TAKE when reading the player's commands, whereas "
        "'Understand \"steal [something]\" as taking.' tells me that "
        "here is a specific grammar for what can be said using the "
        "STEAL command.)");

§18. Understand property names.

void PL::Parsing::understand_property_block(property *pr, int level, inference_subject *subj, wording WHENW) {
    if ((Properties::is_either_or(pr) == FALSE) &&
        (Str::len(Kinds::Behaviour::get_recognition_only_GPR(ValueProperties::kind(pr))) == 0) &&
        ((Kinds::Behaviour::is_object(ValueProperties::kind(pr))) ||
            (Kinds::Behaviour::request_I6_GPR(ValueProperties::kind(pr)) == FALSE))) {
        StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_BadReferringProperty),
            "that property is of a kind which I can't recognise in "
            "typed commands",
            "so that it cannot be understand as describing or referring to "
            "something. I can understand either/or properties, properties "
            "with a limited list of named possible values, numbers, times "
            "of day, or units; but certain built-into-Inform kinds of value "
            "(like snippet or rulebook, for instance) I can't use.");
    }
    if (Visibility::seek(pr, subj, level, WHENW) == FALSE) {
        StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_UnknownUnpermittedProperty),
            "that property is not allowed for the thing or kind in question",
            "just as (ordinarily) 'understand the open property as describing a "
            "device' would not be allowed because it makes no sense to call a "
            "device 'open'.");
    }
}

§19.

void PL::Parsing::understand_nothing(understanding_reference *ur, wording WHENW) {
    if ((ur == NULL) || (ur->gv_result != GV_IS_OBJECT) || (ur->an_reference == NULL)) {
        StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_UnderstandNothingNonAction),
            "'Understand nothing as ...' must be followed by an action",
            "such as 'Understand nothing as taking.'");
    } else if (Wordings::nonempty(WHENW)) {
        StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_UnderstandNothingWhen),
            "'Understand nothing as ...' must be unconditional",
            "so your 'when' or 'while' condition will have to go.");
    } else {
        action_name *an = ur->an_reference;
        LOGIF(GRAMMAR_CONSTRUCTION, "Understand nothing as: $l\n", an);
        PL::Actions::remove_gl(an);
        grammar_verb *gv;
        LOOP_OVER(gv, grammar_verb) PL::Parsing::Verbs::remove_action(gv, an);
    }
}

§20.

void PL::Parsing::understand_block(wording W, understanding_reference *ur, wording WHENW,
    int table_entry) {
    int gv_is = GV_IS_COMMAND,
        reversed = FALSE, mistake_text_at = 0, mistakenly = FALSE, pluralised = FALSE;
    wording file_under = EMPTY_WORDING;
    wording XW = EMPTY_WORDING;
    kind *K = NULL;
    action_name *an = NULL;
    grammar_line *gl = NULL;
    parse_node *to_pn = NULL;
    inference_subject *subj = NULL;
    property *gv_prn = NULL;
    parse_node *gl_value = NULL;
    pcalc_prop *u_prop = NULL;

    if (problem_count > base_problem_count) return;
    if (<quoted-text>(W) == FALSE) {
        if (table_entry)
            StandardProblems::sentence_problem(Task::syntax_tree(), _p_(BelievedImpossible),
                "a table entry in a 'topic' column must be a single double-quoted "
                "text",
                "such as \"eternity\" or \"peruvian skies\".");
        else if (TEST_COMPILATION_MODE(SPECIFICATIONS_CMODE))
            StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_NontextualUnderstandInAP),
                "the topic here should be in the form of a textual description",
                "as in 'asking about \"[something]\"'.");
        else
            StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_NontextualUnderstand),
                "'understand' should be followed by a textual description",
                "as in 'understand \"take [something]\" as taking the noun'.");
        return;
    }
    if (Word::well_formed_text_routine(Lexer::word_text(Wordings::first_wn(W))) == FALSE) {
        StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_UnderstandMismatch),
            "'understand' should be followed by text in which brackets "
            "'[' and ']' match",
            "so for instance 'understand \"take [something]\" as taking the noun' "
            "is fine, but 'understand \"take]\" as taking' is not.");
        return;
    }
    mistake_text_at = 0;
    mistakenly = FALSE;
    if (ur == NULL) gv_is = GV_IS_CONSULT;
    else {
        an = ur->an_reference;
        pluralised = ur->pluralised_reference;
        reversed = ur->reversed_reference;
        if (ur->mword >= 0) mistake_text_at = ur->mword;
        if (ur->mistaken) mistakenly = TRUE;
        gv_is = ur->gv_result;

        if (gv_is == GV_IS_OBJECT) {
            gv_is = GV_IS_COMMAND;
            if (an == NULL) {
                instance *target;
                parse_node *spec = ur->spec_reference;
                target = Specifications::object_exactly_described_if_any(spec);
                if (target) {
                    subj = Instances::as_subject(target);
                    gv_is = GV_IS_OBJECT;
                    if (Descriptions::is_qualified(spec)) {
                        LOG("Offending description: $T", spec);
                        StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_UnderstandAsQualified),
                            "I cannot understand text as meaning an object "
                            "qualified by relative clauses or properties",
                            "only a specific thing, a specific value or a kind. "
                            "(But the same effect can usually be achieved with "
                            "a 'when' clause. For instance, although 'Understand "
                            "\"bad luck\" as the broken mirror' is not allowed, "
                            "'Understand \"bad luck\" as the mirror when the "
                            "mirror is broken' produces the desired effect.)");
                        return;
                    }
                } else {
                    RetryValue:
                    LOGIF(GRAMMAR_CONSTRUCTION, "Understand as specification: $T", spec);
                    if ((Specifications::is_kind_like(spec)) &&
                        (Kinds::Behaviour::is_object(Specifications::to_kind(spec)) == FALSE)) goto ImpreciseProblemMessage;
                    if (Specifications::is_phrasal(spec)) goto ImpreciseProblemMessage;
                    if (Rvalues::is_nothing_object_constant(spec)) goto ImpreciseProblemMessage;
                    if (Rvalues::is_rvalue(spec)) {
                        K = Node::get_kind_of_value(spec);
                        if (Kinds::Behaviour::request_I6_GPR(K)) {
                            gl_value = spec;
                            gv_is = GV_IS_VALUE;
                        } else {
                            if (Kinds::get_construct(K) == CON_activity)
                            StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_UnderstandAsActivity),
                                "this 'understand ... as ...' gives text "
                                "meaning an activity",
                                "rather than an action. Since activities "
                                "happen when Inform decides they need to "
                                "happen, not in response to typed commands, "
                                "this doesn't make sense.");
                            else
                            StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_UnderstandAsBadValue),
                                "'understand ... as ...' gives text "
                                "meaning a value whose kind is not allowed",
                                "and should be a value such as 100.");
                            return;
                        }
                    } else if (Specifications::is_description(spec)) {
                        if ((Descriptions::to_instance(spec) == NULL) &&
                            (Kinds::Behaviour::is_subkind_of_object(Specifications::to_kind(spec)) == FALSE)
                            && (Descriptions::number_of_adjectives_applied_to(spec) == 1)
                            && (AdjectivalPredicates::parity(Propositions::first_unary_predicate(Specifications::to_proposition(spec), NULL)))) {
                            adjective *aph =
                                AdjectivalPredicates::to_adjective(Propositions::first_unary_predicate(Specifications::to_proposition(spec), NULL));
                            instance *q = AdjectiveAmbiguity::has_enumerative_meaning(aph);
                            if (q) {
                                spec = Rvalues::from_instance(q);
                                goto RetryValue;
                            }
                            property *prn = AdjectiveAmbiguity::has_either_or_property_meaning(aph, NULL);
                            if (prn) {
                                gv_is = GV_IS_PROPERTY_NAME;
                                gv_prn = prn;
                                LOGIF(GRAMMAR_CONSTRUCTION, "Grammar confirmed for property $Y\n", gv_prn);
                            }
                        }
                        if ((Descriptions::is_qualified(spec)) && (gv_prn == NULL)) {
                            u_prop = Propositions::copy(Descriptions::to_proposition(spec));
                            spec = Specifications::from_kind(Specifications::to_kind(spec));
                        }
                        kind *K = Specifications::to_kind(spec);
                        if ((K) && (Kinds::Behaviour::is_subkind_of_object(K))) {
                            subj = KindSubjects::from_kind(K);
                            gv_is = GV_IS_OBJECT;
                        } else if (gv_prn == NULL) goto ImpreciseProblemMessage;
                    } else {
                        ImpreciseProblemMessage:
                        LOG("Offending pseudo-meaning is: $T", spec);
                        Actually issue PM_UnderstandVague problem14.4.1;
                        return;
                    }
                }
            }
        }
    }

    if ((pluralised) && (gv_is != GV_IS_OBJECT)) {
        StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_UnderstandPluralValue),
            "'understand' as a plural can only apply to things, rooms or kinds "
            "of things or rooms",
            "so 'Understand \"paperwork\" as the plural of a document.' is "
            "fine (assuming a document is a kind of thing), but 'Understand "
            "\"dozens\" as the plural of 12' is not.");
        return;
    }

    int i, skip = FALSE, literal_punct = FALSE; wchar_t *p = Lexer::word_text(Wordings::first_wn(W));
    for (i=0; p[i]; i++) {
        if (p[i] == '[') skip = TRUE;
        if (p[i] == ']') skip = FALSE;
        if (skip) continue;
        if ((p[i] == '.') || (p[i] == ',') ||
            (p[i] == '!') || (p[i] == '?') || (p[i] == ':') || (p[i] == ';'))
            literal_punct = TRUE;
    }
    if (literal_punct) {
        StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_LiteralPunctuation),
            "'understand' text cannot contain literal punctuation",
            "or more specifically cannot contain any of these: . , ! ? : ; "
            "since they are already used in various ways by the parser, and "
            "would not correctly match here.");
        return;
    }

    XW = Feeds::feed_C_string_full(Lexer::word_text(Wordings::first_wn(W)), TRUE, GRAMMAR_PUNCTUATION_MARKS);
    to_pn = Diagrams::new_UNPARSED_NOUN(W);
    PL::Parsing::Tokens::break_into_tokens(to_pn, XW);
    if (to_pn->down == NULL) {
        StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_UnderstandEmptyText),
            "'understand' should be followed by text which contains at least "
            "one word or square-bracketed token",
            "so for instance 'understand \"take [something]\" as taking' "
            "is fine, but 'understand \"\" as the fog' is not. The same "
            "applies to the contents of 'topic' columns in tables, since "
            "those are also instructions for understanding.");
        return;
    }
    if (gv_is == GV_IS_COMMAND) {
        LOGIF(GRAMMAR_CONSTRUCTION, "Command grammar: $T\n", to_pn);

        LOOP_THROUGH_WORDING(i, XW)
            if (i < Wordings::last_wn(XW))
                if ((compare_word(i, COMMA_V)) && (compare_word(i+1, COMMA_V))) {
                    StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_UnderstandCommaCommand),
                        "'understand' as an action cannot involve a comma",
                        "since a command leading to an action never does. "
                        "(Although Inform understands commands like 'PETE, LOOK' "
                        "only the part after the comma is read as an action command: "
                        "the part before the comma is read as the name of someone, "
                        "according to the usual rules for parsing a name.) "
                        "Because of the way Inform processes text with square "
                        "brackets, this problem message is also sometimes seen "
                        "if empty square brackets are used, as in 'Understand "
                        "\"bless []\" as blessing.'");
                    return;
                }

        if (PL::Parsing::Tokens::is_literal(to_pn->down) == FALSE)
            file_under = EMPTY_WORDING;  this will go into the no verb verb
        else file_under = Wordings::first_word(Node::get_text(to_pn->down));
    }
    LOGIF(GRAMMAR, "GV is %d, an is $l, file under is %W\n", gv_is, an, file_under);
    if (gv_is != GV_IS_COMMAND) gl = PL::Parsing::Lines::new(Wordings::first_wn(W), NULL, to_pn, reversed, pluralised);
    else gl = PL::Parsing::Lines::new(Wordings::first_wn(W), an, to_pn, reversed, pluralised);
    if (mistakenly) PL::Parsing::Lines::set_mistake(gl, mistake_text_at);
    if (Wordings::nonempty(WHENW)) {
        PL::Parsing::Lines::set_understand_when(gl, WHENW);
        if (gv_is == GV_IS_CONSULT) {
            StandardProblems::sentence_problem(Task::syntax_tree(), _p_(BelievedImpossible),  at present, I7 syntax prevents this anyway
                "'when' cannot be used with this kind of 'Understand'",
                "for the time being at least.");
            return;
        }
    }
    if (Wordings::nonempty(WHENW)) {
        PL::Parsing::Lines::set_understand_when(gl, WHENW);
        if (gv_is == GV_IS_CONSULT) {
            StandardProblems::sentence_problem(Task::syntax_tree(), _p_(BelievedImpossible),  at present, I7 syntax prevents this anyway
                "'when' cannot be used with this kind of 'Understand'",
                "for the time being at least.");
            return;
        }
    }
    if (u_prop) {
        PL::Parsing::Lines::set_understand_prop(gl, u_prop);
        if (gv_is == GV_IS_CONSULT) {
            StandardProblems::sentence_problem(Task::syntax_tree(), _p_(BelievedImpossible),  at present, I7 syntax prevents this anyway
                "'when' cannot be used with this kind of 'Understand'",
                "for the time being at least.");
            return;
        }
    }

    switch(gv_is) {
        case GV_IS_TOKEN:
            XW = Feeds::feed_C_string_full(Lexer::word_text(Wordings::first_wn(ur->reference_text)), TRUE, GRAMMAR_PUNCTUATION_MARKS);
            LOGIF(GRAMMAR_CONSTRUCTION, "GV_IS_TOKEN as words: %W\n", XW);
            if (PL::Parsing::valid_new_token_name(XW) == FALSE) {
                StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_UnderstandAsCompoundText),
                    "if 'understand ... as ...' gives the meaning as text "
                    "then it must describe a single new token",
                    "so that 'Understand \"group four/five/six\" as "
                    "\"[department]\"' is legal (defining a new token "
                    "\"[department]\", or adding to its definition if it "
                    "already existed) but 'Understand \"take [thing]\" "
                    "as \"drop [thing]\"' is not allowed, and would not "
                    "make sense, because \"drop [thing]\" is a combination "
                    "of two existing tokens - not a single new one.");
            }
            PL::Parsing::Verbs::add_line(PL::Parsing::Verbs::named_token_new(Wordings::trim_both_ends(Wordings::trim_both_ends(XW))), gl);
            break;
        case GV_IS_COMMAND:
            PL::Parsing::Verbs::add_line(PL::Parsing::Verbs::find_or_create_command(file_under), gl);
            break;
        case GV_IS_OBJECT:
            PL::Parsing::Verbs::add_line(PL::Parsing::Verbs::for_subject(subj), gl);
            break;
        case GV_IS_VALUE:
            PL::Parsing::Lines::set_single_type(gl, gl_value);
            PL::Parsing::Verbs::add_line(PL::Parsing::Verbs::for_kind(K), gl);
            break;
        case GV_IS_PROPERTY_NAME:
            PL::Parsing::Verbs::add_line(PL::Parsing::Verbs::for_prn(gv_prn), gl);
            break;
        case GV_IS_CONSULT:
            PL::Parsing::Lines::set_single_type(gl, gl_value);
            PL::Parsing::Verbs::add_line(
                PL::Parsing::Tokens::General::get_consultation_gv(), gl);
            break;
    }
}

int PL::Parsing::valid_new_token_name(wording W) {
    int cc=0;
    LOOP_THROUGH_WORDING(i, W)
        if (compare_word(i, COMMA_V)) cc++;
    Word::dequote(Wordings::first_wn(W));
    if (*(Lexer::word_text(Wordings::first_wn(W))) != 0) return FALSE;
    Word::dequote(Wordings::last_wn(W));
    if (*(Lexer::word_text(Wordings::last_wn(W))) != 0) return FALSE;
    if (cc != 2) return FALSE;
    return TRUE;
}