The parser for turning the text of the typed command into a proposed action by the player.


§1. A word about words. There are two slightly different versions of the command parser: one for the 16-bit architecture (i.e., the Z-machine), and the other for 32-bit (Glulx or C). The main but not the only difference is that characters typed into the parser are stored in single bytes in a 16-bit world, but will occupy words in a 32-bit world, and therefore can be arbitrary Unicode characters.

There are also minor differences in how the Z-machine stores the result of identifying dictionary words in typed commands.

In general, though, #Iftrue CHARSIZE == 1; will be used to conditionally compile code for the 16-bit parser, and its alternative — that CHARSIZE equals WORDSIZE — will mean the 32-bit parser.

Only when genuinely using some feature of the 16-bit architecture (i.e., of the Z-machine) will we write #Ifdef TARGET_ZCODE;. But this is only a convention: TARGET_ZCODE is defined here if and only if CHARSIZE is 1, and TARGET_GLULX is defined if and only if CHARSIZE is 4.

§2. Identification.

Constant COMMANDPARSERKIT = 1;

§3. Parser Error Numbers. The traditional ways in which the I6 library's parser, which we adopt here more or less intact, can give up on a player's command.

Constant STUCK_PE         = 1;
Constant UPTO_PE          = 2;
Constant NUMBER_PE        = 3;
Constant ANIMA_PE         = 4;
Constant CANTSEE_PE       = 5;
Constant TOOLIT_PE        = 6;
Constant NOTHELD_PE       = 7;
Constant MULTI_PE         = 8;
Constant MMULTI_PE        = 9;
Constant VAGUE_PE         = 10;
Constant EXCEPT_PE        = 11;
Constant VERB_PE          = 12;
Constant SCENERY_PE       = 13;
Constant ITGONE_PE        = 14;
Constant JUNKAFTER_PE     = 15;
Constant TOOFEW_PE        = 16;
Constant NOTHING_PE       = 17;
Constant ASKSCOPE_PE      = 18;
Constant NOTINCONTEXT_PE  = 19;
Constant BLANKLINE_PE     = 20; Not formally a parser error, but used by I7 as if
Constant ANIMAAGAIN_PE    = 21;
Constant COMMABEGIN_PE    = 22;
Constant MISSINGPERSON_PE = 23;
Constant ANIMALISTEN_PE   = 24;
Constant TOTALK_PE        = 25;

§4. Grammar Line Variables. This is the I6 library parser in mostly untouched form: reformatted for template file use, and with paragraph divisions, but otherwise hardly changed at all. It is a complex algorithm but one which is known to produce good results for the most part, and it is well understood from (at time of writing) fifteen years of use. A few I7 additions have been made, but none disrupting the basic method. For instance, I7's system for resolving ambiguities is implemented by providing a ChooseObjects routine, just as a user of the I6 library would do.

The I6 parser uses a huge number of global variables, which is not to modern programming tastes: in the early days of Inform, the parser was essentially written in assembly-language only lightly structured by C-like syntaxes, and the Z-machine's 240 globals were more or less registers. The I6 library made no distinction between which were "private" to the parser and which allowed to be accessed by the user's code at large. The I7 template does impose that boundary, though not very strongly: the variables defined in "Output.i6t" are for general access, while the ones below should only be read or written by the parser.

Global etype;                       parser error number if command not recognised
Global best_etype;                  Preferred error number so far
Global nextbest_etype;              Preferred one, if ASKSCOPE_PE disallowed

Global parser_inflection;           A property (usually "name") to find object names in
Global indirect_parser_inflection;  Set this to have parser_inflection called

Array pattern --> 32;               For the current pattern match
Global pcount;                      and a marker within it
Array pattern2 --> 32;              And another, which stores the best match
Global pcount2;                     so far

Array  line_ttype-->32;             For storing an analysed grammar line
Array  line_tdata-->32;
Array  line_token-->32;

Global nsns;                        Number of special_numbers entered so far

Global params_wanted;               Number of parameters needed (which may change in parsing)

Global inferfrom;                   The point from which the rest of the command must be inferred
Global inferword;                   And the preposition inferred
Global dont_infer_pronoun;          Do not infer an "it"
Global dont_infer;                  Another dull flag

Global cobj_flag = 0;

Global oops_from;                   The "first mistake" word number
Global saved_oops;                  Used in working this out
#Iftrue CHARSIZE == 1;
Array  oops_workspace -> 64;        Used temporarily by "oops" routine
#Ifnot;
Array  oops_workspace --> 64;       Used temporarily by "oops" routine
#Endif;

Global held_back_mode;              Flag: is there some input from last time
Global hb_wn;                       left over?  (And a save value for wn.)
                                    (Used for full stops and "then".)

Global usual_grammar_after;         Point from which usual grammar is parsed (it may vary from
                                    the above if user's routines match multi-word verbs)

Global dont_ask_for_clarification;  Allows deactivating clarification question

§5. Grammar Token Variables. More globals, but dealing at the level of individual tokens now.

Constant PATTERN_NULL = $ffff;      Entry for a token producing no text

Global found_ttype;                 Used to break up tokens into type
Global found_tdata;                 and data (by AnalyseToken)
Global token_filter;                For noun filtering by user routines

Global length_of_noun;              Set by NounDomain to no of words in noun

Global lookahead;                   The token after the one now being matched

Global multi_mode;                  Multiple mode
Global multi_wanted;                Number of things needed in multitude
Global multi_had;                   Number of things actually found
Global multi_context;               What token the multi-obj was accepted for

Global indef_type;                  Bit-map holding types of specification
Global indef_wanted;                Number of items wanted (INDEF_ALL_WANTED for all)
Constant INDEF_ALL_WANTED = 32767;
Global indef_guess_p;               Plural-guessing flag
Global indef_owner;                 Object which must hold these items
Global indef_cases;                 Possible gender and numbers of them
Global indef_possambig;             Has a possibly dangerous assumption
                                    been made about meaning of a descriptor?
Global indef_nspec_at;              Word at which a number like "two" was parsed
                                    (for backtracking)
Global allow_plurals;               Whether plurals presently allowed or not

Global take_all_rule;               Slightly different rules apply to "take all" than other uses
                                    of multiple objects, to make adjudication produce more
                                    pragmatically useful results
                                    (Not a flag: possible values 0, 1, 2)

Global current_noun_is_plural;      True or false
Global pronoun__word;               Saved value
Global pronoun__obj;                Saved value

Constant comma_word = 'comma,';     An "untypeable word" used to substitute
                                    for commas in parse buffers

§6. Match List Variables. The most difficult tokens to match are those which refer to objects, since there is such a variety of names which can be given to any individual object, and we don't of course know which object or objects are meant. We store the possibilities (up to MATCH_LIST_WORDS, anyway) in a data structure called the match list.

Constant MATCH_LIST_WORDS = WorldModelKit`MULTI_OBJ_LIST_SIZE_CFGV;
Array  match_list --> MATCH_LIST_WORDS;    An array of matched objects so far
Array  match_classes --> MATCH_LIST_WORDS; An array of equivalence classes for them
Array  match_scores --> MATCH_LIST_WORDS;  An array of match scores for them
Global number_matched;              How many items in it?  (0 means none)
Global number_of_classes;           How many equivalence classes?
Global match_length;                How many words long are these matches?
Global match_from;                  At what word of the input do they begin?

§7. Words. The player's command is broken down into a numbered sequence of words, which break at spaces or certain punctuation (see the DM4). The numbering runs upwards from 1 to WordCount(). The following utility routines provide access to words in the current command; because buffers have different definitions in Z and Glulx, so these routines must vary also.

In Z, the actual text of each word is stored as a sequence of ZSCII values in a -> (byte) array. In G, the text is a sequence of Unicode code counts in a --> (word) array. In either case, the array has address WordAddress(x) and character length WordLength(x).

We picture the command as a stream of words to be read one at a time, with the global variable wn being the "current word" marker. NextWord, which takes no arguments, returns:

The current word marker wn is always advanced.

NextWordStopped does the same, but returns \(-1\) when wn is out of range (e.g., by having advanced past the last word in the command).

#Iftrue CHARSIZE == 1;
[ WordCount; return parse->1; ];
[ WordAddress wordnum; return buffer + parse->(wordnum*4+1); ];
[ WordLength wordnum; return parse->(wordnum*4); ];
#Ifnot;
[ WordCount; return parse-->0; ];
[ WordAddress wordnum; return buffer + WORDSIZE * parse-->(wordnum*3); ];
[ WordLength wordnum; return parse-->(wordnum*3 - 1); ];
#Endif;

[ WordFrom w p i j wc;
    #Iftrue CHARSIZE == 1;
    wc = p->1; i = w*2-1;
    #Ifnot;
    wc = p-->0; i = w*3-2;
    #Endif;
    if ((w < 1) || (w > wc)) return 0;
    j = p-->i;
    if (j == ',//') j = comma_word;
    if (j == './/') j = THEN1__WD;
    return j;
];

[ NextWord i j wc;
    #Iftrue CHARSIZE == 1;
    wc = parse->1; i = wn*2-1;
    #Ifnot;
    wc = parse-->0; i = wn*3-2;
    #Endif;
    wn++;
    if ((wn < 2) || (wn > wc+1)) return 0;
    j = parse-->i;
    if (j == ',//') j = comma_word;
    if (j == './/') j = THEN1__WD;
    return j;
];

[ NextWordStopped wc;
    #Iftrue CHARSIZE == 1;
    wc = parse->1;
    #Ifnot;
    wc = parse-->0;
    #Endif;
    if ((wn < 1) || (wn > wc)) { wn++; return -1; }
    return NextWord();
];

§8. Snippets. Although the idea is arguably implicit in I6, the formal concept of "snippet" is new in I7. A snippet is a value which represents a word range in the command most recently typed by the player. These words number consecutively upwards from 1, as noted above. The correspondence between \((w_1, w_2)\), the word range, and \(V\), the number used to represent it as an I6 value, is: $$ V = 100w_1 + (w_2-w_1+1) $$ so that the remainder mod 100 is the number of words in the range. We require that \(1\leq w_1\leq w_2\leq N\), where \(N\) is the number of words in the current player's command. The entire command is therefore represented by: $$ C = 100 + N $$

A careless bug in this code lasted for many years: on a 16-bit architecture we have to remember that addresses in byte-addressable memory are potentially signed, and the loop for (i=from: i<=to: ...) will fail if the addresses are above 32K. This only rarely happens, but it can.

+replacing(from BasicInformKit) [ PrintSnippet snip from to i w1 w2;
    w1 = snip/100; w2 = w1 + (snip%100) - 1;
    if ((w2<w1) || (w1<1) || (w2>WordCount())) {
        if ((w1 == 1) && (w2 == 0)) rfalse;
        IssueRTP("SaidInvalidSnippet",
            "Attempt to say a snippet value which is currently invalid.",
            WorldModelKitRTPs);
        print "*** The snippet ran from words ", w1, " to ", w2, ".^";
        rtrue;
    }
    #Iftrue CHARSIZE == 1;
    from = WordAddress(w1); to = WordAddress(w2) + WordLength(w2) - 1;
    for (i=0: i<=to-from: i++) print (char) from->i;
    #Ifnot;
    from = WordAddress(w1); to = WordAddress(w2) + WORDSIZE * (WordLength(w2) - 1);
    for (i=from: i<=to: i=i+WORDSIZE) print (char) i-->0;
    #Endif;
];

[ SpliceSnippet snip t i w1 w2 nextw at endsnippet newlen saved;
    w1 = snip/100; w2 = w1 + (snip%100) - 1;
    if ((w2<w1) || (w1<1)) {
        if ((w1 == 1) && (w2 == 0)) return;
        IssueRTP("SplicedInvalidSnippet",
            "Attempt to splice a snippet value which is currently invalid.",
            WorldModelKitRTPs);
        print "*** The snippet ran from words ", w1, " to ", w2, ".^";
        rtrue;
    }
    @push say__p; @push say__pc;
    nextw = w2 + 1;
    #Iftrue CHARSIZE == 1;
    at = WordAddress(w1) - buffer;
    #ifnot;
    at = (WordAddress(w1) - buffer) / WORDSIZE;
    #endif;
    if (nextw <= WordCount()) endsnippet = 100*nextw + (WordCount() - nextw + 1);
    saved = buffer2-->0;
    buffer2-->0 = INPUT_BUFFER_LEN;
    newlen = VM_PrintToBuffer(buffer2, INPUT_BUFFER_LEN, SpliceSnippet__TextPrinter, t, endsnippet);
    #Iftrue CHARSIZE == 1;
    for (i=0: (i<newlen) && (at+i<INPUT_BUFFER_LEN): i++) buffer->(at+i) = buffer2->(WORDSIZE+i);
    buffer->1 = at+i;
    for (:at+i<INPUT_BUFFER_LEN:i++) buffer->(at+i) = ' ';
    #ifnot;
    for (i=0: (i<newlen) && (at+i<INPUT_BUFFER_LEN): i++) buffer-->(at+i) = buffer2-->(1+i);
    buffer-->0 = at+i;
    for (:at+i<INPUT_BUFFER_LEN:i++) buffer-->(at+i) = ' ';
    #endif;
    VM_Tokenise(buffer, parse);
    players_command = 100 + WordCount();
    buffer2-->0 = saved;
    @pull say__pc; @pull say__p;
];

[ SpliceSnippet__TextPrinter t endsnippet;
    TEXT_TY_Say(t);
    if (endsnippet) { print " "; PrintSnippet(endsnippet); }
];

[ SnippetIncludes test snippet w1 w2 wlen i j;
    w1 = snippet/100; w2 = w1 + (snippet%100) - 1;
    if ((w2<w1) || (w1<1)) {
        if ((w1 == 1) && (w2 == 0)) rfalse;
        IssueRTP("IncludedInvalidSnippet",
            "Attempt to match a snippet value which is currently invalid.",
            WorldModelKitRTPs);
        print "*** The snippet ran from words ", w1, " to ", w2, ".^";
        rtrue;
    }
    if (metaclass(test) == Routine) {
        wlen = snippet%100;
        for (i=w1, j=wlen: j>0: i++, j--) {
            if (((test)(i, 0)) ~= GPR_FAIL) return i*100+wn-i;
        }
    }
    rfalse;
];

[ SnippetMatches snippet topic_gpr rv;
    wn=1;
    if (topic_gpr == 0) rfalse;
    if (metaclass(topic_gpr) == Routine) {
        rv = (topic_gpr)(snippet/100, snippet%100);
        if (rv ~= GPR_FAIL) rtrue;
        rfalse;
    }
    IssueRTP("MatchedAgainstNonTopic",
        "Tried to see if a snippet of text matches something which is not a topic.",
        WorldModelKitRTPs);
    rfalse;
];

§9. Unpacking Grammar Lines. Grammar lines are sequences of tokens in an array built into the story file, but in a format which differs depending on the virtual machine in use, so the following code unpacks the data into more convenient if larger arrays which are VM-independent.

[ UnpackGrammarLine line_address i size;
    for (i=0 : i<32 : i++) {
        line_token-->i = ENDIT_TOKEN;
        line_ttype-->i = ELEMENTARY_TT;
        line_tdata-->i = ENDIT_TOKEN;
    }

    #Ifdef TARGET_ZCODE;

    action_to_be = 256*(line_address->0) + line_address->1;
    action_reversed = ((action_to_be & $400) ~= 0);
    action_to_be = action_to_be & $3ff;
    line_address--;
    size = 3;

    #Ifnot; GLULX

    @aloads line_address 0 action_to_be;
    action_reversed = (((line_address->2) & 1) ~= 0);
    line_address = line_address - 2;
    size = 5;

    #Endif;

    params_wanted = 0;
    for (i=0 : : i++) {
        line_address = line_address + size;
        if (line_address->0 == ENDIT_TOKEN) break;
        line_token-->i = line_address;
        AnalyseToken(line_address);
        if (found_ttype ~= PREPOSITION_TT) params_wanted++;
        line_ttype-->i = found_ttype;
        line_tdata-->i = found_tdata;
    }
    return line_address + 1;
];

[ AnalyseToken token;
    if (token == ENDIT_TOKEN) {
        found_ttype = ELEMENTARY_TT;
        found_tdata = ENDIT_TOKEN;
        return;
    }
    found_ttype = (token->0) & $$1111;
    found_tdata = (token+1)-->0;
];

§10. Keyboard Primitive. This is the primitive routine to read from the keyboard: it usually delegates this to a routine specific to the virtual machine being used, but sometimes uses a hacked version to allow TEST commands to work. (When a TEST is running, the text in the walk-through provided is fed into the buffer as if it had been typed at the keyboard.)

If a fn is provided, it is called to redraw the status line at the top of the main text window.

[ KeyboardPrimitive a_buffer a_table fn;
    #Ifdef DEBUG;
    TestKeyboardPrimitive(a_buffer, a_table, fn);
    #Ifnot;
    KeyboardMorePrimitive(a_buffer, a_table, fn);
    #Endif;
];

[ KeyboardMorePrimitive a_buffer a_table fn ix;
    if (fn) fn();
    VM_ReadKeyboard(a_buffer, a_table);
    if (BasicInformKit`ECHO_COMMANDS_CFGF) {
        print "** ";
        #Iftrue CHARSIZE == 1;
        for (ix=2: ix<=(a_buffer->1)+1: ix++) print (char) a_buffer->ix;
        #Ifnot;
        for (ix=0: ix<(a_buffer-->0): ix++) print (char) a_buffer-->(1+ix);
        #Endif;
        print "^";
    }
];

§11. Reading the Command. The Keyboard routine actually receives the player's words, putting the words in a_buffer and their dictionary addresses in a_table. It is assumed that the table is the same one on each (standard) call. Much of the code handles the OOPS and UNDO commands, which are not actions and do not pass through the rest of the parser. The undo state is saved — it is essentially an internal saved game, in the VM interpreter's memory rather than in an external file — and note that this is therefore also where execution picks up if an UNDO has been typed. Since UNDO recreates the former machine state perfectly, it might seem impossible to tell that an UNDO had occurred, but in fact the VM passes information back in the form of a return code from the relevant instruction, and this allows us to detect an undo. (We deal with it by printing the current location and asking another command.)

Keyboard can also be used by miscellaneous routines in the game to ask yes/no questions and the like, without invoking the rest of the parser.

The return value is the number of words typed.

