Here we handle quoted command grammar arising in source text.

§1. All quoted command grammar in the source text, whether from an Understand sentence, a table column of topics, or a condition to match snippets of commands, ends up here: see Understand Sentences for how.

The reference ur tells us what the grammar should refer to; it is non-NULL for grammar originating in Understand sentences, but NULL for grammar originating elsewhere. In that case, the reference is understood to be to the special consultation grammar.

void CommandGrammarSource::in(wording W, understanding_reference *ur, wording WHENW) {
    int cg_is = CG_IS_COMMAND;
    if (ur == NULL) {
        UnderstandGeneralTokens::prepare_consultation_gv();
        cg_is = CG_IS_CONSULT;
    }

    int 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;
    cg_line *cgl = NULL;
    parse_node *to_pn = NULL;
    inference_subject *subj = NULL;
    property *cg_prn = NULL;
    parse_node *cgl_value = NULL;
    pcalc_prop *u_prop = NULL;

    mistake_text_at = 0;
    mistakenly = FALSE;
    if (ur) {
        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;
        cg_is = ur->cg_result;
        if (cg_is == CG_IS_OBJECT) {
            cg_is = CG_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);
                    cg_is = CG_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)) {
                            cgl_value = spec;
                            cg_is = CG_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) {
                                cg_is = CG_IS_PROPERTY_NAME;
                                cg_prn = prn;
                                LOGIF(GRAMMAR_CONSTRUCTION, "Grammar confirmed for property $Y\n", cg_prn);
                            }
                        }
                        if ((Descriptions::is_qualified(spec)) && (cg_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);
                            cg_is = CG_IS_OBJECT;
                        } else if (cg_prn == NULL) goto ImpreciseProblemMessage;
                    } else {
                        ImpreciseProblemMessage:
                        LOG("Offending pseudo-meaning is: $T", spec);
                        Understand::issue_PM_UnderstandVague();
                        return;
                    }
                }
            }
        }
    }

    if ((pluralised) && (cg_is != CG_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);
    UnderstandTokens::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 (cg_is == CG_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 (UnderstandTokens::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, "CG is %d, an is $l, file under is %W\n", cg_is, an, file_under);
    if (cg_is != CG_IS_COMMAND) cgl = UnderstandLines::new(Wordings::first_wn(W), NULL, to_pn, reversed, pluralised);
    else cgl = UnderstandLines::new(Wordings::first_wn(W), an, to_pn, reversed, pluralised);
    if (mistakenly) UnderstandLines::set_mistake(cgl, mistake_text_at);
    if (Wordings::nonempty(WHENW)) {
        UnderstandLines::set_understand_when(cgl, WHENW);
        if (cg_is == CG_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)) {
        UnderstandLines::set_understand_when(cgl, WHENW);
        if (cg_is == CG_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) {
        UnderstandLines::set_understand_prop(cgl, u_prop);
        if (cg_is == CG_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(cg_is) {
        case CG_IS_TOKEN:
            XW = Feeds::feed_C_string_full(Lexer::word_text(Wordings::first_wn(ur->reference_text)), TRUE, GRAMMAR_PUNCTUATION_MARKS);
            LOGIF(GRAMMAR_CONSTRUCTION, "CG_IS_TOKEN as words: %W\n", XW);
            if (CommandGrammarSource::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.");
            }
            CommandGrammars::add_line(CommandGrammars::named_token_new(Wordings::trim_both_ends(Wordings::trim_both_ends(XW))), cgl);
            break;
        case CG_IS_COMMAND:
            CommandGrammars::add_line(CommandGrammars::find_or_create_command(file_under), cgl);
            break;
        case CG_IS_OBJECT:
            CommandGrammars::add_line(CommandGrammars::for_subject(subj), cgl);
            break;
        case CG_IS_VALUE:
            UnderstandLines::set_single_type(cgl, cgl_value);
            CommandGrammars::add_line(CommandGrammars::for_kind(K), cgl);
            break;
        case CG_IS_PROPERTY_NAME:
            CommandGrammars::add_line(CommandGrammars::for_prn(cg_prn), cgl);
            break;
        case CG_IS_CONSULT:
            UnderstandLines::set_single_type(cgl, cgl_value);
            CommandGrammars::add_line(
                UnderstandGeneralTokens::get_consultation_gv(), cgl);
            break;
    }
}

int CommandGrammarSource::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;
}