[ Keyboard  a_buffer a_table  nw i w w2 x1 x2;
    sline1 = score; sline2 = turns;

    while (true) {
        Save the start of the buffer, in case "oops" needs to restore it
        #Iftrue CHARSIZE == 1;
        for (i=0 : i<64 : i++) oops_workspace->i = a_buffer->i;
        #Ifnot;
        for (i=0 : i<64 : i++) oops_workspace-->i = a_buffer-->i;
        #Endif;

        In case of an array entry corruption that shouldn't happen, but would be
        disastrous if it did:
        #Iftrue CHARSIZE == 1;
        a_buffer->0 = INPUT_BUFFER_LEN;
        a_table->0 = 15;  Allow to split input into this many words
        #Endif; TARGET_

        Print the prompt, and read in the words and dictionary addresses
        PrintPrompt();
        KeyboardPrimitive(a_buffer, a_table, DrawStatusLine);

        Set nw to the number of words
        #Iftrue CHARSIZE == 1;
        nw = a_table->1;
        #Ifnot;
        nw = a_table-->0;
        #Endif;

        If the line was blank, get a fresh line
        if (nw == 0) {
            @push etype; etype = BLANKLINE_PE;
            players_command = 100;
            BeginActivity(PRINTING_A_PARSER_ERROR_ACT);
            if (ForActivity(PRINTING_A_PARSER_ERROR_ACT) == false) {
                PARSER_ERROR_INTERNAL_RM('X', noun); new_line;
            }
            EndActivity(PRINTING_A_PARSER_ERROR_ACT);
            @pull etype;
            continue;
        }

        Unless the opening word was OOPS, return
        Conveniently, a_table-->1 is the first word on both the Z-machine and Glulx

        w = a_table-->1;
        if (w == OOPS1__WD or OOPS2__WD or OOPS3__WD) {
            if (oops_from == 0) { PARSER_COMMAND_INTERNAL_RM('A'); new_line; continue; }
            if (nw == 1) { PARSER_COMMAND_INTERNAL_RM('B'); new_line; continue; }
            if (nw > 2) { PARSER_COMMAND_INTERNAL_RM('C'); new_line; continue; }

            So now we know: there was a previous mistake, and the player has
            attempted to correct a single word of it.

            #Iftrue CHARSIZE == 1;
            for (i=0 : i<INPUT_BUFFER_LEN : i++) buffer2->i = a_buffer->i;
            x1 = a_table->9;  Start of word following "oops"
            x2 = a_table->8;  Length of word following "oops"
            #Ifnot;
            for (i=0 : i<INPUT_BUFFER_LEN : i++) buffer2-->i = a_buffer-->i;
            x1 = a_table-->6; Start of word following "oops"
            x2 = a_table-->5; Length of word following "oops"
            #Endif;

            Repair the buffer to the text that was in it before the "oops"
            was typed:
            #Iftrue CHARSIZE == 1;
            for (i=0 : i<64 : i++) a_buffer->i = oops_workspace->i;
            #Ifnot;
            for (i=0 : i<64 : i++) a_buffer-->i = oops_workspace-->i;
            #Endif;
            VM_Tokenise(a_buffer,a_table);

            Work out the position in the buffer of the word to be corrected:
            #Iftrue CHARSIZE == 1;
            w = a_table->(4*oops_from + 1); Start of word to go
            w2 = a_table->(4*oops_from);    Length of word to go
            #Ifnot;
            w = a_table-->(3*oops_from);      Start of word to go
            w2 = a_table-->(3*oops_from - 1); Length of word to go
            #Endif;

            Write spaces over the word to be corrected:
            #Iftrue CHARSIZE == 1;
            for (i=0 : i<w2 : i++) a_buffer->(i+w) = ' ';
            #Ifnot;
            for (i=0 : i<w2 : i++) a_buffer-->(i+w) = ' ';
            #Endif;

            if (w2 < x2) {
                If the replacement is longer than the original, move up...
                #Iftrue CHARSIZE == 1;
                for (i=INPUT_BUFFER_LEN-1 : i>=w+x2 : i--)
                    a_buffer->i = a_buffer->(i-x2+w2);
                #Ifnot;
                for (i=INPUT_BUFFER_LEN-1 : i>=w+x2 : i--)
                    a_buffer-->i = a_buffer-->(i-x2+w2);
                #Endif;

                ...increasing buffer size accordingly.
                #Iftrue CHARSIZE == 1;
                a_buffer->1 = (a_buffer->1) + (x2-w2);
                #Ifnot;
                a_buffer-->0 = (a_buffer-->0) + (x2-w2);
                #Endif;
            }

            Write the correction in:
            #Iftrue CHARSIZE == 1;
            for (i=0 : i<x2 : i++) a_buffer->(i+w) = buffer2->(i+x1);
            #Ifnot;
            for (i=0 : i<x2 : i++) a_buffer-->(i+w) = buffer2-->(i+x1);
            #Endif;

            VM_Tokenise(a_buffer, a_table);
            #Iftrue CHARSIZE == 1;
            nw = a_table->1;
            #Ifnot;
            nw = a_table-->0;
            #Endif;

            return nw;
        }

        Undo handling

        if ((w == UNDO1__WD or UNDO2__WD or UNDO3__WD) && (nw==1)) {
            Perform_Undo();
            continue;
        }
        i = VM_Save_Undo();
        if (CommandParserKit`UNDO_PREVENTION_CFGF) undo_flag = 0;
        else undo_flag = 2;
        if (i == -1) undo_flag = 0;
        if (i == 0) undo_flag = 1;
        if (i == 2) {
            DealWithUndo();
            continue;
        }
        return nw;
    }
];

[ DealWithUndo;
    VM_RestoreWindowColours();
    VM_Style(SUBHEADER_VMSTY);
    SL_Location(); print "^";
    VM_Style(NORMAL_VMSTY);
    IMMEDIATELY_UNDO_RM('E'); new_line;
];

§12. Parser Proper. The main parser routine is something of a leviathan, and it has traditionally been divided into 11 lettered parts:

This lettering has been preserved here, with the code under each letter now being the body of "Parser Letter A", "Parser Letter B" and so on.

Note that there are three different places where a return can happen. The routine returns only when a sensible request has been made; for a fairly thorough description of its output, which is written into the parser_results array and also into several globals (see "OrderOfPlay.i6t").

[ Parser__parse
    syntax line num_lines line_address i j k token l m inferred_go;
    cobj_flag = 0;
    parser_results-->ACTION_PRES = 0;
    parser_results-->NO_INPS_PRES = 0;
    parser_results-->INP1_PRES = 0;
    parser_results-->INP2_PRES = 0;
    meta = false;

§13. Parser Letter A. Get the input, do OOPS and AGAIN.

    if (held_back_mode) {
        held_back_mode = false; wn = hb_wn;
        if (verb_wordnum > 0) i = WordAddress(verb_wordnum); else i = WordAddress(1);
        j = WordAddress(wn);
        #Iftrue CHARSIZE == 1;
        if (i<=j) for (: i<j : i++) i->0 = ' ';
        #Ifnot;
        if (i<=j) for (: i<j : i=i+WORDSIZE) i-->0 = ' ';
        #Endif;
        i = NextWord();
        if (i == AGAIN1__WD or AGAIN2__WD or AGAIN3__WD) {
            Delete the words "then again" from the again buffer,
            in which we have just realised that it must occur:
            prevents an infinite loop on "i. again"

            #Iftrue CHARSIZE == 1;
            i = WordAddress(wn-2)-buffer;
            if (wn > num_words) j = INPUT_BUFFER_LEN-1;
            else j = WordAddress(wn)-buffer;
            for (: i<j : i++) buffer3->i = ' ';
            #Ifnot;
            i = (WordAddress(wn-2)-buffer) / WORDSIZE;
            if (wn > num_words) j = INPUT_BUFFER_LEN-1;
            else j = (WordAddress(wn)-buffer) / WORDSIZE;
            for (: i<j : i++) buffer3-->i = ' ';
            #Endif;
        }

        VM_Tokenise(buffer, parse);
        jump ReParse;
    }

  .ReType;

    cobj_flag = 0;
    actors_location = ScopeCeiling(player);
    BeginActivity(READING_A_COMMAND_ACT); if (ForActivity(READING_A_COMMAND_ACT)==false) {
        Keyboard(buffer,parse);
        num_words = WordCount(); players_command = 100 + num_words;
    } if (EndActivity(READING_A_COMMAND_ACT)) jump ReType;
  .ReParse;

    parser_inflection = name;

    Initially assume the command is aimed at the player, and the verb
    is the first word

    num_words = WordCount(); players_command = 100 + num_words;
    wn = 1; inferred_go = false;

    LanguageToInformese();
    Re-tokenise:
    VM_Tokenise(buffer,parse);

    num_words = WordCount(); players_command = 100 + num_words; token_filter = 0;
    allow_plurals = true; ResetDescriptors();

    k=0;
    #Ifdef DEBUG;
    if (parser_trace >= 2) {
        print "[ ";
        for (i=0 : i<num_words : i++) {

            #Iftrue CHARSIZE == 1;
            j = parse-->(i*2 + 1);
            #Ifnot;
            j = parse-->(i*3 + 1);
            #Endif;
            k = WordAddress(i+1);
            l = WordLength(i+1);
            #Iftrue CHARSIZE == 1;
            print "~"; for (m=0 : m<l : m++) print (char) k->m; print "~ ";
            #Ifnot;
            print "~"; for (m=0 : m<l : m++) print (char) k-->m; print "~ ";
            #Endif;

            if (j == 0) print "?";
            else if (VM_ProbablyDictionaryAddress(j)) print (address) j;
            else print j;
            if (i ~= num_words-1) print " / ";
        }
        print " ]^";
    }
    #Endif; DEBUG
    verb_wordnum = 1;
    actor = player;
    actors_location = ScopeCeiling(player);
    usual_grammar_after = 0;

  .AlmostReParse;

    scope_token = 0;
    action_to_be = NULL;

    Begin from what we currently think is the verb word

  .BeginCommand;
    wn = verb_wordnum;
    verb_word = NextWordStopped();
    If there's no input here, we must have something like "person,".

    if (verb_word == -1) {
        best_etype = STUCK_PE; jump GiveError;
    }
    if (verb_word == comma_word) {
        best_etype = COMMABEGIN_PE; jump GiveError;
    }

    Now try for "again" or "g", which are special cases: don't allow "again" if nothing
    has previously been typed; simply copy the previous text across

    if (verb_word == AGAIN2__WD or AGAIN3__WD) verb_word = AGAIN1__WD;
    if (verb_word == AGAIN1__WD) {
        if (actor ~= player) {
            best_etype = ANIMAAGAIN_PE;
            jump GiveError;
        }
        #Iftrue CHARSIZE == 1;
        if (buffer3->1 == 0) {
            PARSER_COMMAND_INTERNAL_RM('D'); new_line;
            jump ReType;
        }
        #Ifnot;
        if (buffer3-->0 == 0) {
            PARSER_COMMAND_INTERNAL_RM('D'); new_line;
            jump ReType;
        }
        #Endif; CHARSIZE
        #Iftrue CHARSIZE == 1;
        for (i=0 : i<INPUT_BUFFER_LEN : i++) buffer->i = buffer3->i;
        #Ifnot;
        for (i=0 : i<INPUT_BUFFER_LEN : i++) buffer-->i = buffer3-->i;
        #Endif;
        VM_Tokenise(buffer,parse);
        num_words = WordCount(); players_command = 100 + num_words;
        jump ReParse;
    }

    Save the present input in case of an "again" next time

    if (verb_word ~= AGAIN1__WD) {
        #Iftrue CHARSIZE == 1;
        for (i=0 : i<INPUT_BUFFER_LEN : i++) buffer3->i = buffer->i;
        #Ifnot;
        for (i=0 : i<INPUT_BUFFER_LEN : i++) buffer3-->i = buffer-->i;
        #Endif;
    }

    if (usual_grammar_after == 0) {
        j = verb_wordnum;
        i = RunRoutines(actor, grammar);
        #Ifdef DEBUG;
        if (parser_trace >= 2 && actor.grammar ~= 0 or NULL)
            print " [Grammar property returned ", i, "]^";
        #Endif; DEBUG

        if ((i ~= 0 or 1) && (VM_InvalidDictionaryAddress(i))) {
            usual_grammar_after = verb_wordnum; i=-i;
        }

        if (i == 1) {
            parser_results-->ACTION_PRES = action;
            parser_results-->NO_INPS_PRES = 0;
            parser_results-->INP1_PRES = noun;
            parser_results-->INP2_PRES = second;
            if (noun) parser_results-->NO_INPS_PRES = 1;
            if (second) parser_results-->NO_INPS_PRES = 2;
            rtrue;
        }
        if (i ~= 0) { verb_word = i; wn--; verb_wordnum--; }
        else { wn = verb_wordnum; verb_word = NextWord(); }
    }
    else usual_grammar_after = 0;

§14. Parser Letter B. Is the command a direction name, and so an implicit GO? If so, go to (K).

    if (verb_word == 0) {
        i = wn; verb_word = LanguageIsVerb(buffer, parse, verb_wordnum);
        wn = i;
    }

    If the first word is not listed as a verb, it must be a direction
    or the name of someone to talk to

    if (WordMarkedAsVerb(verb_word) == false) {
        So is the first word an object contained in the special object "compass"
        (i.e., a direction)?  This needs use of NounDomain, a routine which
        does the object matching, returning the object number, or 0 if none found,
        or REPARSE_CODE if it has restructured the parse table so the whole parse
        must be begun again...

        wn = verb_wordnum; indef_mode = false; token_filter = 0; parameters = 0;
        @push actor; @push action; @push action_to_be;
        actor = player; meta = false; action = ##Go; action_to_be = ##Go;
        l = NounDomain(Compass, 0, 0);
        @pull action_to_be; @pull action; @pull actor;
        if (l == REPARSE_CODE) jump ReParse;

        If it is a direction, send back the results:
        action=GoSub, no of arguments=1, argument 1=the direction.

        if ((l~=0) && (l ofclass K3_direction)) {
            parser_results-->ACTION_PRES = ##Go;
            parser_results-->NO_INPS_PRES = 1;
            parser_results-->INP1_PRES = l;
            inferred_go = true;
            jump LookForMore;
        }

    } end of first-word-not-a-verb

§15. Parser Letter C. Is anyone being addressed?

    Only check for a comma (a "someone, do something" command) if we are
    not already in the middle of one.  (This simplification stops us from
    worrying about "robot, wizard, you are an idiot", telling the robot to
    tell the wizard that she is an idiot.)

    if (actor == player) {
        for (j=2 : j<=num_words : j++) {
            i=NextWord();
            if (i == comma_word) jump Conversation;
        }
    }
    jump NotConversation;

    NextWord nudges the word number wn on by one each time, so we've now
    advanced past a comma.  (A comma is a word all on its own in the table.)

    .Conversation;
    j = wn - 1;

    Use NounDomain (in the context of "animate creature") to see if the
    words make sense as the name of someone held or nearby

    wn = 1; lookahead = HELD_TOKEN;
    scope_reason = TALKING_REASON;
    l = NounDomain(player, actors_location, CREATURE_TOKEN);
    scope_reason = PARSING_REASON;
    if (l == REPARSE_CODE) jump ReParse;
    if (l == 0) {
        if (WordMarkedAsVerb(verb_word)) jump NotConversation;
        best_etype = MISSINGPERSON_PE; jump GiveError;
    }

    .Conversation2;

    The object addressed must at least be "talkable" if not actually "animate"
    (the distinction allows, for instance, a microphone to be spoken to,
    without the parser thinking that the microphone is human).

    if (l hasnt animate && l hasnt talkable) {
        best_etype = ANIMALISTEN_PE; noun = l; jump GiveError;
    }

    Check that there aren't any mystery words between the end of the person's
    name and the comma (eg, throw out "dwarf sdfgsdgs, go north").

    if (wn ~= j) {
        if (WordMarkedAsVerb(verb_word)) jump NotConversation;
        best_etype = TOTALK_PE; jump GiveError;
    }

    The player has now successfully named someone.  Adjust "him", "her", "it":

    PronounNotice(l);

    Set the global variable "actor", adjust the number of the first word,
    and begin parsing again from there.

    verb_wordnum = j + 1;

    Stop things like "me, again":

    if (l == player) {
        wn = verb_wordnum;
        if (NextWordStopped() == AGAIN1__WD or AGAIN2__WD or AGAIN3__WD) {
            best_etype = ANIMAAGAIN_PE;
            jump GiveError;
        }
    }

    actor = l;
    actors_location = ScopeCeiling(l);
    #Ifdef DEBUG;
    if (parser_trace >= 1)
        print "[Actor is ", (the) actor, " in ", (name) actors_location, "]^";
    #Endif; DEBUG
    jump BeginCommand;

§16. Parser Letter D. Get the verb: try all the syntax lines for that verb.

    .NotConversation;
    if (WordMarkedAsVerb(verb_word) == false) {
        verb_word = UnknownVerb(verb_word);
        if (verb_word ~= 0) jump VerbAccepted;
        best_etype = VERB_PE;
        jump GiveError;
    }
    .VerbAccepted;

    We now definitely have a verb, not a direction, whether we got here by the
    "take ..." or "person, take ..." method.

    You can't order other people to "full score" for you, and so on...

    if (WordMarkedAsMeta(verb_word) && actor ~= player) {
        best_etype = VERB_PE;
        meta = 0;
        jump GiveError;
    }

    Now let i be the corresponding verb number...

    i = DictionaryWordToVerbNum(verb_word);

    ...then look up the i-th entry in the verb table, whose address is at word
    7 in the Z-machine (in the header), so as to get the address of the syntax
    table for the given verb...

    #Ifdef TARGET_ZCODE;
    syntax = (HDR_STATICMEMORY-->0)-->i;
    #Ifnot; TARGET_GLULX
    syntax = (#grammar_table)-->(i+1);
    #Endif; TARGET_

    ...and then see how many lines (ie, different patterns corresponding to the
    same verb) are stored in the parse table...

    num_lines = (syntax->0) - 1;

    ...and now go through them all, one by one.
    To prevent pronoun_word 0 being misunderstood,

    pronoun_word = NULL; pronoun_obj = NULL;

    #Ifdef DEBUG;
    if (parser_trace >= 1)
        print "[Parsing for the verb '", (address) verb_word, "' (", num_lines+1, " lines)]^";
    #Endif; DEBUG

    best_etype = STUCK_PE; nextbest_etype = STUCK_PE;
    multiflag = false;

    "best_etype" is the current failure-to-match error - it is by default
    the least informative one, "don't understand that sentence".
    "nextbest_etype" remembers the best alternative to having to ask a
    scope token for an error message (i.e., the best not counting ASKSCOPE_PE).
    multiflag is used here to prevent inappropriate MULTI_PE errors
    in addition to its unrelated duties passing information to action routines

§17. Parser Letter E. Break down a syntax line into analysed tokens.

    line_address = syntax + 1;

    for (line=0 : line<=num_lines : line++) {

        Unpack the syntax line from Inform format into three arrays; ensure that
        the sequence of tokens ends in an ENDIT_TOKEN.

        line_address = UnpackGrammarLine(line_address);

        #Ifdef DEBUG;
        if (parser_trace >= 1) {
            if (parser_trace >= 2) new_line;
            print "[line ", line; DebugGrammarLine();
            print "]^";
        }
        #Endif; DEBUG

        We aren't in "not holding" or inferring modes, and haven't entered
        any parameters on the line yet, or any special numbers; the multiple
        object is still empty.

        inferfrom = 0;
        parameters = 0;
        nsns = 0; special_word = 0;
        multiple_object-->0 = 0;
        multi_context = 0;
        etype = STUCK_PE; multi_had = 0;

        Put the word marker back to just after the verb

        wn = verb_wordnum+1;

§18. Parser Letter F. Look ahead for advance warning for multiexcept/multiinside.

There are two special cases where parsing a token now has to be affected by the result of parsing another token later, and these two cases (multiexcept and multiinside tokens) are helped by a quick look ahead, to work out the future token now. We can only carry this out in the simple (but by far the most common) case:

multiexcept <one or more prepositions> noun
and similarly for multiinside.
        advance_warning = -1; indef_mode = false;
        for (i=0,m=false,pcount=0 : line_token-->pcount ~= ENDIT_TOKEN : pcount++) {
            scope_token = 0;

            if (line_ttype-->pcount ~= PREPOSITION_TT) i++;

            if (line_ttype-->pcount == ELEMENTARY_TT) {
                if (line_tdata-->pcount == MULTI_TOKEN) m = true;
                if (line_tdata-->pcount == MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN  && i == 1) {
                    First non-preposition is "multiexcept" or
                    "multiinside", so look ahead.

                    #Ifdef DEBUG;
                    if (parser_trace >= 2) print " [Trying look-ahead]^";
                    #Endif; DEBUG

                    We need this to be followed by 1 or more prepositions.

                    pcount++;
                    if (line_ttype-->pcount == PREPOSITION_TT) {
                        skip ahead to a preposition word in the input
                        do {
                            l = NextWord();
                        } until ((wn > num_words) ||
                                 (l && (l->#dict_par1) & DICTPAR1_PREP ~= 0));

                        if (wn > num_words) {
                            #Ifdef DEBUG;
                            if (parser_trace >= 2)
                                print " [Look-ahead aborted: prepositions missing]^";
                            #Endif;
                            jump EmptyLine;
                        }

                        do {
                            if (PrepositionChain(l, pcount) ~= -1) {
                                advance past the chain
                                if ((line_token-->pcount)->0 & $20 ~= 0) {
                                    pcount++;
                                    while ((line_token-->pcount ~= ENDIT_TOKEN) &&
                                           ((line_token-->pcount)->0 & $10 ~= 0))
                                        pcount++;
                                } else {
                                    pcount++;
                                }
                            } else {
                                try to find another preposition word
                                do {
                                    l = NextWord();
                                } until ((wn >= num_words) ||
                                         (l && (l->#dict_par1) & DICTPAR1_PREP ~= 0));

                                if (l && (l->#dict_par1) & DICTPAR1_PREP) continue;

                                lookahead failed
                                #Ifdef DEBUG;
                                if (parser_trace >= 2)
                                    print " [Look-ahead aborted: prepositions don't match]^";
                                #endif;
                                jump LineFailed;
                            }
                            if (wn <= num_words) l = NextWord();
                        } until (line_ttype-->pcount ~= PREPOSITION_TT);

                        .EmptyLine;
                        put back the non-preposition we just read
                        wn--;

                        if ((line_ttype-->pcount == ELEMENTARY_TT) &&
                            (line_tdata-->pcount == NOUN_TOKEN)) {
                            l = Descriptors();  skip past THE etc
                            if (l~=0) etype=l;  don't allow multiple objects
                            k = parser_results-->INP1_PRES; @push k; @push parameters;
                            parameters = 1; parser_results-->INP1_PRES = 0;
                            l = NounDomain(actors_location, actor, NOUN_TOKEN, true);
                            @pull parameters; @pull k; parser_results-->INP1_PRES = k;
                            #Ifdef DEBUG;
                            if (parser_trace >= 2) {
                                print " [Advanced to ~noun~ token: ";
                                if (l == REPARSE_CODE) print "re-parse request]^";
                                else {
                                    if (l == 1) print "but multiple found]^";
                                    if (l == 0) print "error ", etype, "]^";
                                    if (l >= 2) print (the) l, "]^";
                                }
                            }
                            #Endif; DEBUG
                            if (l == REPARSE_CODE) jump ReParse;
                            if (l >= 2) advance_warning = l;
                        }
                    }
                    break;
                }
            }
        }

        Slightly different line-parsing rules will apply to "take multi", to
        prevent "take all" behaving correctly but misleadingly when there's
        nothing to take.

        take_all_rule = 0;
        if (m && params_wanted == 1 && action_to_be == ##Take)
            take_all_rule = 1;

        And now start again, properly, forearmed or not as the case may be.
        As a precaution, we clear all the variables again (they may have been
        disturbed by the call to NounDomain, which may have called outside
        code, which may have done anything!).

        inferfrom = 0;
        parameters = 0;
        nsns = 0; special_word = 0;
        multiple_object-->0 = 0;
        etype = STUCK_PE; multi_had = 1;
        wn = verb_wordnum+1;

§19. Parser Letter G. Parse each token in turn (calling ParseToken to do most of the work).

The pattern gradually accumulates what has been recognised so far, so that it may be reprinted by the parser later on.

        m = true;
        for (pcount=1 : : pcount++)
            if (line_token-->(pcount-1) == ENDIT_TOKEN) {
                if (pcount >= 2) {
                    while ((((line_token-->(pcount-2))->0) & $10) ~= 0) pcount--;
                    AnalyseToken(line_token-->(pcount-2));
                    if (found_ttype == PREPOSITION_TT) {
                        l = -1;
                        while (true) {
                            m = NextWordStopped();
                            if (m == -1) break;
                            l = m;
                        }
                        if (PrepositionChain(l, pcount-2) == -1) {
                            m = false;
                            #Ifdef DEBUG;
                            if (parser_trace >= 2)
                                print "[line rejected for not ending with correct preposition]^";
                            #Endif; DEBUG
                        } else m = true;
                    }
                }
                break;
            }
        wn = verb_wordnum+1;

        if (m) for (pcount=1 : : pcount++) {
            pattern-->pcount = PATTERN_NULL; scope_token = 0;

            token = line_token-->(pcount-1);
            lookahead = line_token-->pcount;

            #Ifdef DEBUG;
            if (parser_trace >= 2)
                print " [line ", line, " token ", pcount, " word ", wn, " : ", (DebugToken) token,
                  "]^";
            #Endif; DEBUG

            if (token ~= ENDIT_TOKEN) {
                scope_reason = PARSING_REASON;
                AnalyseToken(token);

                l = ParseToken(found_ttype, found_tdata, pcount-1, token);
                while ((l >= GPR_NOUN) && (l < -1)) l = ParseToken(ELEMENTARY_TT, l + 256);
                scope_reason = PARSING_REASON;

                if (l == GPR_PREPOSITION) {
                    if (found_ttype~=PREPOSITION_TT && (found_ttype~=ELEMENTARY_TT ||
                        found_tdata~=TOPIC_TOKEN)) params_wanted--;
                    l = true;
                }
                else
                    if (l < 0) l = false;
                    else
                        if (l ~= GPR_REPARSE) {
                            if (l == GPR_NUMBER) {
                                if (nsns == 0) special_number1 = parsed_number;
                                else special_number2 = parsed_number;
                                nsns++; l = 1;
                            }
                            if (l == GPR_MULTIPLE) l = 0;
                            parser_results-->(parameters+INP1_PRES) = l;
                            parameters++;
                            pattern-->pcount = l;
                            l = true;
                        }

                #Ifdef DEBUG;
                if (parser_trace >= 3) {
                    print "  [token resulted in ";
                    if (l == REPARSE_CODE) print "re-parse request]^";
                    if (l == 0) print "failure with error type ", etype, "]^";
                    if (l == 1) print "success]^";
                }
                #Endif; DEBUG

                if (l == REPARSE_CODE) jump ReParse;
                if (l == false) break;
            }
            else {

                If the player has entered enough already but there's still
                text to wade through: store the pattern away so as to be able to produce
                a decent error message if this turns out to be the best we ever manage,
                and in the mean time give up on this line

                However, if the superfluous text begins with a comma or "then" then
                take that to be the start of another instruction

                if (wn <= num_words) {
                    l = NextWord();
                    if (l == THEN1__WD or THEN2__WD or THEN3__WD or comma_word) {
                        held_back_mode = true; hb_wn = wn-1;
                    } else {
                        for (m=0 : m<32 : m++) pattern2-->m = pattern-->m;
                        pcount2 = pcount;
                        etype = UPTO_PE;
                        break;
                    }
                }

                Now, we may need to revise the multiple object because of the single one
                we now know (but didn't when the list was drawn up).

                if (parameters >= 1) {
                    if (parser_results-->INP1_PRES == 0) {
                        l = ReviseMulti(parser_results-->INP2_PRES);
                        if (l ~= 0) { etype = l; parser_results-->ACTION_PRES = action_to_be; break; }
                    }
                }
                if (parameters >= 2) {
                    if (parser_results-->INP2_PRES == 0) {
                        l = ReviseMulti(parser_results-->INP1_PRES);
                        if (l ~= 0) { etype = l; break; }
                    } else {
                        k = parser_results-->INP1_PRES; l = parser_results-->INP2_PRES;
                        if (k && l) {
                            if ((multi_context==MULTIEXCEPT_TOKEN && k == l) ||
                                ((multi_context==MULTIINSIDE_TOKEN && k notin l && l notin k))) {
                                best_etype = NOTHING_PE;
                                parser_results-->ACTION_PRES = action_to_be; jump GiveError;
                            }
                        }
                    }
                }

                To trap the case of "take all" inferring only "yourself" when absolutely
                nothing else is in the vicinity...

                if (take_all_rule == 2 && parser_results-->INP1_PRES == actor) {
                    best_etype = NOTHING_PE;
                    jump GiveError;
                }

                if (multi_had > 1) {
                    best_etype = TOOFEW_PE;
                    jump GiveError;
                }

                #Ifdef DEBUG;
                if (parser_trace >= 1) print "[Line successfully parsed]^";
                #Endif; DEBUG

                The line has successfully matched the text.  Declare the input error-free...

                oops_from = 0;

                ...explain any inferences made (using the pattern)...

                if (inferfrom ~= 0) {
                    PrintInferredCommand(inferfrom);
                    ClearParagraphing(20);
                }

                ...copy the action number, and the number of parameters...

                parser_results-->ACTION_PRES = action_to_be;
                parser_results-->NO_INPS_PRES = parameters;

                ...reverse first and second parameters if need be...

                if (action_reversed && parameters == 2) {
                    i = parser_results-->INP1_PRES;
                    parser_results-->INP1_PRES = parser_results-->INP2_PRES;
                    parser_results-->INP2_PRES = i;
                    if (nsns == 2) {
                        i = special_number1; special_number1 = special_number2;
                        special_number2 = i;
                    }
                }

                ...and to reset "it"-style objects to the first of these parameters, if
                there is one (and it really is an object)...

                if (parameters > 0 && parser_results-->INP1_PRES >= 2)
                    PronounNotice(parser_results-->INP1_PRES);

                ...and return from the parser altogether, having successfully matched
                a line.

                if (held_back_mode) {
                    wn=hb_wn;
                    jump LookForMore;
                }
                rtrue;

            } end of if(token ~= ENDIT_TOKEN) else
        } end of for(pcount++)

        .LineFailed;
        The line has failed to match.
        We continue the outer "for" loop, trying the next line in the grammar.

        if (etype > best_etype) best_etype = etype;
        if (etype ~= ASKSCOPE_PE && etype > nextbest_etype) nextbest_etype = etype;

        ...unless the line was something like "take all" which failed because
        nothing matched the "all", in which case we stop and give an error now.

        if (take_all_rule == 2 && etype==NOTHING_PE) break;

    } end of for(line++)

    The grammar is exhausted: every line has failed to match.

§20. Parser Letter H. Cheaply parse otherwise unrecognised conversation and return.

(Errors are handled differently depending on who was talking. If the command was addressed to somebody else (eg, DWARF, SFGH) then it is taken as conversation which the parser has no business in disallowing.)

The parser used to return the fake action ##NotUnderstood when a command in the form PERSON, ARFLE BARFLE GLOOP is parsed, where a character is addressed but with an instruction which the parser can't understand. (If a command such as ARFLE BARFLE GLOOP is not an instruction to someone else, the parser prints an error and requires the player to type another command: thus ##NotUnderstood was only returned when actor is not the player.) And I6 had elaborate object-oriented ways to deal with this, but we won't use any of that: we simply convert to a ##Answer action, which communicates a snippet of words to another character, just as if the player had typed ANSWER ARFLE BARFLE GLOOP TO PERSON. For I7 purposes, the fake action ##NotUnderstood does not exist.

In order to assist people who do want to parse that type of mistyped command in extensions, wn is left pointing at the first word not parsed as a command.

  .GiveError;

    etype = best_etype;
    if (actor ~= player) {
        if (usual_grammar_after ~= 0) {
            verb_wordnum = usual_grammar_after;
            jump AlmostReParse;
        }
        m = wn; Save wn so extension authors can parse command errors if they want to
        wn = 1;
        while ((wn <= num_words) && (NextWord() ~= comma_word)) ;
        parser_results-->ACTION_PRES = ##Answer;
        parser_results-->NO_INPS_PRES = 2;
        parser_results-->INP1_PRES = actor;
        parser_results-->INP2_PRES = 1; special_number1 = special_word;
        actor = player;
        consult_from = wn; consult_words = num_words-consult_from+1;
        wn = m; Restore wn so extension authors can parse command errors if they want to
        rtrue;
    }

§21. Parser Letter I. Print best possible error message.

    If the player was the actor (eg, in "take dfghh") the error must be printed,
    and fresh input called for.  In three cases the oops word must be jiggled.

    if ((etype ofclass Routine) || (etype ofclass String)) {
        if (ParserError(etype) ~= 0) jump ReType;
    } else {
        if (verb_wordnum == 0 && etype == CANTSEE_PE) etype = VERB_PE;
        players_command = 100 + WordCount(); The snippet variable "player's command"
        BeginActivity(PRINTING_A_PARSER_ERROR_ACT);
        if (ForActivity(PRINTING_A_PARSER_ERROR_ACT)) jump SkipParserError;
    }
    pronoun_word = pronoun__word; pronoun_obj = pronoun__obj;

    if (etype == STUCK_PE) {    PARSER_ERROR_INTERNAL_RM('A'); new_line; oops_from = 1; }
    if (etype == UPTO_PE) {
        for (m=0 : m<32 : m++) pattern-->m = pattern2-->m;
        pcount = pcount2;
        if (inferred_go) {
            PARSER_ERROR_INTERNAL_RM('C');
            print (name) parser_results-->INP1_PRES;
        } else {
            PARSER_ERROR_INTERNAL_RM('B');
            PrintCommand(0);
        }
        print ".^";
    }
    if (etype == NUMBER_PE) {   PARSER_ERROR_INTERNAL_RM('D'); new_line; }
    if (etype == CANTSEE_PE) {  PARSER_ERROR_INTERNAL_RM('E'); new_line; oops_from=saved_oops; }
    if (etype == TOOLIT_PE) {   PARSER_ERROR_INTERNAL_RM('F'); new_line; }
    if (etype == NOTHELD_PE) {  PARSER_ERROR_INTERNAL_RM('G'); new_line; oops_from=saved_oops; }
    if (etype == MULTI_PE) {    PARSER_ERROR_INTERNAL_RM('H'); new_line; }
    if (etype == MMULTI_PE) {   PARSER_ERROR_INTERNAL_RM('I'); new_line; }
    if (etype == VAGUE_PE) {    PARSER_ERROR_INTERNAL_RM('J'); new_line; }
    if (etype == ITGONE_PE) {
        if (pronoun_obj == NULL) { PARSER_ERROR_INTERNAL_RM('J'); new_line; }
        else { PARSER_ERROR_INTERNAL_RM('K', noun); new_line; }
    }
    if (etype == EXCEPT_PE) {   PARSER_ERROR_INTERNAL_RM('L'); new_line; }
    if (etype == ANIMA_PE) {    PARSER_ERROR_INTERNAL_RM('M'); new_line; }
    if (etype == VERB_PE) {     PARSER_ERROR_INTERNAL_RM('N'); new_line; }
    if (etype == SCENERY_PE) {  PARSER_ERROR_INTERNAL_RM('O'); new_line; }
    if (etype == JUNKAFTER_PE) {  PARSER_ERROR_INTERNAL_RM('P'); new_line; }
    if (etype == TOOFEW_PE) {  PARSER_ERROR_INTERNAL_RM('Q', multi_had); new_line; }
    if (etype == NOTHING_PE) {
        if (parser_results-->ACTION_PRES == ##Remove &&
            parser_results-->INP2_PRES ofclass Object) {
            noun = parser_results-->INP2_PRES; ensure valid for messages
            if (noun has animate) { PARSER_N_ERROR_INTERNAL_RM('C', noun); new_line; }
            else if (noun hasnt container or supporter) { PARSER_N_ERROR_INTERNAL_RM('D', noun); new_line; }
            else if (noun has container && noun hasnt open)  { PARSER_N_ERROR_INTERNAL_RM('E', noun); new_line; }
            else if (children(noun)==0) { PARSER_N_ERROR_INTERNAL_RM('F', noun); new_line; }
            else parser_results-->ACTION_PRES = 0;
        }
        if (parser_results-->ACTION_PRES ~= ##Remove) {
            if (multi_wanted==100) { PARSER_N_ERROR_INTERNAL_RM('A'); new_line; }
            else                  {  PARSER_N_ERROR_INTERNAL_RM('B'); new_line; }
        }
    }
    if (etype == NOTINCONTEXT_PE) { PARSER_ERROR_INTERNAL_RM('R'); new_line; }
    if (etype == ANIMAAGAIN_PE) { PARSER_ERROR_INTERNAL_RM('S'); new_line; }
    if (etype == COMMABEGIN_PE) { PARSER_ERROR_INTERNAL_RM('T'); new_line; }
    if (etype == MISSINGPERSON_PE) { PARSER_ERROR_INTERNAL_RM('U'); new_line; }
    if (etype == ANIMALISTEN_PE) { PARSER_ERROR_INTERNAL_RM('V', noun); new_line; }
    if (etype == TOTALK_PE) { PARSER_ERROR_INTERNAL_RM('W'); new_line; }
    if (etype == ASKSCOPE_PE) {
        scope_stage = 3;
        if (scope_error() == -1) {
            best_etype = nextbest_etype;
            if (~~((etype ofclass Routine) || (etype ofclass String)))
                EndActivity(PRINTING_A_PARSER_ERROR_ACT);
            jump GiveError;
        }
    }

    .SkipParserError;
    if ((etype ofclass Routine) || (etype ofclass String)) jump ReType;
    say__p = 1;
    EndActivity(PRINTING_A_PARSER_ERROR_ACT);

§22. Parser Letter J. Retry the whole lot.

    And go (almost) right back to square one...

    jump ReType;

    ...being careful not to go all the way back, to avoid infinite repetition
    of a deferred command causing an error.

§23. Parser Letter K. Last thing: check for THEN and further instructions(s), return.

    At this point, the return value is all prepared, and we are only looking
    to see if there is a "then" followed by subsequent instruction(s).

  .LookForMore;

    if (wn > num_words) rtrue;

    i = NextWord();
    if (i == THEN1__WD or THEN2__WD or THEN3__WD or comma_word) {
        if (wn > num_words) {
           held_back_mode = false;
           return;
        }
        hb_wn = wn;
        held_back_mode = true;
       return;
    }
    best_etype = UPTO_PE;
    jump GiveError;

§24. End of Parser Proper.

]; end of Parser__parse

§25. Internal Rule. As a hook on which to hang responses.

[ PARSER_ERROR_INTERNAL_R; ];
[ PARSER_N_ERROR_INTERNAL_R; ];
[ PARSER_COMMAND_INTERNAL_R; ];

§26. Parse Token. The main parsing routine above tried a sequence of "grammar lines" in turn, matching each against the text typed until one fitted. A grammar line is itself a sequence of "grammar tokens". Here we have to parse the tokens.

ParseToken(type, data) tries the match text beginning at the current word marker wn against a token of the given type, with the given data. The optional further arguments token_n and token supply the token number in the current grammar line (because some tokens do depend on what has happened before or is needed later) and the address of the dictionary word which makes up the token, in the case where it's a "preposition".

The return values are:

Strictly speaking ParseToken is a shell routine which saves the current state on the stack, and calling ParseToken__ to do the actual work.

Once again the routine is traditionally divided into six letters, here named under paragraphs "Parse Token Letter A", and so on.

[ ParseTokenStopped x y;
    if (wn>WordCount()) return GPR_FAIL;
    return ParseToken(x,y);
];

Global parsetoken_nesting = 0;
[ ParseToken given_ttype given_tdata token_n token  i t rv;
    if (parsetoken_nesting > 0) {
        save match globals
        @push match_from; @push token_filter; @push match_length;
        @push number_of_classes; @push oops_from;
        for (i=0: i<number_matched: i++) {
            t = match_list-->i; @push t;
            t = match_classes-->i; @push t;
            t = match_scores-->i; @push t;
        }
        @push number_matched;
     }

    parsetoken_nesting++;
    rv = ParseToken__(given_ttype, given_tdata, token_n, token);
    parsetoken_nesting--;

    if (parsetoken_nesting > 0) {
        restore match globals
        @pull number_matched;
        for (i=number_matched-1: i>=0: i--) {
            @pull t; match_scores-->i = t;
            @pull t; match_classes-->i = t;
            @pull t; match_list-->i = t;
        }
        @pull oops_from; @pull number_of_classes;
        @pull match_length; @pull token_filter; @pull match_from;
    }
    return rv;
];

[ ParseToken__ given_ttype given_tdata token_n token
    l o i j k and_parity single_object desc_wn many_flag
    token_allows_multiple prev_indef_wanted;

§27. Parse Token Letter A. Analyse token; handle all not involving object lists, break down others.

    token_filter = 0;
    parser_inflection = name;

    switch (given_ttype) {
      ELEMENTARY_TT:
        switch (given_tdata) {
          SPECIAL_TOKEN:
            l = TryNumber(wn);
            special_word = NextWord();
            #Ifdef DEBUG;
            if (l ~= -1000)
                if (parser_trace >= 3) print "  [Read special as the number ", l, "]^";
            #Endif; DEBUG
            if (l == -1000) {
                #Ifdef DEBUG;
                if (parser_trace >= 3) print "  [Read special word at word number ", wn, "]^";
                #Endif; DEBUG
                l = special_word;
            }
            parsed_number = l;
            return GPR_NUMBER;

          NUMBER_TOKEN:
            l=TryNumber(wn++);
            if (l == -1000) {
                etype = NUMBER_PE;
                return GPR_FAIL;
            }
            #Ifdef DEBUG;
            if (parser_trace>=3) print "  [Read number as ", l, "]^";
            #Endif; DEBUG
            parsed_number = l;
            return GPR_NUMBER;

          CREATURE_TOKEN:
            if (action_to_be == ##Answer or ##Ask or ##AskFor or ##Tell)
                scope_reason = TALKING_REASON;

          TOPIC_TOKEN:
            consult_from = wn;
            if ((line_ttype-->(token_n+1) ~= PREPOSITION_TT) &&
               (line_token-->(token_n+1) ~= ENDIT_TOKEN)) {
                IssueRTP("ComplicatedUnderstandTextToken",
                    "This use of '[text]' is too complicated.", WorldModelKitRTPs);
                return GPR_PREPOSITION;
            }
            do {
                o = NextWordStopped();
            } until (o == -1 || PrepositionChain(o, token_n+1) ~= -1);
            wn--;
            consult_words = wn-consult_from;
            if (consult_words == 0) return GPR_FAIL;
            if (action_to_be == ##Ask or ##Answer or ##Tell) {
                o = wn; wn = consult_from; parsed_number = NextWord();
                wn = o; return 1;
            }
            if (o==-1 && (line_ttype-->(token_n+1) == PREPOSITION_TT))
                return GPR_FAIL;    don't infer if required preposition is absent
            return GPR_PREPOSITION;
        }

      PREPOSITION_TT:
        #Ifdef DEBUG;
        if (parser_trace>=5) print "  [Preposition token]^";
        #Endif; DEBUG
        Is it an unnecessary alternative preposition, when a previous choice
        has already been matched?
        if ((token->0) & $10) return GPR_PREPOSITION;

        If we've run out of the player's input, but still have parameters to
        specify, we go into "infer" mode, remembering where we are and the
        preposition we are inferring...

        if (wn > num_words) {
            if (inferfrom==0 && parameters<params_wanted) {
                inferfrom = pcount; inferword = token;
                pattern-->pcount = REPARSE_CODE + VM_DictionaryAddressToNumber(given_tdata);
            }

            If we are not inferring, then the line is wrong...

            if (inferfrom == 0) return -1;

            If not, then the line is right but we mark in the preposition...

            pattern-->pcount = REPARSE_CODE + VM_DictionaryAddressToNumber(given_tdata);
            return GPR_PREPOSITION;
        }

        o = NextWord();

        pattern-->pcount = REPARSE_CODE + VM_DictionaryAddressToNumber(o);

        Whereas, if the player has typed something here, see if it is the
        required preposition... if it's wrong, the line must be wrong,
        but if it's right, the token is passed (jump to finish this token).

        if (o == given_tdata) return GPR_PREPOSITION;
        if (PrepositionChain(o, token_n) ~= -1) return GPR_PREPOSITION;
        return -1;

      GPR_TT:
        l = given_tdata();
        #Ifdef DEBUG;
        if (parser_trace >= 3) print "  [Outside parsing routine returned ", l, "]^";
        #Endif; DEBUG
        return l;

      SCOPE_TT:
        scope_token = given_tdata;
        scope_stage = 1;
        #Ifdef DEBUG;
        if (parser_trace >= 3) print "  [Scope routine called at stage 1]^";
        #Endif; DEBUG
        l = scope_token();
        #Ifdef DEBUG;
        if (parser_trace >= 3) print "  [Scope routine returned multiple-flag of ", l, "]^";
        #Endif; DEBUG
        if (l == 1) given_tdata = MULTI_TOKEN; else given_tdata = NOUN_TOKEN;

      ATTR_FILTER_TT:
        token_filter = 1 + given_tdata;
        given_tdata = NOUN_TOKEN;

      ROUTINE_FILTER_TT:
        token_filter = given_tdata;
        given_tdata = NOUN_TOKEN;

    } end of switch(given_ttype)

    token = given_tdata;

§28. Parse Token Letter B. Begin parsing an object list.

    There are now three possible ways we can be here:
        parsing an elementary token other than "special" or "number";
        parsing a scope token;
        parsing a noun-filter token (either by routine or attribute).
    
    In each case, token holds the type of elementary parse to
    perform in matching one or more objects, and
    token_filter is 0 (default), an attribute + 1 for an attribute filter
    or a routine address for a routine filter.

    token_allows_multiple = false;
    if (token == MULTI_TOKEN or MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN)
        token_allows_multiple = true;

    many_flag = false; and_parity = true; dont_infer = false; dont_infer_pronoun = false;

§29. Parse Token Letter C. Parse descriptors (articles, pronouns, etc.) in the list.

    We expect to find a list of objects next in what the player's typed.

  .ObjectList;

    #Ifdef DEBUG;
    if (parser_trace >= 3) print "  [Object list from word ", wn, "]^";
    #Endif; DEBUG

    Take an advance look at the next word: if it's "it" or "them", and these
    are unset, set the appropriate error number and give up on the line
    (if not, these are still parsed in the usual way - it is not assumed
    that they still refer to something in scope)

    o = NextWord(); wn--;

    pronoun_word = NULL; pronoun_obj = NULL;
    l = PronounValue(o);
    if (l ~= 0) {
        pronoun_word = o; pronoun_obj = l;
        if (l == NULL) {
            Don't assume this is a use of an unset pronoun until the
            descriptors have been checked, because it might be an
            article (or some such) instead

            for (l=1 : l<=LanguageDescriptors-->0 : l=l+4)
                if (o == LanguageDescriptors-->l) jump AssumeDescriptor;
            pronoun__word = pronoun_word; pronoun__obj = pronoun_obj;
            etype = VAGUE_PE;
            if (parser_trace >= 3) print "  [Stop: unset pronoun]^";
            return GPR_FAIL;
        }
    }

  .AssumeDescriptor;

    if (o == ME1__WD or ME2__WD or ME3__WD) { pronoun_word = o; pronoun_obj = player; }

    allow_plurals = true; desc_wn = wn;

  .TryAgain;

    First, we parse any descriptive words (like "the", "five" or "every"):
    l = Descriptors(token_allows_multiple);
    if (l ~= 0) { etype = l; return 0; }

  .TryAgain2;

§30. Parse Token Letter D. Parse an object name.

    This is an actual specified object, and is therefore where a typing error
    is most likely to occur, so we set:

    oops_from = wn;

    So, two cases.  Case 1: token not equal to "held" (so, no implicit takes)
    but we may well be dealing with multiple objects

    In either case below we use NounDomain, giving it the token number as
    context, and two places to look: among the actor's possessions, and in the
    present location.  (Note that the order depends on which is likeliest.)

    if (token ~= HELD_TOKEN) {
        i = multiple_object-->0;
        #Ifdef DEBUG;
        if (parser_trace >= 3) print "  [Calling NounDomain on location and actor]^";
        #Endif; DEBUG
        l = NounDomain(actors_location, actor, token, dont_ask_for_clarification);
        if (l == REPARSE_CODE) return l;                  Reparse after Q&A
        if (indef_wanted == INDEF_ALL_WANTED && l == 0 && number_matched == 0)
            l = 1;  ReviseMulti if TAKE ALL FROM empty container

        if (token_allows_multiple && ~~multiflag) {
            if (best_etype==MULTI_PE) best_etype=STUCK_PE;
            multiflag = true;
        }
        if (l == 0) {
            if (indef_possambig) {
                ResetDescriptors();
                wn = desc_wn;
                jump TryAgain2;
            }
            if (etype == MULTI_PE && multiflag) etype = STUCK_PE;
            etype=CantSee();
            jump FailToken;
        } Choose best error

        #Ifdef DEBUG;
        if (parser_trace >= 3) {
            if (l > 1) print "  [ND returned ", (the) l, "]^";
            else {
                print "  [ND appended to the multiple object list:^";
                k = multiple_object-->0;
                for (j=i+1 : j<=k : j++)
                    print "  Entry ", j, ": ", (The) multiple_object-->j,
                          " (", multiple_object-->j, ")^";
                print "  List now has size ", k, "]^";
            }
        }
        #Endif; DEBUG

        if (l == 1) {
            if (~~many_flag) many_flag = true;
            else {                                Merge with earlier ones
                k = multiple_object-->0;            (with either parity)
                multiple_object-->0 = i;
                for (j=i+1 : j<=k : j++) {
                    if (and_parity) MultiAdd(multiple_object-->j);
                    else            MultiSub(multiple_object-->j);
                }
                #Ifdef DEBUG;
                if (parser_trace >= 3)
                    print "  [Merging ", k-i, " new objects to the ", i, " old ones]^";
                #Endif; DEBUG
            }
        }
        else {
            A single object was indeed found

            if (match_length == 0 && indef_possambig) {
                So the answer had to be inferred from no textual data,
                and we know that there was an ambiguity in the descriptor
                stage (such as a word which could be a pronoun being
                parsed as an article or possessive).  It's worth having
                another go.

                ResetDescriptors();
                wn = desc_wn;
                jump TryAgain2;
            }

            if ((token == CREATURE_TOKEN) && (CreatureTest(l) == 0)) {
                etype = ANIMA_PE;
                jump FailToken;
            }  Animation is required

            if (~~many_flag) single_object = l;
            else {
                if (and_parity) MultiAdd(l); else MultiSub(l);
                #Ifdef DEBUG;
                if (parser_trace >= 3) print "  [Combining ", (the) l, " with list]^";
                #Endif; DEBUG
            }
        }
    }

    else {

    Case 2: token is "held" (which fortunately can't take multiple objects)
    and may generate an implicit take

        l = NounDomain(actor,actors_location,token,dont_ask_for_clarification);       Same as above...
        if (l == REPARSE_CODE) return l;
        if (l == 0) {
            if (indef_possambig) {
                ResetDescriptors();
                wn = desc_wn;
                jump TryAgain2;
            }
            etype = CantSee(); jump FailToken;            Choose best error
        }

        ...until it produces something not held by the actor.  Then an implicit
        take must be tried.  If this is already happening anyway, things are too
        confused and we have to give up (but saving the oops marker so as to get
        it on the right word afterwards).
        The point of this last rule is that a sequence like
        
            > read newspaper
            (taking the newspaper first)
            The dwarf unexpectedly prevents you from taking the newspaper!
        
        should not be allowed to go into an infinite repeat - read becomes
        take then read, but take has no effect, so read becomes take then read...
        Anyway for now all we do is record the number of the object to take.

        o = parent(l);
        if (o ~= actor) {
            #Ifdef DEBUG;
            if (parser_trace >= 3) print "  [Allowing object ", (the) l, " for now]^";
            #Endif; DEBUG
        }
        single_object = l;
    } end of if (token ~= HELD_TOKEN) else

    The following moves the word marker to just past the named object...

if (match_from ~= oops_from) print match_from, " vs ", oops_from, "^";

   wn = oops_from + match_length;
    wn = match_from + match_length;

§31. Parse Token Letter E. Parse connectives (AND, BUT, etc.) and go back to (C).

    Object(s) specified now: is that the end of the list, or have we reached
    "and", "but" and so on?  If so, create a multiple-object list if we
    haven't already (and are allowed to).

  .NextInList;

    o = NextWord();

    if (o == AND1__WD or AND2__WD or AND3__WD or BUT1__WD or BUT2__WD or BUT3__WD or comma_word) {

        #Ifdef DEBUG;
        if (parser_trace >= 3) print "  [Read connective '", (address) o, "']^";
        #Endif; DEBUG

        if (~~token_allows_multiple) {
            if (multiflag) jump PassToken; give UPTO_PE error
            etype=MULTI_PE;
            jump FailToken;
        }

        if (o == BUT1__WD or BUT2__WD or BUT3__WD) and_parity = 1-and_parity;

        if (~~many_flag) {
            multiple_object-->0 = 1;
            multiple_object-->1 = single_object;
            many_flag = true;
            #Ifdef DEBUG;
            if (parser_trace >= 3) print "  [Making new list from ", (the) single_object, "]^";
            #Endif; DEBUG
        }
        dont_infer = true; dont_infer_pronoun = false; inferfrom=0; Don't print (inferences)
        jump ObjectList; And back around
    }

    wn--;   Word marker back to first not-understood word

§32. Parse Token Letter F. Return the conclusion of parsing an object list.

    Happy or unhappy endings:

  .PassToken;
    if (many_flag) {
        single_object = GPR_MULTIPLE;
        multi_context = token;
    }
    else {
        if (indef_mode == 1 && indef_type & PLURAL_BIT ~= 0) {
            if (token == MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN) multi_context = token;
            if (indef_wanted < INDEF_ALL_WANTED && indef_wanted > 1) {
                multi_had = 1; multi_wanted = indef_wanted;
                #Ifdef DEBUG;
                if (parser_trace >= 4) print "Too few found at letter F^";
                #Endif; DEBUG
               jump FailToken;
            }
        }
    }
    return single_object;

  .FailToken;

    If we were only guessing about it being a plural, try again but only
    allowing singulars (so that words like "six" are not swallowed up as
    Descriptors)

    if (allow_plurals && indef_guess_p == 1) {
        #Ifdef DEBUG;
        if (parser_trace >= 4) print "   [Retrying singulars after failure ", etype, "]^";
        #Endif;
        prev_indef_wanted = indef_wanted;
        allow_plurals = false;
        wn = desc_wn;
        jump TryAgain;
    }

    if ((indef_wanted > 0 || prev_indef_wanted > 0) && (~~multiflag)) etype = MULTI_PE;

    return GPR_FAIL;

]; end of ParseToken__

§33. Descriptors. In grammatical terms, a descriptor appears at the front of an English noun phrase and clarifies the quantity or specific identity of what is referred to: for instance, {\it my} mirror, {\it the} dwarf, {\it that} woman. (Numbers, as in {\it four} duets, are also descriptors in linguistics: but the I6 parser doesn't handle them that way.)

Slightly unfortunately, the bitmap constants used for descriptors in the I6 parser have names in the form *_BIT, coinciding with the names of style bits in the list-writer: but they never occur in the same context.

The actual words used as descriptors are read from tables in the language definition. ArticleDescriptors uses this table to move current word marker past a run of one or more descriptors which refer to the definite or indefinite article.

Constant OTHER_BIT  =   1;      These will be used in Adjudicate()
Constant MY_BIT     =   2;      to disambiguate choices
Constant THAT_BIT   =   4;
Constant PLURAL_BIT =   8;
Constant LIT_BIT    =  16;
Constant UNLIT_BIT  =  32;

[ ResetDescriptors;
    indef_mode = 0; indef_type = 0; indef_wanted = 0; indef_guess_p = 0;
    indef_possambig = false;
    indef_owner = nothing;
    indef_cases = $$111111111111;
    indef_nspec_at = 0;
];

[ ArticleDescriptors  o x flag cto type n;
    if (wn > num_words) return 0;

    for (flag=true : flag :) {
        o = NextWordStopped(); flag = false;

       for (x=1 : x<=LanguageDescriptors-->0 : x=x+4)
            if (o == LanguageDescriptors-->x) {
                type = LanguageDescriptors-->(x+2);
                if (type == DEFART_PK or INDEFART_PK) flag = true;
            }
    }
    wn--;
    return 0;
];

§34. Parsing Descriptors. The Descriptors() routine parses the descriptors at the head of a noun phrase, leaving the current word marker wn at the first word of the noun phrase's body. It is allowed to set up for a plural only if allow_p is set; it returns a parser error number, or 0 if no error occurred.

[ Descriptors  o x flag cto type n;
    ResetDescriptors();
    if (wn > num_words) return 0;

    for (flag=true : flag :) {
        o = NextWordStopped(); flag = false;

       for (x=1 : x<=LanguageDescriptors-->0 : x=x+4)
            if (o == LanguageDescriptors-->x) {
                flag = true;
                type = LanguageDescriptors-->(x+2);
                if (type ~= DEFART_PK) indef_mode = true;
                indef_possambig = true;
                indef_cases = indef_cases & (LanguageDescriptors-->(x+1));

                if (type == POSSESS_PK) {
                    cto = LanguageDescriptors-->(x+3);
                    switch (cto) {
                      0: indef_type = indef_type | MY_BIT;
                      1: indef_type = indef_type | THAT_BIT;
                      default:
                        indef_owner = PronounValue(cto);
                        if (indef_owner == NULL) indef_owner = nothing;
                    }
                }

                if (type == LIGHTED_PK)  indef_type = indef_type | LIT_BIT;
                if (type == UNLIGHTED_PK) indef_type = indef_type | UNLIT_BIT;
            }

        if (o == OTHER1__WD or OTHER2__WD or OTHER3__WD) {
            indef_mode = 1; flag = 1;
            indef_type = indef_type | OTHER_BIT;
        }
        if (o == ALL1__WD or ALL2__WD or ALL3__WD or ALL4__WD or ALL5__WD) {
            indef_mode = 1; flag = 1; indef_wanted = INDEF_ALL_WANTED;
            if (take_all_rule == 1) take_all_rule = 2;
            indef_type = indef_type | PLURAL_BIT;
        }
        if (allow_plurals) {
            if (NextWordStopped() ~= -1 or THEN1__WD) { wn--; n = TryNumber(wn-1); } else { n=0; wn--; }
            if (n == 1) { indef_mode = 1; flag = 1; }
            if (n > 1) {
                indef_guess_p = 1;
                indef_mode = 1; flag = 1; indef_wanted = n;
                indef_nspec_at = wn-1;
                indef_type = indef_type | PLURAL_BIT;
            }
        }
        if (flag == 1 && NextWordStopped() ~= OF1__WD or OF2__WD or OF3__WD or OF4__WD)
            wn--;  Skip 'of' after these
    }
    wn--;
    return 0;
];

[ SafeSkipDescriptors;
    @push indef_mode; @push indef_type; @push indef_wanted;
    @push indef_guess_p; @push indef_possambig; @push indef_owner;
    @push indef_cases; @push indef_nspec_at;

    Descriptors();

    @pull indef_nspec_at; @pull indef_cases;
    @pull indef_owner; @pull indef_possambig; @pull indef_guess_p;
    @pull indef_wanted; @pull indef_type; @pull indef_mode;
];

§35. Preposition Chain. A small utility for runs of prepositions.

[ PrepositionChain wd index;
    if (line_tdata-->index == wd) return wd;
    if ((line_token-->index)->0 & $20 == 0) return -1;
    do {
        if (line_tdata-->index == wd) return wd;
        index++;
    } until ((line_token-->index == ENDIT_TOKEN) || (((line_token-->index)->0 & $10) == 0));
    return -1;
];

§36. Creature. Will this object do for an I6 creature token? (In I7 terms, this affects the tokens "[someone]", "[somebody]", "[anyone]" and "[anybody]".)

[ CreatureTest obj;
    if (obj has animate) rtrue;
    if (obj hasnt talkable) rfalse;
    if (action_to_be == ##Ask or ##Answer or ##Tell or ##AskFor) rtrue;
    rfalse;
];

§37. Noun Domain. NounDomain does the most substantial part of parsing an object name. It is given two "domains" — usually a location and then the actor who is looking — and a context (i.e. token type), and returns:

In case (c), NounDomain also sets the variable length_of_noun to the number of words in the input text matched to the noun. In case (b), the multiple objects are added to multiple_object by hand (not by MultiAdd, because we want to allow duplicates).

[ NounDomain domain1 domain2 context dont_ask
    first_word i j k l answer_words marker;
    #Ifdef DEBUG;
    if (parser_trace >= 4) {
        print "   [NounDomain called at word ", wn,
            " (domain1 ", (name) domain1, ", domain2 ", (name) domain2, ")^";
        print "   ";
        if (indef_mode) {
            print "seeking indefinite object: ";
            if (indef_type & OTHER_BIT)  print "other ";
            if (indef_type & MY_BIT)     print "my ";
            if (indef_type & THAT_BIT)   print "that ";
            if (indef_type & PLURAL_BIT) print "plural ";
            if (indef_type & LIT_BIT)    print "lit ";
            if (indef_type & UNLIT_BIT)  print "unlit ";
            if (indef_owner ~= 0) print "owner:", (name) indef_owner;
            new_line;
            print "   number wanted: ";
            if (indef_wanted == INDEF_ALL_WANTED) print "all"; else print indef_wanted;
            new_line;
            print "   most likely GNAs of names: ", indef_cases, "^";
        }
        else print "seeking definite object^";
    }
    #Endif; DEBUG

    match_length = 0; number_matched = 0; match_from = wn;

    SearchScope(domain1, domain2, context);

    #Ifdef DEBUG;
    if (parser_trace >= 4) print "   [ND made ", number_matched, " matches]^";
    #Endif; DEBUG

    wn = match_from+match_length;

    If nothing worked at all, leave with the word marker skipped past the
    first unmatched word...

    if (number_matched == 0) { wn++; rfalse; }

    Suppose that there really were some words being parsed (i.e., we did
    not just infer).  If so, and if there was only one match, it must be
    right and we return it...

    if (match_from <= num_words) {
        if (number_matched == 1) {
            i=match_list-->0;
            return i;
        }

        ...now suppose that there was more typing to come, i.e. suppose that
        the user entered something beyond this noun.  If nothing ought to follow,
        then there must be a mistake, (unless what does follow is just a full
        stop, and or comma)

        if (wn <= num_words) {
            i = NextWord(); wn--;
            if (i ~=  AND1__WD or AND2__WD or AND3__WD or comma_word
                   or THEN1__WD or THEN2__WD or THEN3__WD
                   or BUT1__WD or BUT2__WD or BUT3__WD) {
                if (lookahead == ENDIT_TOKEN) rfalse;
            }
        }
    }

    Now look for a good choice, if there's more than one choice...

    number_of_classes = 0;

    if (number_matched == 1) {
        i = match_list-->0;
        if (indef_mode == 1 && indef_type & PLURAL_BIT ~= 0) {
            if (context == MULTI_TOKEN or MULTIHELD_TOKEN or
                MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN or
                NOUN_TOKEN or HELD_TOKEN or CREATURE_TOKEN) {
                BeginActivity(DECIDING_WHETHER_ALL_INC_ACT, i);
                if ((ForActivity(DECIDING_WHETHER_ALL_INC_ACT, i)) &&
                    (RulebookFailed())) rfalse;
                EndActivity(DECIDING_WHETHER_ALL_INC_ACT, i);
            }
        }
    }
    if (number_matched > 1) {
        i = true;
        if (number_matched > 1)
            for (j=0 : j<number_matched-1 : j++)
                if (Identical(match_list-->j, match_list-->(j+1)) == false)
                    i = false;
        if (i) dont_infer = true;
        i = Adjudicate(context);
        if (i == -1) rfalse;
        if (i == 1) rtrue;    Adjudicate has made a multiple
                              object, and we pass it on
        dont_infer_pronoun = true; See bug I7-2115 for discussion of this
    }

    If i is non-zero here, one of two things is happening: either
    (a) an inference has been successfully made that object i is
        the intended one from the user's specification, or
    (b) the user finished typing some time ago, but we've decided
        on i because it's the only possible choice.
    In either case we have to keep the pattern up to date,
    note that an inference has been made and return.
    (Except, we don't note which of a pile of identical objects.)

    if (i ~= 0) {
        if (dont_infer) return i;
        if (inferfrom == 0) inferfrom=pcount;
        pattern-->pcount = i;
        return i;
    }

    if (dont_ask) return match_list-->0;

    If we get here, there was no obvious choice of object to make.  If in
    fact we've already gone past the end of the player's typing (which
    means the match list must contain every object in scope, regardless
    of its name), then it's foolish to give an enormous list to choose
    from - instead we go and ask a more suitable question...

    if (match_from > num_words) jump Incomplete;

    Now we print up the question, using the equivalence classes as worked
    out by Adjudicate() so as not to repeat ourselves on plural objects...

    BeginActivity(ASKING_WHICH_DO_YOU_MEAN_ACT);
    if (ForActivity(ASKING_WHICH_DO_YOU_MEAN_ACT)) jump SkipWhichQuestion;
    j = 1; marker = 0;
    for (i=1 : i<=number_of_classes : i++) {
        while (((match_classes-->marker) ~= i) && ((match_classes-->marker) ~= -i))
            marker++;
        if (match_list-->marker hasnt animate) j = 0;
    }
    if (j) PARSER_CLARIF_INTERNAL_RM('A');
    else PARSER_CLARIF_INTERNAL_RM('B');

    j = number_of_classes; marker = 0;
    for (i=1 : i<=number_of_classes : i++) {
        while (((match_classes-->marker) ~= i) && ((match_classes-->marker) ~= -i)) marker++;
        k = match_list-->marker;

        if (match_classes-->marker > 0) print (the) k; else print (a) k;

        if (i < j-1)  print ", ";
        if (i == j-1) {
            if (BasicInformKit`SERIAL_COMMA_CFGF) {
                if (j ~= 2) print ",";
            }
            PARSER_CLARIF_INTERNAL_RM('H');
        }
    }
    print "?^";

    .SkipWhichQuestion; EndActivity(ASKING_WHICH_DO_YOU_MEAN_ACT);

    ...and get an answer:

  .WhichOne;
    #Iftrue CHARSIZE == 1;
    for (i=2 : i<INPUT_BUFFER_LEN : i++) buffer2->i = ' ';
    #Endif;
    answer_words=Keyboard(buffer2, parse2);

    Conveniently, parse2-->1 is the first word in both ZCODE and GLULX.
    first_word = (parse2-->1);

    Take care of "all", because that does something too clever here to do
    later on:

    if (first_word == ALL1__WD or ALL2__WD or ALL3__WD or ALL4__WD or ALL5__WD) {
        if (context == MULTI_TOKEN or MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN) {
            l = multiple_object-->0;
            for (i=0 : i<number_matched && l+i<MATCH_LIST_WORDS : i++) {
                k = match_list-->i;
                multiple_object-->(i+1+l) = k;
            }
            multiple_object-->0 = i+l;
            rtrue;
        }
        PARSER_CLARIF_INTERNAL_RM('C');
        jump WhichOne;
    }

    Look for a comma, and interpret this as a fresh conversation command
    if so:

    for (i=1 : i<=answer_words : i++)
        if (WordFrom(i, parse2) == comma_word) {
            VM_CopyBuffer(buffer, buffer2);
            jump RECONSTRUCT_INPUT;
        }

    If the first word of the reply can be interpreted as a verb, then
    assume that the player has ignored the question and given a new
    command altogether.
    (This is one time when it's convenient that the directions are
    not themselves verbs - thus, "north" as a reply to "Which, the north
    or south door" is not treated as a fresh command but as an answer.)

    if (first_word == 0) {
        j = wn; first_word = LanguageIsVerb(buffer2, parse2, 1); wn = j;
    }
    if ((WordMarkedAsVerb(first_word)) && ~~LanguageVerbMayBeName(first_word)) {
        VM_CopyBuffer(buffer, buffer2);
        jump RECONSTRUCT_INPUT;
    }

    Now we insert the answer into the original typed command, as
    words additionally describing the same object
    (eg, > take red button
         Which one, ...
         > music
    becomes "take music red button".  The parser will thus have three
    words to work from next time, not two.)

    #Iftrue CHARSIZE == 1;

    k = WordAddress(match_from) - buffer; l=buffer2->1+1;
    for (j=buffer + buffer->0 - 1 : j>=buffer+k+l : j--) j->0 = 0->(j-l);
    for (i=0 : i<l : i++) buffer->(k+i) = buffer2->(2+i);
    buffer->(k+l-1) = ' ';
    buffer->1 = buffer->1 + l;
    if (buffer->1 >= (buffer->0 - 1)) buffer->1 = buffer->0;

    #Ifnot;

    k = (WordAddress(match_from) - buffer) / WORDSIZE;
    l = (buffer2-->0) + 1;
    for (j=INPUT_BUFFER_LEN-1 : j>=k+l : j--) buffer-->j = buffer-->(j-l);
    for (i=0 : i<l : i++) buffer-->(k+i) = buffer2-->(1+i);
    buffer-->(k+l-1) = ' ';
    buffer-->0 = buffer-->0 + l;
    if (buffer-->0 > (INPUT_BUFFER_LEN-WORDSIZE)) buffer-->0 = (INPUT_BUFFER_LEN-WORDSIZE);

    #Endif; CHARSIZE

    Having reconstructed the input, we warn the parser accordingly
    and get out.

    .RECONSTRUCT_INPUT;

    num_words = WordCount(); players_command = 100 + num_words;
    wn = 1;
    LanguageToInformese();
    Re-tokenise:
    VM_Tokenise(buffer,parse);
    num_words = WordCount(); players_command = 100 + num_words;
    actors_location = ScopeCeiling(player);
    FollowRulebook(Activity_after_rulebooks-->READING_A_COMMAND_ACT);

    return REPARSE_CODE;

    Now we come to the question asked when the input has run out
    and can't easily be guessed (eg, the player typed "take" and there
    were plenty of things which might have been meant).

  .Incomplete;

    if (context == CREATURE_TOKEN) PARSER_CLARIF_INTERNAL_RM('D', actor);
    else                           PARSER_CLARIF_INTERNAL_RM('E', actor);
    new_line;

    #Iftrue CHARSIZE == 1;
    for (i=2 : i<INPUT_BUFFER_LEN : i++) buffer2->i=' ';
    #Endif;
    answer_words = Keyboard(buffer2, parse2);

    Look for a comma, and interpret this as a fresh conversation command
    if so:

    for (i=1 : i<=answer_words : i++)
        if (WordFrom(i, parse2) == comma_word) {
            VM_CopyBuffer(buffer, buffer2);
            jump RECONSTRUCT_INPUT;
        }

    first_word=(parse2-->1);
    if (first_word==0) {
        j = wn; first_word=LanguageIsVerb(buffer2, parse2, 1); wn = j;
    }

    Once again, if the reply looks like a command, give it to the
    parser to get on with and forget about the question...

    if ((WordMarkedAsVerb(first_word)) && ~~LanguageVerbMayBeName(first_word)) {
        VM_CopyBuffer(buffer, buffer2);
        jump RECONSTRUCT_INPUT;
    }

    ...but if we have a genuine answer, then:
    
    (1) we must glue in text suitable for anything that's been inferred.

    if (inferfrom ~= 0) {
        for (j=inferfrom : j<pcount : j++) {
            if (pattern-->j == PATTERN_NULL) continue;
            #Iftrue CHARSIZE == 1;
            i = 2+buffer->1; (buffer->1)++; buffer->(i++) = ' ';
            #Ifnot;
            i = 1 + buffer-->0;
            (buffer-->0)++; buffer-->(i++) = ' ';
            #Endif;

            #Ifdef DEBUG;
            if (parser_trace >= 5)
                print "[Gluing in inference at ", j, " with pattern code ", pattern-->j, "]^";
            #Endif; DEBUG

            Conveniently, parse2-->1 is the first word in both ZCODE and GLULX.

            parse2-->1 = 0;

            An inferred object.  Best we can do is glue in a pronoun.
            (This is imperfect, but it's very seldom needed anyway.)

            if (pattern-->j >= 2 && pattern-->j < REPARSE_CODE) {
                if (dont_infer_pronoun == false) {
                    PronounNotice(pattern-->j);
                    for (k=1 : k<=LanguagePronouns-->0 : k=k+3)
                        if (pattern-->j == LanguagePronouns-->(k+2)) {
                            parse2-->1 = LanguagePronouns-->k;
                            #Ifdef DEBUG;
                            if (parser_trace >= 5)
                                print "[Using pronoun '", (address) parse2-->1, "']^";
                            #Endif; DEBUG
                            break;
                        }
                }
            }
            else {
                An inferred preposition.
                parse2-->1 = VM_NumberToDictionaryAddress(pattern-->j - REPARSE_CODE);
                #Ifdef DEBUG;
                if (parser_trace >= 5)
                    print "[Using preposition '", (address) parse2-->1, "']^";
                #Endif; DEBUG
            }

            parse2-->1 now holds the dictionary address of the word to glue in.

            if (parse2-->1 ~= 0) {
                #Ifdef TARGET_ZCODE;
                k = buffer + i;
                @output_stream 3 k;
                print (address) parse2-->1;
                @output_stream -3;
                k = k-->0;
                for (l=i : l<i+k : l++) buffer->l = buffer->(l+2);
                i = i + k; buffer->1 = i-2;
                #Ifnot; TARGET_GLULX
                k = Glulx_PrintAnyToArrayUni(buffer+WORDSIZE*i, INPUT_BUFFER_LEN-i, parse2-->1);
                i = i + k; buffer-->0 = i - 1;
                #Endif; TARGET_
            }
        }
    }

    (2) we must glue the newly-typed text onto the end.

    #Iftrue CHARSIZE == 1;

    i = 2+buffer->1; (buffer->1)++; buffer->(i++) = ' ';
    for (j=0 : j<buffer2->1 : i++,j++) {
        buffer->i = buffer2->(j+2);
        (buffer->1)++;
        if (buffer->1 == INPUT_BUFFER_LEN) break;
    }

    #Ifnot;

    i = 1 + buffer-->0;
    (buffer-->0)++; buffer-->(i++) = ' ';
    for (j=0 : j<buffer2-->0 : i++,j++) {
        buffer-->i = buffer2-->(j+1);
        (buffer-->0)++;
        if (buffer-->0 == INPUT_BUFFER_LEN) break;
    }

    #Endif; CHARSIZE

    (3) we fill up the buffer with spaces, which is unnecessary, but may
        help incorrectly-written interpreters to cope.

    #Iftrue CHARSIZE == 1;
    for (: i<INPUT_BUFFER_LEN : i++) buffer->i = ' ';
    #Endif;

    jump RECONSTRUCT_INPUT;

]; end of NounDomain

[ PARSER_CLARIF_INTERNAL_R; ];

§38. Adjudicate. The Adjudicate routine tries to see if there is an obvious choice, when faced with a list of objects (the match_list) each of which matches the player's specification equally well. To do this it makes use of the context (the token type being worked on).

It counts up the number of obvious choices for the given context — all to do with where a candidate is, except for 6 (animate) which is to do with whether it is animate or not — and then:

Adjudicate returns \(-1\) if an error occurred.

[ Adjudicate context i j k good_ones last n ultimate flag offset;
    #Ifdef DEBUG;
    if (parser_trace >= 4) {
        print "   [Adjudicating match list of size ", number_matched,
            " in context ", context, "^";
        print "   ";
        if (indef_mode) {
            print "indefinite type: ";
            if (indef_type & OTHER_BIT)  print "other ";
            if (indef_type & MY_BIT)     print "my ";
            if (indef_type & THAT_BIT)   print "that ";
            if (indef_type & PLURAL_BIT) print "plural ";
            if (indef_type & LIT_BIT)    print "lit ";
            if (indef_type & UNLIT_BIT)  print "unlit ";
            if (indef_owner ~= 0) print "owner:", (name) indef_owner;
            new_line;
            print "   number wanted: ";
            if (indef_wanted == INDEF_ALL_WANTED) print "all"; else print indef_wanted;
            new_line;
            print "   most likely GNAs of names: ", indef_cases, "^";
        }
        else print "definite object^";
    }
    #Endif; DEBUG

    j = number_matched-1; good_ones = 0; last = match_list-->0;
    for (i=0 : i<=j : i++) {
        n = match_list-->i;
        match_scores-->i = good_ones;
        ultimate = ScopeCeiling(n);

        if (context==HELD_TOKEN && parent(n)==actor)
        {   good_ones++; last=n; }
        if (context==MULTI_TOKEN && ultimate==ScopeCeiling(actor)
            && n~=actor && n hasnt concealed && n hasnt scenery)
        {   good_ones++; last=n; }
        if (context==MULTIHELD_TOKEN && parent(n)==actor)
        {   good_ones++; last=n; }

        if (context==MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN)
        {   if (advance_warning==-1)
            {   if (context==MULTIEXCEPT_TOKEN)
                {   good_ones++; last=n;
                 }
                if (context==MULTIINSIDE_TOKEN)
                {   if (parent(n)~=actor) { good_ones++; last=n; }
                 }
            }
            else
            {   if (context==MULTIEXCEPT_TOKEN && n~=advance_warning)
                {   good_ones++; last=n; }
                if (context==MULTIINSIDE_TOKEN && n in advance_warning)
                {   good_ones++; last=n; }
            }
         }
        if (context==CREATURE_TOKEN && CreatureTest(n)==1)
        {   good_ones++; last=n; }

        match_scores-->i = 1000*(good_ones - match_scores-->i);
    }
    if (good_ones == 1) {
        if (indef_mode == 1 && indef_type & PLURAL_BIT ~= 0 &&
            context == MULTI_TOKEN or MULTIHELD_TOKEN or
                MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN) {
            BeginActivity(DECIDING_WHETHER_ALL_INC_ACT, last);
            if ((ForActivity(DECIDING_WHETHER_ALL_INC_ACT, last)) &&
                (RulebookFailed())) good_ones = 0;
            EndActivity(DECIDING_WHETHER_ALL_INC_ACT, last);
            if (good_ones == 1) return last;
        } else {
            return last;
        }
    }

    If there is ambiguity about what was typed, but it definitely wasn't
    animate as required, then return anything; higher up in the parser
    a suitable error will be given.  (This prevents a question being asked.)

    if (context == CREATURE_TOKEN && good_ones == 0) return match_list-->0;

    if (indef_mode == 0) indef_type=0;

    ScoreMatchL(context);
    if (number_matched == 0) return -1;

    if (indef_mode == 0) {
         Is there now a single highest-scoring object?
        i = SingleBestGuess();
        if (i >= 0) {

            #Ifdef DEBUG;
            if (parser_trace >= 4) print "   Single best-scoring object returned.]^";
            #Endif; DEBUG
            return i;
        }
    }

    if (indef_mode == 1 && indef_type & PLURAL_BIT ~= 0) {
        if (context ~= MULTI_TOKEN or MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN
                     or MULTIINSIDE_TOKEN) {
            etype = MULTI_PE;
            return -1;
        }
        i = 0; offset = multiple_object-->0;
        for (j=BestGuess(): j~=-1 && i<indef_wanted && i+offset<MATCH_LIST_WORDS-1:
            j=BestGuess()) {
            flag = 0;
            BeginActivity(DECIDING_WHETHER_ALL_INC_ACT, j);
            if ((ForActivity(DECIDING_WHETHER_ALL_INC_ACT, j)) == 0) {
                if (j hasnt concealed && j hasnt worn) flag = 1;
                if (context == MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN && parent(j) ~= actor)
                    flag = 0;

                if (action_to_be == ##Take or ##Remove && parent(j) == actor)
                    flag = 0;

                k = ChooseObjects(j, flag);

                if (k == 1)
                    flag = 1;
                else {
                    if (k == 2) flag = 0;
                }
            } else {
                flag = 0; if (RulebookSucceeded()) flag = 1;
            }
            EndActivity(DECIDING_WHETHER_ALL_INC_ACT, j);
            if (flag == 1) {
                i++; multiple_object-->(i+offset) = j;
                #Ifdef DEBUG;
                if (parser_trace >= 4) print "   Accepting it^";
                #Endif; DEBUG
            }
            else {
                i = i;
                #Ifdef DEBUG;
                if (parser_trace >= 4) print "   Rejecting it^";
                #Endif; DEBUG
            }
        }
        if (i < indef_wanted && indef_wanted < INDEF_ALL_WANTED) {
            multi_wanted = indef_wanted;
            #Ifdef DEBUG;
            if (parser_trace >= 4) print "Too few found in Adjudicate^";
            #Endif; DEBUG
            multi_had=i; Allow to proceed for now
        }
        multiple_object-->0 = i+offset;
        multi_context = context;
        #Ifdef DEBUG;
        if (parser_trace >= 4)
            print "   Made multiple object of size ", i, "]^";
        #Endif; DEBUG
        return 1;
    }

    for (i=0 : i<number_matched : i++) match_classes-->i = 0;

    n = 1;
    for (i=0 : i<number_matched : i++)
        if (match_classes-->i == 0) {
            match_classes-->i = n++; flag = 0;
            for (j=i+1 : j<number_matched : j++)
                if (match_classes-->j == 0 && Identical(match_list-->i, match_list-->j) == 1) {
                    flag=1;
                    match_classes-->j = match_classes-->i;
                }
            if (flag == 1) match_classes-->i = 1-n;
        }
     n--; number_of_classes = n;

    #Ifdef DEBUG;
    if (parser_trace >= 4) {
        print "   Grouped into ", n, " possibilities by name:^";
        for (i=0 : i<number_matched : i++)
            if (match_classes-->i > 0)
                print "   ", (The) match_list-->i, " (", match_list-->i, ")  ---  group ",
                  match_classes-->i, "^";
    }
    #Endif; DEBUG

    if (indef_mode == 0) {
        if (n > 1) {
            k = -1;
            for (i=0 : i<number_matched : i++) {
                if (match_scores-->i > k) {
                    k = match_scores-->i;
                    j = match_classes-->i; j = j*j;
                    flag = 0;
                }
                else
                    if (match_scores-->i == k) {
                        if ((match_classes-->i) * (match_classes-->i) ~= j)
                            flag = 1;
                    }
            }

        if (flag) {
            #Ifdef DEBUG;
            if (parser_trace >= 4) print "   Unable to choose best group, so ask player.]^";
            #Endif; DEBUG
            return 0;
        }
        #Ifdef DEBUG;
        if (parser_trace >= 4) print "   Best choices are all from the same group.^";
        #Endif; DEBUG
        }
    }

     When the player is really vague, or there's a single collection of
     indistinguishable objects to choose from, choose the one the player
     most recently acquired, or if the player has none of them, then
     the one most recently put where it is.

    if (n == 1) dont_infer = true;
    return BestGuess();

]; Adjudicate

§39. ReviseMulti. ReviseMulti revises the multiple object which already exists, in the light of information which has come along since then (i.e., the second parameter). It returns a parser error number, or else 0 if all is well. This only ever throws things out, never adds new ones.

[ ReviseMulti second_p  i low;
    #Ifdef DEBUG;
    if (parser_trace >= 4)
        print "   Revising multiple object list of size ", multiple_object-->0,
        " with 2nd ", (name) second_p, "^";
    #Endif; DEBUG

    if (multi_context == MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN) {
        for (i=1,low=0 : i<=multiple_object-->0 : i++) {
            if ( (multi_context==MULTIEXCEPT_TOKEN && multiple_object-->i ~= second_p) ||
                 (multi_context==MULTIINSIDE_TOKEN && multiple_object-->i in second_p)) {
                low++;
                multiple_object-->low = multiple_object-->i;
            }
        }
        multiple_object-->0 = low;
    }

    if (multi_context == MULTI_TOKEN && action_to_be == ##Take) {
        #Ifdef DEBUG;
        if (parser_trace >= 4) print "   Token 2 plural case: number with actor ", low, "^";
        #Endif; DEBUG
        if (take_all_rule == 2) {
            for (i=1,low=0 : i<=multiple_object-->0 : i++) {
                if (ScopeCeiling(multiple_object-->i) == ScopeCeiling(actor)) {
                    low++;
                    multiple_object-->low = multiple_object-->i;
                }
            }
            multiple_object-->0 = low;
        }
    }

    i = multiple_object-->0;
    #Ifdef DEBUG;
    if (parser_trace >= 4) print "   Done: new size ", i, "^";
    #Endif; DEBUG
    if (i == 0) return NOTHING_PE;
    return 0;
];

§40. Match List. The match list is an array, match_list-->, which holds the current best guesses at what object(s) a portion of the command refers to. The global number_matched is set to the current length of the match_list.

When the parser sees a possible match of object obj at quality level q, it calls MakeMatch(obj, q). If this is the best quality match so far, then we wipe out all the previous matches and start a new list with this one. If it's only as good as the best so far, we add it to the list (provided we haven't run out of space, and provided it isn't in the list already). If it's worse, we ignore it altogether.

I6 tokens in the form noun=Filter or Attribute are "noun filter tokens", and mean that the match list should be filtered to accept only nouns which are acceptable to the given routine, or have the given attribute. Such a token is in force if token_filter is used. (I7 makes no use of this in the attribute case, which is deprecated nowadays.)

Quality is essentially the number of words in the command referring to the object: the idea is that "red panic button" is better than "red button" or "panic".

[ MakeMatch obj quality i;
    #Ifdef DEBUG;
    if (parser_trace >= 6) print "    Match with quality ",quality,"^";
    #Endif; DEBUG
    if (token_filter ~= 0 && ConsultNounFilterToken(obj) == 0) {
        #Ifdef DEBUG;
        if (parser_trace >= 6) print "    Match filtered out: token filter ", token_filter, "^";
        #Endif; DEBUG
        rtrue;
    }
    if (quality < match_length) rtrue;
    if (quality > match_length) { match_length = quality; number_matched = 0; }
    else {
        if (number_matched >= MATCH_LIST_WORDS) rtrue;
        for (i=0 : i<number_matched : i++)
            if (match_list-->i == obj) rtrue;
    }
    match_list-->number_matched++ = obj;
    #Ifdef DEBUG;
    if (parser_trace >= 6) print "    Match added to list^";
    #Endif; DEBUG
];

[ ConsultNounFilterToken obj sn rv;
    if (token_filter ofclass Routine) {
        sn = noun;
        noun = obj;
        rv = token_filter();
        noun = sn;
        return rv;
    }
    if (obj has (token_filter-1)) rtrue;
    rfalse;
];

§41. ScoreMatchL. ScoreMatchL scores the match list for quality in terms of what the player has vaguely asked for. Points are awarded for conforming with requirements like "my", and so on. Remove from the match list any entries which fail the basic requirements of the descriptors. (The scoring system used to evaluate the possibilities is discussed in detail in the DM4.)

Constant SCORE__CHOOSEOBJ = 1000;
Constant SCORE__IFGOOD = 500;
Constant SCORE__UNCONCEALED = 100;
Constant SCORE__BESTLOC = 60;
Constant SCORE__NEXTBESTLOC = 40;
Constant SCORE__NOTCOMPASS = 20;
Constant SCORE__NOTSCENERY = 10;
Constant SCORE__NOTACTOR = 5;
Constant SCORE__GNA = 1;
Constant SCORE__DIVISOR = 20;

[ ScoreMatchL context its_owner its_score obj i j threshold met a_s l_s;
  if (indef_type & OTHER_BIT ~= 0) threshold++;
    if (indef_type & MY_BIT ~= 0)    threshold++;
    if (indef_type & THAT_BIT ~= 0)  threshold++;
    if (indef_type & LIT_BIT ~= 0)   threshold++;
    if (indef_type & UNLIT_BIT ~= 0) threshold++;
    if (indef_owner ~= nothing)      threshold++;

    #Ifdef DEBUG;
    if (parser_trace >= 4) print "   Scoring match list: indef mode ", indef_mode, " type ",
      indef_type, ", satisfying ", threshold, " requirements:^";
    #Endif; DEBUG

    a_s = SCORE__BESTLOC; l_s = SCORE__NEXTBESTLOC;
    if (action_to_be == ##Take or ##Remove) {
        a_s = SCORE__NEXTBESTLOC; l_s = SCORE__BESTLOC;
    }
    context = context;  silence warning

    for (i=0 : i<number_matched : i++) {
        obj = match_list-->i; its_owner = parent(obj); its_score=0; met=0;

             if (indef_type & OTHER_BIT ~= 0
                 &&  obj ~= itobj or himobj or herobj) met++;
        if (indef_type & MY_BIT ~= 0 && its_owner == actor) met++;
        if (indef_type & THAT_BIT ~= 0 && its_owner == actors_location) met++;
        if (indef_type & LIT_BIT ~= 0 && obj has light) met++;
        if (indef_type & UNLIT_BIT ~= 0 && obj hasnt light) met++;
        if (indef_owner ~= 0 && ((its_owner == indef_owner) || (CoreOf(obj) == indef_owner))) met++;
        if (met < threshold) {
            #Ifdef DEBUG;
            if (parser_trace >= 4)
                print "   ", (The) match_list-->i, " (", match_list-->i, ") in ",
                    (the) its_owner, " is rejected (doesn't match descriptors)^";
            #Endif; DEBUG
            match_list-->i = -1;
        }
        else {
            its_score = 0;
            if (obj hasnt concealed) its_score = SCORE__UNCONCEALED;

            if (its_owner == actor) its_score = its_score + a_s;
            else
                if (its_owner == actors_location) its_score = its_score + l_s;
                else
                    if (its_owner ~= Compass) its_score = its_score + SCORE__NOTCOMPASS;

            its_score = its_score + SCORE__CHOOSEOBJ * ChooseObjects(obj, 2);

            if (obj hasnt scenery) its_score = its_score + SCORE__NOTSCENERY;
            if (obj ~= actor) its_score = its_score + SCORE__NOTACTOR;

              A small bonus for having the correct GNA,
              for sorting out ambiguous articles and the like.

            if (indef_cases & (PowersOfTwo_TB-->(GetGNAOfObject(obj))))
                its_score = its_score + SCORE__GNA;

            match_scores-->i = match_scores-->i + its_score;
            #Ifdef DEBUG;
            if (parser_trace >= 4) print "     ", (The) match_list-->i,
              " in ", (the) its_owner, " : ", match_scores-->i, " points^";
            #Endif; DEBUG
        }
     }

    for (i=0 : i<number_matched : i++) {
        while (match_list-->i == -1) {
            if (i == number_matched-1) { number_matched--; break; }
            for (j=i : j<number_matched-1 : j++) {
                match_list-->j = match_list-->(j+1);
                match_scores-->j = match_scores-->(j+1);
            }
            number_matched--;
        }
    }
];

§42. BestGuess. BestGuess makes the best guess it can out of the match list, assuming that everything in the match list is textually as good as everything else; however it ignores items marked as \(-1\), and so marks anything it chooses. It returns \(-1\) if there are no possible choices.

[ BestGuess  earliest its_score best i;
    earliest = 0; best = -1;
    for (i=0 : i<number_matched : i++) {
        if (match_list-->i >= 0) {
            its_score = match_scores-->i;
            if (its_score > best) { best = its_score; earliest = i; }
        }
    }
    #Ifdef DEBUG;
    if (parser_trace >= 4)
      if (best < 0) print "   Best guess ran out of choices^";
      else print "   Best guess ", (the) match_list-->earliest, "^";
    #Endif; DEBUG
    if (best < 0) return -1;
    i = match_list-->earliest;
    match_list-->earliest = -1;
    return i;
];

§43. SingleBestGuess. SingleBestGuess returns the highest-scoring object in the match list if it is the clear winner, or returns \(-1\) if there is no clear winner.

[ SingleBestGuess  earliest its_score best i;
    earliest = -1; best = -1000;
    for (i=0 : i<number_matched : i++) {
        its_score = match_scores-->i;
        if (its_score == best) earliest = -1;
        if (its_score > best) { best = its_score; earliest = match_list-->i; }
    }
    return earliest;
];

§44. Identical. Identical decides whether or not two objects can be distinguished from each other by anything the player can type. If not, it returns true. (This routine is critical to the handling of plurals, and the list-writer requires it to be an equivalence relation between objects: but it is, because it is equivalent to \(O_1\sim O_2\) if and only if \(f(O_1) = f(O_2)\) for some function \(f\).)

[ Identical o1 o2 p1 p2 n1 n2 i j flag;
    if (o1 == o2) rtrue;  This should never happen, but to be on the safe side
    if (o1 == 0 || o2 == 0) rfalse;  Similarly
    if (o1 ofclass K3_direction || o2 ofclass K3_direction) rfalse; Saves time

     What complicates things is that o1 or o2 might have a parsing routine,
     so the parser can't know from here whether they are or aren't the same.
     If they have different parsing routines, we simply assume they're
     different.  If they have the same routine (which they probably got from
     a class definition) then the decision process is as follows:
    
        the routine is called (with self being o1, not that it matters)
          with noun and second being set to o1 and o2, and action being set
          to the fake action TheSame.  If it returns -1, they are found
          identical; if -2, different; and if >=0, then the usual method
          is used instead.

    if (o1.parse_name ~= 0 || o2.parse_name ~= 0) {
      if (o1.parse_name ~= o2.parse_name) rfalse;
      parser_action = ##TheSame; parser_one = o1; parser_two = o2;
      j = wn; i = RunRoutines(o1,parse_name); wn = j;
      if (i == -1) rtrue;
      if (i == -2) rfalse;
    }

     This is the default algorithm: do they have the same words in their
     "name" (i.e. property no. 1) properties.  (Note that the following allows
     for repeated words and words in different orders.)

    p1 = o1.&name; n1 = (o1.#name)/WORDSIZE;
    p2 = o2.&name; n2 = (o2.#name)/WORDSIZE;

      for (i=0 : i<n1 : i++) { print (address) p1-->i, " "; } new_line;
      for (i=0 : i<n2 : i++) { print (address) p2-->i, " "; } new_line;

    for (i=0 : i<n1 : i++) {
        flag = 0;
        for (j=0 : j<n2 : j++)
            if (p1-->i == p2-->j) flag = 1;
        if (flag == 0) rfalse;
    }

    for (j=0 : j<n2 : j++) {
        flag = 0;
        for (i=0 : i<n1 : i++)
            if (p1-->i == p2-->j) flag = 1;
        if (flag == 0) rfalse;
    }

      print "Which are identical!^";
    rtrue;
];

§45. Print Command. PrintCommand reconstructs the command as it presently reads, from the pattern which has been built up.

If from is 0, it starts with the verb: then it goes through the pattern.

The other parameter is emptyf — a flag: if 0, it goes up to pcount: if 1, it goes up to pcount-1.

Note that verbs and prepositions are printed out of the dictionary: and that since the dictionary may only preserve the first six characters of a word (in a V3 game), we have to hand-code the longer words needed. At present, I7 doesn't do this, but it probably should.

(Recall that pattern entries are 0 for "multiple object", 1 for "special word", 2 to REPARSE_CODE-1 are object numbers and REPARSE_CODE+n means the preposition n.)

[ PrintInferredCommand from singleton_noun;
    singleton_noun = false;
    if ((from ~= 0) && (from == pcount-1) &&
        (pattern-->from > 1) && (pattern-->from < REPARSE_CODE))
            singleton_noun = true;
    if (singleton_noun) {
        BeginActivity(CLARIFYING_PARSERS_CHOICE_ACT, pattern-->from);
        if (ForActivity(CLARIFYING_PARSERS_CHOICE_ACT, pattern-->from) == 0) {
            print "("; PrintCommand(from); print ")^";
        }
        EndActivity(CLARIFYING_PARSERS_CHOICE_ACT, pattern-->from);
    } else {
        print "("; PrintCommand(from); print ")^";
    }
];

[ PrintCommand from i k spacing_flag;
    if (from == 0) {
        i = verb_word;
        if (LanguageVerb(i) == 0)
            if (PrintVerb(i) == 0) print (address) i;
        from++; spacing_flag = true;
    }
    for (k=from : k<pcount : k++) {
        i = pattern-->k;
        if (i == PATTERN_NULL) continue;
        if (spacing_flag) print (char) ' ';
        if (i == 0) { PARSER_CLARIF_INTERNAL_RM('F'); jump TokenPrinted; }
        if (i == 1) { PARSER_CLARIF_INTERNAL_RM('G'); jump TokenPrinted; }
        if (i >= REPARSE_CODE)
            print (address) VM_NumberToDictionaryAddress(i-REPARSE_CODE);
        else
            if (i ofclass K3_direction)
                print (LanguageDirection) i; the direction name as adverb
            else
                print (the) i;
      .TokenPrinted;
        spacing_flag = true;
    }
];

§46. CantSee. The CantSee routine returns a good error number for the situation where the last word looked at didn't seem to refer to any object in context.

The idea is that: if the actor is in a location (but not inside something like, for instance, a tank which is in that location) then an attempt to refer to one of the words listed as meaningful-but-irrelevant there will cause "you don't need to refer to that in this game" rather than "no such thing" or "what's `it'?".

(The advantage of not having looked at "irrelevant" local nouns until now is that it stops them from clogging up the ambiguity-resolving process. Thus game objects always triumph over scenery.)

[ CantSee  i w e;
    saved_oops=oops_from;

    if (scope_token ~= 0) {
        scope_error = scope_token; return ASKSCOPE_PE;
    }

    wn--; w = NextWord();
    e = CANTSEE_PE;
    if (w == pronoun_word) {
        w = NextWordStopped(); wn--;
        if ((w == -1) || (line_token-->(pcount) ~= ENDIT_TOKEN)) {
            if (pcount > 0) AnalyseToken(line_token-->(pcount-1));
            if ((pcount > 0) && (found_ttype == ROUTINE_FILTER_TT or ATTR_FILTER_TT))
                e = NOTINCONTEXT_PE;
            else {
                pronoun__word = pronoun_word; pronoun__obj = pronoun_obj;
                e = ITGONE_PE;
            }
        }
    }

    if (etype > e) return etype;
    return e;
];

§47. Multiple Object List. The MultiAdd routine adds object o to the multiple-object-list. This is only allowed to hold MATCH_LIST_WORDS minus one objects at most, at which point it ignores any new entries (and sets a global flag so that a warning may later be printed if need be).

The MultiSub routine deletes object o from the multiple-object-list. It returns 0 if the object was there in the first place, and 9 (because this is the appropriate error number in Parser()) if it wasn't.

The MultiFilter routine goes through the multiple-object-list and throws out anything without the given attribute attr set.

[ MultiAdd o i j;
    i = multiple_object-->0;
    if (i == MATCH_LIST_WORDS-1) { toomany_flag = 1; rtrue; }
    for (j=1 : j<=i : j++)
        if (o == multiple_object-->j) rtrue;
    i++;
    multiple_object-->i = o;
    multiple_object-->0 = i;
];

[ MultiSub o i j k;
    i = multiple_object-->0;
    for (j=1 : j<=i : j++)
        if (o == multiple_object-->j) {
            for (k=j : k<=i : k++) multiple_object-->k = multiple_object-->(k+1);
            multiple_object-->0 = --i;
            return 0;
        }
    return VAGUE_PE;
];

[ MultiFilter attr  i j o;
    .MFiltl;
    i = multiple_object-->0;
    for (j=1 : j<=i : j++) {
        o = multiple_object-->j;
        if (o hasnt attr) { MultiSub(o); jump MFiltl; }
    }
];

§48. Scope. The scope of an actor is the set of objects which he can refer to in typed commands, which is normally the same as the set of visible objects; but this can be modified. This is how I7 handles tokens like "[any room]".

Scope determination is done by calling SearchScope to iterate through the objects in scope, and "visit" each one: which means, carry out some task for each as we get there. The task depends on the current value of scope_reason, which is PARSING_REASON when the parser is matching command text against object names.

The scope machinery is built on a number of levels, each making use only of lower levels:

Two routines are provided for code external to the parser to modify the scope. They should be called only during scope deliberations — i.e., in scope=... tokens or in rules for the "deciding the scope of" activity. (At present, AddToScope is not used in I7 at all.) Note that this I7 form of PlaceInScope has a slightly different specification to its I6 library counterpart of the same name: it can place a room in scope. (In I6, room names were not normally parsed.)

[ PlaceInScope O opts ws; If opts is set, do not place contents in scope
    ws = wn; wn = match_from;
    if (opts == false) DoScopeActionAndRecurse(O);
    else DoScopeAction(O);
    wn = ws; return;
];

[ AddToScope obj;
    if (ats_flag >= 2) DoScopeActionAndRecurse(obj, 0, ats_flag-2);
    if (ats_flag == 1) { if (HasLightSource(obj)==1) ats_hls = 1; }
];

§49. Scope Level 0. The two ways of starting up the scope machinery other than via the parser code above.

[ TestScope obj act a al sr ss st x y;
    x = parser_one; y = parser_two;
    parser_one = obj; parser_two = 0; a = actor; al = actors_location;
    ss = scope_stage; st = scope_token;
    scope_stage = 0; scope_token = 0;
    sr = scope_reason; scope_reason = TESTSCOPE_REASON;
    if (act == 0) actor = player; else actor = act;
    actors_location = ScopeCeiling(actor);
    SearchScope(actors_location, actor, 0); scope_reason = sr; actor = a;
    scope_stage = ss; scope_token = st;
    actors_location = al; parser_one = x; x = parser_two; parser_two = y;
    return x;
];

[ LoopOverScope routine act x y a al;
    x = parser_one; y = scope_reason; a = actor; al = actors_location;
    parser_one = routine;
    if (act == 0) actor = player; else actor = act;
    actors_location = ScopeCeiling(actor);
    scope_reason = LOOPOVERSCOPE_REASON;
    SearchScope(actors_location, actor, 0);
    parser_one = x; scope_reason = y; actor = a; actors_location = al;
];

§50. SearchScope. Level 1. The method is:

[ SearchScope domain1 domain2 context i;
    if (domain1 == 0) return;
    (a)
    if (scope_token) {
        scope_stage = 2;
        #Ifdef DEBUG;
        if (parser_trace >= 3) print "  [Scope routine called at stage 2]^";
        #Endif;
        if (scope_token() ~= 0) rtrue;
    }
    (b)
    BeginActivity(DECIDING_SCOPE_ACT, actor);
    if (ForActivity(DECIDING_SCOPE_ACT, actor) == false) {
        (c.1)
        if ((scope_reason == PARSING_REASON) && (context == MULTIINSIDE_TOKEN) &&
            (advance_warning ~= -1)) {
            if (IsSeeThrough(advance_warning) == 1)
                ScopeWithin(advance_warning, 0, context);
        } else {
            (c.2)
            if ((scope_reason == PARSING_REASON) && (context ~= CREATURE_TOKEN) &&
                (indef_mode == 0) && (domain1 == actors_location))
                    ScopeWithin(Compass);
            (c.3)
            if (domain1 has supporter or container) DoScopeAction(domain1);
            ScopeWithin(domain1, domain2, context);
            (c.4)
            if (domain2) {
                if (domain2 has supporter or container) DoScopeAction(domain2);
                ScopeWithin(domain2, 0, context);
            }
        }
        (c.5)
        if (thedark == domain1 or domain2) {
            DoScopeActionAndRecurse(actor, actor, context);
            if (parent(actor) has supporter or container)
                DoScopeActionAndRecurse(parent(actor), parent(actor), context);
        }
    }
    EndActivity(DECIDING_SCOPE_ACT, actor);
];

§51. ScopeWithin. Level 2. ScopeWithin puts objects visible from within the domain into scope. An item belonging to the domain is placed in scope unless it is being concealed by the domain: and even then, if the domain is the current actor. Suppose Zorro conceals a book beneath his cloak: then the book is not in scope to his lady friend The Black Whip, but it is in scope to Zorro himself. (Thus an actor is not allowed to conceal anything from himself.)

Note that the domain object itself, and its component parts if any, are not placed in scope by this routine, though nothing prevents some other code doing so.

[ ScopeWithin domain nosearch context obj next_obj;
    if (domain == 0) rtrue;
    Look through the objects in the domain, avoiding "objectloop" in case
    movements occur.
    obj = child(domain);
    while (obj) {
        next_obj = sibling(obj);
        if ((domain == actor) || (TestConcealment(domain, obj) == false))
            DoScopeActionAndRecurse(obj, nosearch, context);
        obj = next_obj;
    }
];

§52. DoScopeActionAndRecurse. Level 3. In all cases, the domain itself is visited. There are then three possible forms of recursion:

[ DoScopeActionAndRecurse domain nosearch context i ad n obj next_obj;
    DoScopeAction(domain);

    (a)
    if ((domain ~= nosearch) &&
        ((domain ofclass K1_room or K8_person) || (IsSeeThrough(domain) == 1))) {
        obj = child(domain);
        while (obj) {
            next_obj = sibling(obj);
            if ((domain == actor) || (TestConcealment(domain, obj) == false))
                DoScopeActionAndRecurse(obj, nosearch, context);
            obj = next_obj;
        }
    }

    (b)
    if (domain provides component_child) {
        obj = domain.component_child;
        while (obj) {
            next_obj = obj.component_sibling;
            if ((domain == actor) || (TestConcealment(domain, obj) == false))
                DoScopeActionAndRecurse(obj, 0, context);
            obj = next_obj;
        }
    }

    (c)
    ad = domain.&add_to_scope;
    if (ad ~= 0) {
        Test if the property value is not an object.
        i = (metaclass(ad-->0) == Object);
        if (i) {
            ats_flag = 2+context;
            RunRoutines(domain, add_to_scope);
            ats_flag = 0;
        }
        else {
            n = domain.#add_to_scope;
            for (i=0 : (WORDSIZE*i)<n : i++)
                if (ad-->i)
                    DoScopeActionAndRecurse(ad-->i, 0, context);
        }
    }
];

§53. DoScopeAction. Level 4. This is where we take whatever action is to be performed as the "visit" to each scoped object, and it's the bottom at last of the scope mechanism.

[ DoScopeAction item;

    #Ifdef DEBUG;
    if (parser_trace >= 6)
        print "[DSA on ", (the) item, " with reason = ", scope_reason,
            " p1 = ", parser_one, " p2 = ", parser_two, "]^";
    #Endif; DEBUG

    @push parser_one; @push scope_reason;

    switch(scope_reason) {
        TESTSCOPE_REASON: if (item == parser_one) parser_two = 1;
        LOOPOVERSCOPE_REASON: if (parser_one ofclass Routine) parser_one(item);
        PARSING_REASON, TALKING_REASON: MatchTextAgainstObject(item);
    }

    @pull scope_reason; @pull parser_one;
];

§54. Parsing Object Names. We now reach the final major block of code in the parser: the part which tries to match a given object's name(s) against the text at word position match_from in the player's command, and calls MakeMatch if it succeeds. There are basically four possibilities: ME, a pronoun such as IT, a name which doesn't begin misleadingly with a number, and a name which does. In the latter two cases, we pass the job down to TryGivenObject.

[ MatchTextAgainstObject item i;
    if (token_filter ~= 0 && ConsultNounFilterToken(item) == 0) return;
    if (match_from <= num_words) { If there's any text to match, that is
        wn = match_from;
        i = NounWord();
        if ((i == 1) && (player == item)) MakeMatch(item, 1); "me"
        if ((i >= 2) && (i < 128) && (LanguagePronouns-->i == item)) MakeMatch(item, 1);
    }

    Construing the current word as the start of a noun, can it refer to the
    object?

    wn = match_from;
    if (TryGivenObject(item) > 0)
        if (indef_nspec_at > 0 && match_from ~= indef_nspec_at) {
            This case arises if the player has typed a number in
            which is hypothetically an indefinite descriptor:
            e.g. "take two clubs".  We have just checked the object
            against the word "clubs", in the hope of eventually finding
            two such objects.  But we also backtrack and check it
            against the words "two clubs", in case it turns out to
            be the 2 of Clubs from a pack of cards, say.  If it does
            match against "two clubs", we tear up our original
            assumption about the meaning of "two" and lapse back into
            definite mode.

            wn = indef_nspec_at;
            if (TryGivenObject(item) > 0) {
                match_from = indef_nspec_at;
                ResetDescriptors();
            }
            wn = match_from;
        }
];

§55. TryGivenObject. TryGivenObject tries to match as many words as possible in what has been typed to the given object, obj. If it manages any words matched at all, it calls MakeMatch to say so, then returns the number of words (or 1 if it was a match because of inadequate input).

[ TryGivenObject obj nomatch threshold k w j;
    #Ifdef DEBUG;
    if (parser_trace >= 5) print "    Trying ", (the) obj, " at word ", wn, "^";
    #Endif; DEBUG

    if (nomatch && obj == 0) return 0;

    current_noun_is_plural = false;

 If input has run out then always match, with only quality 0 (this saves
 time).

    if (wn > num_words) {
        if (nomatch) return 0;
        if (indef_mode ~= 0) current_noun_is_plural = false;
        MakeMatch(obj,0);
        #Ifdef DEBUG;
        if (parser_trace >= 5) print "    Matched (0)^";
        #Endif; DEBUG
        return 1;
    }

 Ask the object to parse itself if necessary, sitting up and taking notice
 if it says the plural was used:

    if (obj.parse_name~=0) {
        parser_action = NULL; j=wn;
        k = RunRoutines(obj,parse_name);
        if (k > 0) {
            wn=j+k;

          .MMbyPN;

            if (parser_action == ##PluralFound) current_noun_is_plural = true;

            if (current_noun_is_plural) {
                if (~~allow_plurals) k = 0;
                else {
                    if (indef_mode == 0) {
                        indef_mode = 1; indef_type = 0; indef_wanted = 0;
                    }
                    indef_type = indef_type | PLURAL_BIT;
                    if (indef_wanted == 0) indef_wanted = INDEF_ALL_WANTED;
                }
            }

            #Ifdef DEBUG;
            if (parser_trace >= 5) print "    Matched (", k, ")^";
            #Endif; DEBUG
            if (nomatch == false) MakeMatch(obj,k);
            return k;
        }
        if (k == 0) jump NoWordsMatch;
    }

    The default algorithm is simply to count up how many words pass the
    Refers test:

    parser_action = NULL;

    w = NounWord();

    if (w == 1 && player == obj) { k=1; jump MMbyPN; }

    if (w >= 2 && w < 128 && (LanguagePronouns-->w == obj)) { k = 1; jump MMbyPN; }

    if (Refers(obj, wn-1) == 0) {
        .NoWordsMatch;
        if (indef_mode ~= 0) { k = 0; parser_action = NULL; jump MMbyPN; }
        rfalse;
    }

    threshold = 1;
    current_noun_is_plural = WorkMarkedAsUntruncatedPlural(w);
    w = NextWord();
    while (Refers(obj, wn-1)) {
        threshold++;
        if (w)
           current_noun_is_plural = current_noun_is_plural | (WorkMarkedAsUntruncatedPlural(w));
        w = NextWord();
    }

    k = threshold;
    jump MMbyPN;
];

§56. Refers. Refers works out whether the word at number wnum can refer to the object obj, returning true or false. The standard method is to see if the word is listed under the name property for the object, but this is more complex in languages other than English.

[ Refers obj wnum   wd k l m;
    if (obj == 0) rfalse;

    k = LanguageRefers(obj,wnum); if (k >= 0) return k;

    k = wn; wn = wnum; wd = NextWordStopped(); wn = k;

    if (indirect_parser_inflection) {
        k = parser_inflection(obj, wd);
        if (k >= 0) return k;
        m = -k;
    }
    else
        m = parser_inflection;
    k = obj.&m; l = (obj.#m)/WORDSIZE-1;
    for (m=0 : m<=l : m++)
        if (wd == k-->m) rtrue;
    rfalse;
];

[ WordInProperty wd obj prop k l m;
    k = obj.&prop; l = (obj.#prop)/WORDSIZE;
    for (m=0 : m<l : m++) {
        if (wd == k-->m) { rtrue; }
    }
    rfalse;
];

§57. NounWord. NounWord (which takes no arguments) returns:

[ NounWord i j s;
    i = NextWord();
    if (i == 0) rfalse;
    if (i == ME1__WD or ME2__WD or ME3__WD) return 1;
    s = LanguagePronouns-->0;
    for (j=1 : j<=s : j=j+3)
        if (i == LanguagePronouns-->j)
            return j+2;
    if ((i->#dict_par1) & DICTPAR1_NOUN == 0) rfalse;
    return i;
];

§58. TryNumber. TryNumber takes word number wordnum and tries to parse it as an (unsigned) decimal number or the name of a small number, returning

(The danger of allowing 5 digits is that Z-machine integers are only 16 bits long, and anyway this routine isn't meant to be perfect: it only really needs to be good enough to handle numeric descriptors such as those in TAKE 31 COINS or DROP FOUR DAGGERS. In particular, it is not the way I7 "[number]" tokens are parsed.)

[ TryNumber wordnum   i j c num len mul tot d digit;
    i = wn; wn = wordnum; j = NextWord(); wn = i;
    j = NumberWord(j); Test for verbal forms ONE to THIRTY
    if (j >= 1) return j;

    #Iftrue CHARSIZE == 1;
    i = wordnum*4+1; j = parse->i; num = j+buffer; len = parse->(i-1);
    #Ifnot;
    num = WordAddress(wordnum); len = WordLength(wordnum);
    #Endif;

    if (len >= 4) mul=1000;
    if (len == 3) mul=100;
    if (len == 2) mul=10;
    if (len == 1) mul=1;

    tot = 0; c = 0; len = len-1;

    for (c=0 : c<=len : c++) {
        #Iftrue CHARSIZE == 1;
        digit=num->c;
        #Ifnot;
        digit=num-->c;
        #Endif;
        if (digit == '0') { d = 0; jump digok; }
        if (digit == '1') { d = 1; jump digok; }
        if (digit == '2') { d = 2; jump digok; }
        if (digit == '3') { d = 3; jump digok; }
        if (digit == '4') { d = 4; jump digok; }
        if (digit == '5') { d = 5; jump digok; }
        if (digit == '6') { d = 6; jump digok; }
        if (digit == '7') { d = 7; jump digok; }
        if (digit == '8') { d = 8; jump digok; }
        if (digit == '9') { d = 9; jump digok; }
        return -1000;
     .digok;
        tot = tot+mul*d; mul = mul/10;
    }
    if (len > 3) tot=10000;
    return tot;
];

§59. Gender. GetGender returns 0 if the given animate object is female, and 1 if male, and is abstracted as a routine in case something more elaborate is ever needed.

For GNAs — gender/noun/animation combinations — see the {\it Inform Designer's Manual}, 4th edition.

[ GetGender person;
    if (person hasnt female) rtrue;
    rfalse;
];

+replacing(from BasicInformKit) [ GetGNAOfObject obj case gender;
    if (obj hasnt animate) case = 6;
    if (obj has male) gender = male;
    if (obj has female) gender = female;
    if (obj has neuter) gender = neuter;
    if (gender == 0) {
        if (case == 0) gender = LanguageAnimateGender;
        else gender = LanguageInanimateGender;
    }
    if (gender == female)   case = case + 1;
    if (gender == neuter)   case = case + 2;
    if (obj has pluralname) case = case + 3;
    return case;
];

§60. Noticing Plurals.

[ DetectPluralWord at n i w swn outcome;
    swn = wn; wn = at;
    for (i=0:i<n:i++) {
        w = NextWordStopped();
        if (w == 0 or THEN1__WD or COMMA_WORD or -1) break;
        if (WorkMarkedAsUntruncatedPlural(w)) {
            parser_action = ##PluralFound;
            outcome = true;
        }
    }
    wn = swn;
    return outcome;
];

§61. Pronoun Handling.

[ SetPronoun dword value x;
    for (x=1 : x<=LanguagePronouns-->0 : x=x+3)
        if (LanguagePronouns-->x == dword) {
            LanguagePronouns-->(x+2) = value; return;
        }
    RunTimeError(14);
];

[ PronounValue dword x;
    for (x=1 : x<=LanguagePronouns-->0 : x=x+3)
        if (LanguagePronouns-->x == dword)
            return LanguagePronouns-->(x+2);
    return 0;
];

[ ResetVagueWords obj; PronounNotice(obj); ];

[ PronounNotice obj x bm g;
    if (obj == player) return;

    g = (GetGNAOfObject(obj));

    bm = PowersOfTwo_TB-->g;
    for (x=1 : x<=LanguagePronouns-->0 : x=x+3)
        if (bm & (LanguagePronouns-->(x+1)) ~= 0)
            LanguagePronouns-->(x+2) = obj;

    if (((g % 6) < 3) && (obj has ambigpluralname)) {
        g = g + 3;
        bm = PowersOfTwo_TB-->g;
        for (x=1 : x<=LanguagePronouns-->0 : x=x+3)
            if (bm & (LanguagePronouns-->(x+1)) ~= 0)
                LanguagePronouns-->(x+2) = obj;
    }
];

[ PronounNoticeHeldObjects x;
    if (CommandParserKit`MANUAL_PRONOUNS_CFGF == 0)
        objectloop(x in player)
            PronounNotice(x);
    rfalse;
];

§62. Yes/No Questions.

[ YesOrNo i j;
    for (::) {
        if (location == nothing || parent(player) == nothing) KeyboardPrimitive(buffer2, parse2);
        else KeyboardPrimitive(buffer2, parse2, DrawStatusLine);
        #Iftrue CHARSIZE == 1;
        j = parse2->1;
        #Ifnot;
        j = parse2-->0;
        #Endif;
        if (j) { at least one word entered
            i = parse2-->1;
            if (i == YES1__WD or YES2__WD or YES3__WD) rtrue;
            if (i == NO1__WD or NO2__WD or NO3__WD) rfalse;
        }
        YES_OR_NO_QUESTION_INTERNAL_RM('A'); print "> ";
    }
];

[ YES_OR_NO_QUESTION_INTERNAL_R; ];

§63. Number Words. Not much of a parsing routine: we look through an array of pairs of number words (single words) and their numeric equivalents.

[ NumberWord o i n;
    n = LanguageNumbers-->0;
    for (i=1 : i<=n : i=i+2)
        if (o == LanguageNumbers-->i) return LanguageNumbers-->(i+1);
    return 0;
];

§64. Tracing. Uncomment this line and rebuild the kit to enable tracing of what the algorithm below is doing. (This constant should not be used anywhere except in this file, where #Ifdef on it will have the expected effect: elsewhere, it might not.)

Constant LKTRACE_CHOOSE_OBJECTS;

§65. Choose Objects. This material, the final body of code in the parser, is an I7 addition. The I6 parser leaves it to the user to provide a ChooseObjects routine to decide between possibilities when the situation is ambiguous. For I7 use, we provide a ChooseObjects which essentially runs the "does the player mean" rulebook to decide, though this is not obvious from the code below because it is hidden in the CheckDPMR routine — which is defined in the Standard Rules, not here.

the highest value returned by CheckDPMR (see the Standard Rules)
Constant HIGHEST_DPMR_SCORE = 4;

Array alt_match_list --> (MATCH_LIST_WORDS+1);

swap alt_match_list with match_list/number_matched
[ COBJ__SwapMatches i x;
    swap the counts
    x = number_matched;
    number_matched = alt_match_list-->0;
    alt_match_list-->0 = x;
    swap the values
    if (x < number_matched) x = number_matched;
    for (i=x: i>0: i--) {
        x = match_list-->(i-1);
        match_list-->(i-1) = alt_match_list-->i;
        alt_match_list-->i = x;
    }
];

[ ChooseObjects obj code  l i swn spcount;
    if (code<2) rfalse;

    if (cobj_flag == 1) {
        .CodeOne;
        if (parameters > 0) {
            #ifdef LKTRACE_CHOOSE_OBJECTS;
            print "[scoring ", (the) obj, " (second)]^";
            #endif;
            return ScoreDabCombo(parser_results-->INP1_PRES, obj);
        } else {
            #ifdef LKTRACE_CHOOSE_OBJECTS;
            print "[scoring ", (the) obj, " (first) in ",
                alt_match_list-->0, " combinations]^";
            #endif;
            l = 0;
            for (i=1: i<=alt_match_list-->0: i++) {
                spcount = ScoreDabCombo(obj, alt_match_list-->i);
                if (spcount == HIGHEST_DPMR_SCORE) {
                    #ifdef LKTRACE_CHOOSE_OBJECTS;
                    print "[scored ", spcount, " - best possible]^";
                    #endif;
                    return spcount;
                }
                if (spcount>l) l = spcount;
            }
            return l;
        }
    }
    if (cobj_flag == 2) {
        .CodeTwo;
        #ifdef LKTRACE_CHOOSE_OBJECTS;
        print "[scoring ", (the) obj, " (simple); parameters = ", parameters,
            " aw = ", advance_warning, "]^";
        #endif;
        @push action_to_be;
        if (parameters==0) {
            if (advance_warning > 0)
                l = ScoreDabCombo(obj, advance_warning);
            else
                l = ScoreDabCombo(obj, 0);
        } else {
            l = ScoreDabCombo(parser_results-->INP1_PRES, obj);
        }
        @pull action_to_be;
        return l;
    }

    #ifdef LKTRACE_CHOOSE_OBJECTS;
    print "[choosing a cobj strategy: ";
    #endif;
    swn = wn;
    spcount = pcount;
    while (line_ttype-->pcount == PREPOSITION_TT) pcount++;
    if (line_ttype-->pcount == ELEMENTARY_TT) {
        if (line_tdata-->pcount == TOPIC_TOKEN) {
            pcount = spcount;
            jump CodeTwo;
        }
        while (wn <= num_words) {
            l = NextWordStopped(); wn--;
            if (l == THEN1__WD) break;
            if ((l ~= -1 or 0) && (l->#dict_par1) & DICTPAR1_PREP) { wn++; continue; }
            if (l == ALL1__WD or ALL2__WD or ALL3__WD or ALL4__WD or ALL5__WD) { wn++; continue; }
            SafeSkipDescriptors();
            save the current match state
            @push match_length; @push token_filter; @push match_from;
            alt_match_list-->0 = number_matched;
            VM_CopyWords(number_matched, match_list, alt_match_list+WORDSIZE);
            now get all the matches for the second noun
            match_length = 0; number_matched = 0; match_from = wn;
            token_filter = 0;
            SearchScope(actor, actors_location, line_tdata-->pcount);
            #ifdef LKTRACE_CHOOSE_OBJECTS;
            print number_matched, " possible second nouns]^";
            #endif;
            wn = swn;
            cobj_flag = 1;
            restore match variables
            COBJ__SwapMatches();
            @pull match_from; @pull token_filter; @pull match_length;
            pcount = spcount;
            jump CodeOne;
        }
    }
    pcount = spcount;
    wn = swn;

    #ifdef LKTRACE_CHOOSE_OBJECTS;
    print "nothing interesting]^";
    #endif;
    cobj_flag = 2;
    jump CodeTwo;
];

[ ScoreDabCombo a b  result;
    @push action; @push act_requester; @push noun; @push second;
    action = action_to_be;
    act_requester = player;
    if (action_reversed) { noun = b; second = a; }
    else { noun = a; second = b; }
    result = CheckDPMR();
    @pull second; @pull noun; @pull act_requester; @pull action;
    #ifdef LKTRACE_CHOOSE_OBJECTS;
    print "[", (the) a, " / ", (the) b, " => ", result, "]^";
    #endif;
    return result;
];

[ CheckDPMR result sinp1 sinp2 rv;
    sinp1 = inp1; sinp2 = inp2; inp1 = noun; inp2 = second;
    rv = FollowRulebook(DOES_THE_PLAYER_MEAN_RB);
    inp1 = sinp1; inp2 = sinp2;
    if ((rv) && RulebookSucceeded()) {
        result = ResultOfRule();
        if (result == RBNO4_OUTCOME) return 4;
        if (result == RBNO3_OUTCOME) return 3;
        if (result == RBNO2_OUTCOME) return 2;
        if (result == RBNO1_OUTCOME) return 1;
        if (result == RBNO0_OUTCOME) return 0;
    }
    return 2;
];

§66. Default Topic. A default value for the I7 sort-of-kind "topic", which never matches.

+replacing(from BasicInformKit) [ DefaultTopic; return GPR_FAIL; ];

§67. Recognition-only-GPR. An I6 general parsing routine to look at words from the position marker wn in the player's command to see if they match the contents of the text txt, returning either GPR_PREPOSITION or GPR_FAIL according to whether a match could be made. This is used when the an object's name is set to include one of its properties, and the property in question is a text: "A flowerpot is a kind of thing. A flowerpot has a text called pattern. Understand the pattern property as describing a flowerpot." When the player types EXAMINE STRIPED FLOWERPOT, and there is a flowerpot in scope, the following routine is called to test whether its pattern property — a text — matches any words at the position STRIPED FLOWERPOT. Assuming a pot does indeed have the pattern "striped", the routine advances wn by 1 and returns GPR_PREPOSITION to indicate a match.

This kind of GPR is called a "recognition-only-GPR", because it only recognises an existing value: it doesn't parse a new one.

[ TEXT_TY_ROGPR txt p cp r;
    if (txt == 0) return GPR_FAIL;
    cp = txt-->0; p = TEXT_TY_Temporarily_Transmute(txt);
    r = TEXT_TY_ROGPRI(txt);
    TEXT_TY_Untransmute(txt, p, cp);
    return r;
];
[ TEXT_TY_ROGPRI txt
    pos len wa wl wpos wach bdm ch own;
    bdm = true; own = wn;
    len = BlkValueLBCapacity(txt);
    for (pos=0: pos<=len: pos++) {
        if (pos == len) ch = 0; else ch = BlkValueRead(txt, pos);
        if (ch == 32 or 9 or 10 or 0) {
            if (bdm) continue;
            bdm = true;
            if (wpos ~= wl) return GPR_FAIL;
            if (ch == 0) break;
        } else {
            if (bdm) {
                bdm = false;
                if (NextWordStopped() == -1) return GPR_FAIL;
                wa = WordAddress(wn-1);
                wl = WordLength(wn-1);
                wpos = 0;
            }
            #Iftrue CHARSIZE == 1;
            wach = wa->wpos;
            #Ifnot;
            wach = wa-->wpos;
            #Endif;
            if (wach ~= ch or TEXT_TY_RevCase(ch)) return GPR_FAIL;
            wpos++;
        }
    }
    if (wn == own) return GPR_FAIL; Progress must be made to avoid looping
    return GPR_PREPOSITION;
];

§68. RunRoutines. This function may not be very well-named, but the idea is to take a property of a given object and either to print it (and return true) if it's a string, and call it (and pass along its return value) if it's a routine. If the object does not provide the property, we act on the default value for the property if it has one, and otherwise do nothing (and return false).

The I6 pseudo-object thedark is used to give the impression that Darkness is a room in its own right, which is not really true. Note that it is not permitted to have properties other than the three named here: all other properties are redirected to the current location's object.

At one time this function correctly handled the case of a common property whose default value was itself a function which could be run; but this is now awkward to compile (since it needed the magic compiler-defimed constant INDIV_PROP_START, existing on Inform 6 for Glulx but not on other platforms), and in the Inform 7 world it was a facility never used.

[ RunRoutines obj prop;
    if (obj == thedark) obj = real_location;
    if (obj.&prop == 0) rfalse;
    return obj.prop();
];

§69. Setting the Player's Command. In effect, the text typed most recently by the player is a sort of text already, though it isn't in text format, and doesn't live on the heap.

[ SetPlayersCommand from_txt i len at p cp;
    cp = from_txt-->0; p = TEXT_TY_Temporarily_Transmute(from_txt);
    len = TEXT_TY_CharacterLength(from_txt);
    if (len > INPUT_BUFFER_LEN-2) len = INPUT_BUFFER_LEN-2;
    #Iftrue CHARSIZE == 1;
    buffer->1 = len; at = 2;
    for (i=0:i<len:i++) buffer->(i+at) = CharToCase(BlkValueRead(from_txt, i), 0);
    for (:at+i<INPUT_BUFFER_LEN:i++) buffer->(at+i) = ' ';
    #ifnot;
    buffer-->0 = len; at = 1;
    for (i=0:i<len:i++) buffer-->(i+at) = CharToCase(BlkValueRead(from_txt, i), 0);
    for (:at+i<INPUT_BUFFER_LEN:i++) buffer-->(at+i) = ' ';
    #endif;
    VM_Tokenise(buffer, parse);
    players_command = 100 + WordCount(); The snippet variable "player's command"
    TEXT_TY_Untransmute(from_txt, p, cp);
];

§70. Multiple Object List. The parser uses one data structure which is really a list: but which can't be represented as such because the heap might not exist. This is the multiple object list, which is used to handle commands like TAKE ALL by firing off a sequence of actions with one of the objects taken from entries in turn of the list. The following converts it to a list structure.

[ LIST_OF_TY_Mol list len i;
    if ((list==0) || (BlkValueWeakKind(list) ~= LIST_OF_TY)) return 0;
    len = multiple_object-->0;
    LIST_OF_TY_SetLength(list, len);
    for (i=1: i<=len: i++)
        LIST_OF_TY_PutItem(list, i, multiple_object-->i);
    return list;
];

[ LIST_OF_TY_Set_Mol list len i;
    if ((list==0) || (BlkValueWeakKind(list) ~= LIST_OF_TY)) return 0;
    len = BlkValueRead(list, LIST_LENGTH_F);
    if (len > 63) len = 63;
    multiple_object-->0 = len;
    for (i=1: i<=len: i++)
        multiple_object-->i = BlkValueRead(list, LIST_ITEM_BASE+i-1);
];