Original Parser

version 1 by Ron Newcomb

  • Home page
  • Beginning
  • Previous



  • Book - Preserving I6 Function Names Where Necessary

    [Section - (in place of Section SR5/6/4 - Understanding - Scope and pronouns in Standard Rules by Graham Nelson)]

    [ Some I6 functions are called from outside Parser.i6t so I create the following stubs to bounce control back up into their new I7 incarnations. I then comment out those lines when I no longer need them. ]

    Include (-
    ![ ScopeWithin domain nosearch context; return ((+ ScopeWithin +)-->1)(domain, nosearch, context); ]; ! I7 SR, but overridden
    [ PlaceInScope O opts; return ((+ PlaceInScope +)-->1)(O, opts); ]; ! in testcommandnoun (and I7 SR but can't override)
    ![ AddToScope obj; return ((+ AddToScope +)-->1)(obj); ]; ! I6 hook, but not normally used
    ![ DoScopeAction item; return ((+ DoScopeAction +)-->1)(item); ];
    [ TestScope obj actr; return ((+ TestScope +)-->1)(obj, actr); ]; ! TestVisibility, TestTouchability
    [ LoopOverScope routine actr; return ((+ LoopOverScope +)-->1)(routine, actr); ]; ! SCOPE testing command
    ![ DoScopeActionAndRecurse d1 d2 cntxt; return ((+ DoScopeActionAndRecurse +)-->1)(d1, d2, cntxt); ];
    ![ ConsultNounFilterToken obj; return ((+ ConsultNounFilterToken +)-->1)(obj); ];
    ![ SearchScope d1 d2 cntxt; return ((+ SearchScope +)-->1)(d1, d2, cntxt); ];
    ![ MakeMatch obj quality; return ((+ MakeMatch +)-->1)(obj, quality); ];
    ![ MatchTextAgainstObject item; return ((+ MatchTextAgainstObject +)-->1)(item); ];
    ![ MultiSub o; return ((+ MultiSub +)-->1)(o); ];
    ![ MultiAdd o dups; return ((+ MultiAdd +)-->1)(o, dups); ];
    ![ MultiFilter o; return ((+ MultiFilter +)-->1)(o); ];
    ![ Refers obj wnum; return ((+ Refers +)-->1)(wnum, obj); ]; ! order of params swapped
    [ WordInProperty wd obj prop; return ((+ WordInProperty +)-->1)(wd, obj, prop); ];
    ![ CreatureTest obj; return ((+ CreatureTest +)-->1)(obj); ];
    ![ ReviseMulti second_p; return ((+ ReviseMulti +)-->1)(second_p); ];
    ![ BestGuess; return ((+ BestGuess +)-->1)(); ];
    ![ SingleBestGuess; return ((+ SingleBestGuess +)-->1)(); ];
    ![ PrintInferredCommand from; return ((+ PrintInferredCommand +)-->1)(from); ];
    ! 21 functions down, 50 to go
    ![ ScoreDabCombo a b; return ((+ ScoreDabCombo +)-->1)(a,b); ];
    [ YesOrNo; return ((+ YesOrNo +)-->1)(); ]; ! quitting the game rules
    ![ NumberWord o; return ((+ NumberWord +)-->1)(o); ];
    ![ ResetVagueWords x; ]; ! not used?
    ![ SetPronoun p o; return ((+ SetPronoun +)-->1)(p, o); ];
    ![ PronounValue p; return ((+ PronounValue +)-->1)(p); ];
    [ PrintCommand from; return ((+ PrintCommand +)-->1)(from); ]; ! language.i6t
    [ WordCount; return ((+ WordCount +)-->1)(); ];
    [ WordLength w; return ((+ WordLength +)-->1)(w); ];
    ![ Keyboard b t ; return ((+ Keyboard +)-->1)(b, t); ];
    ![ ResetDescriptors; return ((+ ResetDescriptors +)-->1)(); ];
    ![ ArticleDescriptors ] ! unused?
    ![ NounWord i; return ((+ NounWord +)-->1)(i); ];
    ![ TryGivenObject obj nomatch; return ((+ TryGivenObject +)-->1)(obj, nomatch); ];
    ![ GetGender person; ]; ! unused?
    [ DetectPluralWord at len; return ((+ DetectPluralWord +)-->1)(at, len); ]; ! auto-generated parse name functions
    ![ PronounNoticeHeldObjects x; ]; ! deprecated per 16.18 in Writing With Inform
    ![ PronounNotice obj; return ((+ PronounNotice +)-->1)(obj); ]; ! SR but re-exposed directly here
    ![ COBJ__Copy len from dest; return ((+ PronounNotice +)-->1)(len, from, dest); ];
    ![ COBJ__SwapMatches; return ((+ COBJ__SwapMatches +)-->1)(); ];
    ![ ChooseObjects obj strat; return ((+ ChooseObjects +)-->1)(obj, strat); ];
    ![ ScoreMatchL context; return ((+ ScoreMatchL +)-->1)(context); ];
    [ NextWord; return ((+ NextWord +)-->1)(); ];
    [ NextWordStopped; return ((+ NextWordStopped +)-->1)(); ];
    ! 46 functions down; 25 to go
    [ WordAddress wordnum; return ((+ WordAddress +)-->1)(wordnum); ];
    [ PrintSnippet snip; return ((+ PrintSnippet +)-->1)(snip); ]; ! LCORE.i6t, indexed text
    ![ SpliceSnippet snip t; return ((+ SpliceSnippet +)-->1)(snip, t); ]; ! I7 SR, overridden
    ![ SnippetMatches s t; return ((+ SnippetMatches +)-->1)(s,t); ];
    [ WordFrom w p; return ((+ WordFrom +)-->1)(w,p); ];
    ![ SafeSkipDescriptors; return ((+ SafeSkipDescriptors +)-->1)(); ];
    ![ Descriptors t; return ((+ Descriptors +)-->1)(t); ];
    ![ NounDomain d1 d2 c; return ((+ NounDomain +)-->1)(d1,d2,c); ];
    ![ Adjudicate context;]
    ![ CantSee; return ((+ CantSee +)-->1)(); ];
    ![ I7_ExtendedTryNumber;] ! obsolete. reported as bug
    ![ DefaultTopic ;] ! this is too simple to translate, and must be an I6 name anyway.
    ![ KeyboardPrimitive; ] ! also too simple to translate, called from outside Parser.i6t, uses many unique features
    [ ParseTokenStopped x y; return ((+ ParseTokenStopped +)-->1)(y,x); ]; ! order of params swapped ! only callled from Number.i6t
    [ UnpackGrammarLine line_address; return ((+ UnpackGrammarLine +)-->1)(line_address); ]; ! used in SHOWVERBsub
    ![ DictionaryWordToVerbNum ! not translated: called from outside Parser.i6t
    ![ ParseToken ty data token_n token; return ((+ ParseToken +)-->1)(data, ty, token_n, token); ]; !order of params swapped a bit
    ![ PrepositionChain wd index; return ((+ PrepositionChain +)-->1)(wd, index); ];
    [ Identical o1 o2; return ((+ Identical +)-->1)(o1,o2); ]; ! ListEqual()
    [ TryNumber wordnum; return ((+ TryNumber +)-->1)(wordnum); ]; ! [time] tokens
    [ GetGNAOfObject obj; return ((+ GetGNAOfObject +)-->1)(obj); ]; ! PrefaceByArticle()
    ![ AnalyseToken
    ![ ParseToken__ ttype tdata Nth token; return ((+ ParseToken__ Part A +)-->1)(tdata, ttype, Nth, token); ]; ! order of 1st params swapped
    [ Parser__parse; return ((+ Parser__parse +)-->1)(); ]; ! the parse command rule, in the turn sequence rulebook
    -).

    [![ MoveWord w1 source w2; return ((+ MoveWord +)-->1)(w1, source, w2); ]; ! unused]


    Book - Array Phrases

    Chapter - Arrays in Inform 7

    [ There's lots of different kinds of arrays in I6, and in the parser. Some are word-based, some byte-based. Some hold objects, some individual letters, some a unique struct. Some keep the size of the array in element 0, some in globals, some in named constants. It's less of a headache if I create lots of finely-tuned array definitions and overload the same functions heavily, so the client code cannot tell what kind of array is what. ]

    [ To overload the same phrases requires the array to "carry with itself" the type of things it's pointing to -- numbers, objects, structures, etc. -- as well as if it's a 0-based or 1-based index. For structures I use a unique phrase for each field. ]

    Some 0-based indexes are the 0-based-array.
    Some 1-based indexes are the 1-based-array.
    9<7> specifies a struct with parts desired [column] and maximum [column].

    [The chosen row is a number that varies. The chosen row variable translates into I6 as "ct_1".]
    The chosen is a number that varies. The chosen variable translates into I6 as "ct_1".

    To decide what 1-based index based rulebook producing ZSCII letters is the chosen array: (-ct_0-).
    To decide what object is the chosen element: (- (ct_0-->ct_1) -). [commonest case]
    To decide which K is the (name of kind of value K) element: (- ct_0-->ct_1 -). [allows typecasting]


    To decide which 1-based index based rulebook producing ZSCII letters is the player's input buffer: (-buffer-).
    To decide which 1-based index based rulebook producing ZSCII letters is the secondary input buffer: (-buffer2-).
    To decide which 1-based index based rulebook producing ZSCII letters is the AGAIN input buffer: (-buffer3-).
    To decide what number is the maximum buffer size: (-INPUT_BUFFER_LEN-).

    To decide which 1-based index based rulebook producing structs is the player's parsed command: (-parse-).
    To decide which 1-based index based rulebook producing structs is the second parsed command: (-parse2-).
    To decide which understood word is the word element: (-(+ the chosen word element of the chosen array +)-).
    To decide which number is the length element: (-(+ the chosen length element of the chosen array +)-).
    To decide which number is the position element: (-(+ the chosen position element of the chosen array +)-).

    To decide which 1-based index based rulebook producing objects is the multiple-object list: (-multiple_object-).

    To decide which 1-based index based rulebook producing structs is the language's number list: (-LanguageNumbers-).
    To decide which understood word is the spelled-out number element: (- ct_0-->(ct_1) -).
    To decide which number is the number as digits element: (- ct_0-->(ct_1+1) -).
    To decide which struct is as a spelled-out number: (-(+ 1<2> +)-).

    To decide which 1-based index based rulebook producing structs is the language's pronoun list: (-LanguagePronouns-).
    To decide which understood word is the pronoun element: (- ct_0-->ct_1 -).
    To decide which word usage is the gender-animation element: (- ct_0-->(ct_1+1) -).
    To decide which object is the antecedent element: (- ct_0-->(ct_1+2) -).
    To change the (Nth - number) antecedent element to (data - object): (- ct_0-->({Nth}+2) = {data}; -).
    To decide which struct is as one of the pronouns: (-(+ 1<3> +)-).
    To decide which struct is as one of the antecedents: (- ct_0-->(ct_1+2) -).

    To decide which 1-based index based rulebook producing structs is the language's descriptor list:
    (- LanguageDescriptors -).
    To decide which understood word is the descriptor element: (- ct_0-->ct_1 -).
    [To decide which word usage is the gender-animation element: (- ct_0-->(ct_1+1) -).] [same as above]
    To decide which descriptor property is the category element: (- ct_0-->(ct_1+2) -).
    To decide which understood word is the possessive descriptor element: (- ct_0-->(ct_1+3) -).
    To decide which struct is as one of the descriptors: (-(+ 1<4> +)-).
    To decide which understood word is 'MY\THIS\THESE': (- 0 -).
    To decide which understood word is 'THAT\THOSE': (- 1 -).


    Chapter - Get and Set a particular element for arrays of word values

        [ Get a[i] ]
    To decide what K is the (n - a number) element of (arr - a 1-based index based rulebook producing a word value of kind K):
    (- {arr}-->{n} -).
    To decide what K is the (n - a number) element of (arr - a 0-based index based rulebook producing a word value of kind K):
    (- {arr}-->{n} -).

    [ Assign a[i] = x; ]
    To change (n - a number) element of (arr - a 1-based index based rulebook producing a word value of kind K) to (obj - K):
    (- {arr}-->{n} = {obj}; -).
    To change (n - a number) element of (arr - a 0-based index based rulebook producing a word value of kind K) to (obj - K):
    (- {arr}-->{n} = {obj}; -).

    [ Assign a[i] = x; where i is assumed to be the chosen row ]
    To change (arr - a 0-based index based rulebook producing a word value of kind K) element to (obj - K):
    (- {arr}-->ct_1 = {obj}; -).

        [ Get a[i] where i is assumed: it's the chosen row. ]
    To decide what K is the (arr - a 1-based index based rulebook producing a word value of kind K) element:
    (- {arr}-->ct_1 -).
    To decide what K is the (arr - a 0-based index based rulebook producing a word value of kind K) element:
    (- {arr}-->ct_1 -).

    Chapter - Get and Set a particular element for arrays of letters

    [ Assign a[i] = x; ]
    To change (Nth - a number) letter of (arr - a 1-based index based rulebook producing ZSCII letters) to (char - ZSCII letter):
    (- {arr}->({Nth}+LETTER_ARRAY_BASE) = {char}; -).

    [ Get a[i] ]
    To decide what ZSCII letter is the (Nth - a number) letter of (arr - a 1-based index based rulebook producing ZSCII letters):
    (- {arr}->({Nth}+LETTER_ARRAY_BASE) -).

    [ Get a[i] where i is assumed: it's the chosen row. ]
    To decide what ZSCII letter is the (arr - a 1-based index based rulebook producing ZSCII letters) letter:
    (- {arr}->(ct_1+LETTER_ARRAY_BASE) -).

    [ Say a run of letters. Used by PrintSnippet and some TRACE output. ]
    To say (buf - a 1-based index based rulebook producing ZSCII letters) between/from (min - a number) and/to (max - a number):
    (- {-require-ctvs} for(ct_1 = {min} + LETTER_ARRAY_BASE; ct_1 <= {max} + LETTER_ARRAY_BASE; ++ct_1) print (char) {buf}->ct_1; -).


    Section 1 - Get and Set the array size for 1-based arrays only

    [ Get size of a ]
    To decide what number is the number of elements in/of (arr - a 1-based index based rulebook producing word values): [ The number of words & letters variations are re-defined in the (for Z-machine only) section below. ]
    (- {arr}-->0 -).

    [ Set size of a ]
    To change (arr - a 1-based index based rulebook producing word values) to have (n - a number) elements:
    (- {arr}-->0 = {n}; -).

    Section 2 - Searches, Loops, and Ifs

    Include (-
    Global cacheval1; ! it hurts performance to put ct_0-->0 into the While condition test, so,
    Global cacheval2; ! these variables just exist so I needn't do that.
    Global cacheval3;
    Global cacheval4;
    Global cacheval5;
    Global cacheval6;
    -).

    Any problems is a command parser error that varies.
    The any problems variable translates into I6 as "cacheval4". [too many locals error]
    Where that word was found is a number that varies.
    Where that word was found variable translates into I6 as "cacheval4". [if... then used it]


        [ Repeat through, setting ct_0 and ct_1 for the various chosen/element phrases. ]
    To repeat through (arr - a 0-based index based rulebook producing a value of kind K) of size (size - a number) begin -- end:
    (- {-require-ctvs}ct_0 = {arr};
        for (ct_1=0 : ct_1<{size} : ++ct_1) -).

    To repeat through (arr - a 0-based index based rulebook producing a value of kind K) of size (size - a number) starting at (min - a number) begin -- end:
    (- {-require-ctvs}ct_0 = {arr};
        for (ct_1={min} : ct_1<{size} : ++ct_1) -).

    To repeat through (arr - a 1-based index based rulebook producing a value of kind K) begin -- end:
    (- {-require-ctvs}ct_0 = {arr}; cacheval1 = ct_0-->0;
        for (ct_1=1 : ct_1 <= cacheval1 : ++ct_1) -).

    To repeat through (arr - a 1-based index based rulebook producing a value of kind K) starting at (min - a number) begin -- end:
    (- {-require-ctvs}ct_0 = {arr}; cacheval1 = ct_0-->0;
        for (ct_1={min} : ct_1 <= cacheval1 : ++ct_1) -).

    To repeat through all (struct size - a number) columns of (arr - a 1-based index based rulebook producing structs) begin -- end:
    (- {-require-ctvs}ct_0 = {arr}; cacheval1 = ct_0-->0;
        for (ct_1=1 : ct_1 <= cacheval1 : ct_1 = ct_1 + {struct size} ) -).

        [ If x is listed in a, phrase. ]
    To if (obj - K) is listed in (arr - a 0-based index based rulebook producing a value of kind K) of size (size - a number), (ph - a phrase):
    (- {-require-ctvs}
        ct_0 = {arr};
        cacheval1 = {size};
        cacheval2 = {obj};
    #ifdef TARGET_ZCODE;
        @scan_table cacheval2 ct_0 cacheval1 $82 -> ct_1 ?~ScanTable{-counter:scantable};
    .ScanTable{-advance-counter:scantable};
        if (ct_1) ! returns the address if found or 0 if not found
        { ! convert the address to the element #
            ct_1 = (ct_1 - ct_0) / WORDSIZE;
            if (true) {ph}; }
        }
    #ifnot;
        @linearsearch cacheval2 WORDSIZE ct_0 WORDSIZE cacheval1 0 $$0100 ct_1;
        if (ct_1 ~= -1)
            {ph}; }
    #endif; !-).

        [ if x is listed in a, block of phrases. ]
    To if (obj - K) is listed in (arr - a 0-based index based rulebook producing a value of kind K) of size (size - a number) begin -- end:
    (- {-require-ctvs}
        ct_0 = {arr};
        cacheval1 = {size};
        cacheval2 = {obj};
    #ifdef TARGET_ZCODE;
        @scan_table cacheval2 ct_0 cacheval1 $82 -> ct_1 ?~ScanTable{-counter:scantable};
    .ScanTable{-advance-counter:scantable};
        if (ct_1) ! if something found, convert the pointer to an index
        { ct_1 = (ct_1 - ct_0) / WORDSIZE;
    #ifnot;
        @linearsearch cacheval2 WORDSIZE ct_0 WORDSIZE cacheval1 0 $$0100 ct_1;
        if (ct_1 ~= -1)
        {
    #endif; !-).

    To if (obj - K) is listed in (arr - a 1-based index based rulebook producing a value of kind K), (ph - phrase):
    (- {-require-ctvs}
        ct_0 = {arr};
        cacheval1 = {arr}-->0;
        cacheval2 = {obj};
    #ifdef TARGET_ZCODE;
        @scan_table cacheval2 ct_0 cacheval1 $82 -> ct_1 ?~ScanTable{-counter:scantable};
    .ScanTable{-advance-counter:scantable};
        if (ct_1) ! if something found, convert the pointer to an index
        { ct_1 = (ct_1 - ct_0) / WORDSIZE;
            if (true) {ph}; }
        }
    #ifnot;
        @linearsearch cacheval2 WORDSIZE ct_0 WORDSIZE cacheval1 0 $$0100 ct_1;
        if (ct_1 ~= -1)
            {ph}; }
    #endif; !-).

    To if (obj - a word value) is listed (field - a struct) in/from (arr - a 1-based index based rulebook producing structs) begin -- end:
    (- {-require-ctvs}ct_0 = {arr};
        cacheval1 = ct_0-->0;
        cacheval2 = ({field} & 7);
        cacheval3 = ct_0 + ({field} / 8) - 1;
        for (ct_1=1 : ct_1 <= cacheval1 : ct_1 = ct_1 + cacheval2)
    if (cacheval3-->ct_1 == {obj}) { cacheval1 = 0; !-).

    To if (obj - K) is listed in (arr - a 1-based index based rulebook producing a value of kind K) begin -- end:
    (- {-require-ctvs}
        ct_0 = {arr};
        cacheval1 = {arr}-->0;
        cacheval2 = {obj};
    #ifdef TARGET_ZCODE;
        @scan_table cacheval2 ct_0 cacheval1 $82 -> ct_1 ?~ScanTable{-counter:scantable};
    .ScanTable{-advance-counter:scantable};
        if (ct_1) ! if something found, convert the pointer to an index
        { ct_1 = (ct_1 - ct_0) / WORDSIZE;
    #ifnot;
        @linearsearch cacheval2 WORDSIZE ct_0 WORDSIZE cacheval1 0 $$0100 ct_1;
        if (ct_1 ~= -1)
        {
    #endif; !-).

        [ Misc. ]
    To if (obj - K) is listed in (arr - a 1-based index based rulebook producing a value of kind K) between (min - a number) and (max - a number) begin -- end:
    (- {-require-ctvs}
        ct_0 = {arr} + {min};
        cacheval1 = {max} - {min};
        cacheval2 = {obj};
    #ifdef TARGET_ZCODE;
        @scan_table cacheval2 ct_0 cacheval1 $82 -> ct_1 ?~ScanTable{-counter:scantable};
    .ScanTable{-advance-counter:scantable};
        if (ct_1) ! if something found, convert the pointer to an index
        { ct_1 = (ct_1 - ct_0) / WORDSIZE;
    #ifnot;
        @linearsearch cacheval2 WORDSIZE ct_0 WORDSIZE cacheval1 0 $$0100 ct_1;
        if (ct_1 ~= -1)
        {
    #endif; !-).

    To fill (arr - a 1-based index based rulebook producing ZSCII letters) with (char - a ZSCII letter) starting at (min - a number) for (n - a number) elements:
    (- {-require-ctvs} cacheval1 = {arr}+{min}+LETTER_ARRAY_BASE;
        for (ct_1=0 : ct_1<{n} : ++ct_1)
            cacheval1->ct_1 = {char}; -).

    To shift (arr - a 1-based index based rulebook producing ZSCII letters) left by (size - a number) starting at (source - a number):
    (- {-require-ctvs} ct_0 = {arr};
        cacheval1 = {arr} + LETTER_ARRAY_BASE - {size};
        cacheval2 = {arr} + LETTER_ARRAY_BASE;
        for (ct_1={source} : ct_1<INPUT_BUFFER_LEN : ++ct_1)
            cacheval1->ct_1 = cacheval2->ct_1;
        (+ the number of letters in the chosen array +) = (+ the number of letters in the chosen array +) - {size}; -).

    To shift (arr - a 1-based index based rulebook producing a word value) left between/from (min - a number) and/to (max - a number) using (i - a nonexisting number variable):
    (- for ({i} = {min} : {i} <= {max} : ++{i} ) {arr}-->({i}) = {arr}-->({i} + 1); -).

    To shift (arr - a 0-based index based rulebook producing a word value) left between/from (min - a number) and/to (max - a number) using (i - a nonexisting number variable):
    (- for ({i} = {min} : {i} < {max} : ++{i} ) {arr}-->({i}) = {arr}-->({i} + 1); -).

    To insert (length - a number) elements into (arr - a 1-based index based rulebook producing ZSCII letters) at (position - number):
    (- {-require-ctvs} ct_0 = {arr};
        for (ct_1=INPUT_BUFFER_LEN-1 : ct_1 >= {position}+LETTER_ARRAY_BASE+1 : --ct_1)
            {arr}->ct_1 = {arr}->(ct_1 - ({length}));
        (+ the number of letters in the chosen array +) = (+ the number of letters in the chosen array +) + {length}; -).

    To copy (count - a number) elements FROM (source - a 1-based index based rulebook producing ZSCII letters) at (sourceAt - number) TO (dest - a 1-based index based rulebook producing ZSCII letters) at (destAt - number):
    (- cacheval1 = {dest} + {destAt} + LETTER_ARRAY_BASE;
        cacheval2 = {source} + {sourceAt} + LETTER_ARRAY_BASE;
        #ifdef TARGET_ZCODE;
        @copy_table cacheval2 cacheval1 {count};
        #ifnot;
        @mcopy {count} cacheval2 cacheval1;
        #endif; -).

    [ "The previous input buffer", used for OOPS, is only 64 letters long, not INPUT_BUFFER_LEN letters long. ]
    To copy (count - a number) letters from (source - a 1-based index based rulebook producing ZSCII letters) to (destination - a 1-based index based rulebook producing ZSCII letters):
    (- #ifdef TARGET_ZCODE;
        @copy_table {source} {destination} {count};
        #ifnot;
        @mcopy {count} {source} {destination};
        #endif; -).

    To copy (source - a 1-based index based rulebook producing ZSCII letters) into (destination - a 1-based index based rulebook producing ZSCII letters):
    (- #ifdef TARGET_ZCODE;
        @copy_table {source} {destination} INPUT_BUFFER_LEN;
        #ifnot;
        @mcopy INPUT_BUFFER_LEN {source} {destination};
        #endif; -).

    To decide what 1-based index based rulebook producing ZSCII letters is (arr - a 0-based index based rulebook producing values) as a letter array: (- {arr} -). [ be sure to multiply the #/elements by WORDSIZE ]

    To copy (mwords - a number) chosen objects from (source - a 0-based index based rulebook producing objects) to (destination - a 0-based index based rulebook producing objects) (this is COBJ__Copy):
        let the count of bytes be mwords multiplied by the virtual machine's wordsize;
        copy count of bytes letters from the source as a letter array to the destination as a letter array.

    To copy (mwords - a number) elements from (source - a 0-based index based rulebook producing pattern unions) to (destination - a 0-based index based rulebook producing pattern unions):
        let the count of bytes be mwords multiplied by the virtual machine's wordsize;
        copy count of bytes letters from the source as a letter array to the destination as a letter array.

    To append a/an/-- (z - a ZSCII letter) to (buf - a 1-based index based rulebook producing ZSCII letters):
    (- {-require-ctvs} ct_0 = {buf}; ct_1 = (+ the number of letters in the chosen array +);
        ct_0->(ct_1 + WORDSIZE) = {z};
        (+ the number of letters in the chosen array +)++; -).

    To append (count - a number) letters from (source - a 1-based index based rulebook producing ZSCII letters) to (destination - a 1-based index based rulebook producing ZSCII letters):
    (- {-require-ctvs}
        ct_0 = {destination};
        cacheval1 = {destination} + WORDSIZE + (+ the number of letters in the chosen array +);
        cacheval2 = {source} + WORDSIZE;
        cacheval3 = {count};
        #ifdef TARGET_ZCODE;
        @copy_table cacheval2 cacheval1 cacheval3;
        #ifnot;
        @mcopy cacheval3 cacheval2 cacheval1;
        #endif;
        (+ the number of letters in the chosen array +) = (+ the number of letters in the chosen array +) + {count}; -).

    To append (obj - K) to (arr - a 0-based index based rulebook producing a value of kind K) of size (size - a number variable):
    (- {arr}-->({size})++ = {obj}; -).


    Section - For use by WDYM element grouping

    [ Used in the which-do-you-mean activity on the match list & match groups list. ]

    The current group-together number is a number that varies.

    To decide if we should say the group as A\AN\SOME: (- (match_classes-->ct_1 < 0) -).
    To we should say (element - a number) as A\AN\SOME: (- if (({element}) > 0) ({element}) = -({element}); -).

    To repeat through the first item of each group begin -- end:
    (- {-require-ctvs}ct_0 = match_list; ct_1 = 0;
            for ((+ the current group-together number+) = 1 : (+ the current group-together number+)<=number_of_classes : ++(+ the current group-together number+)) {
                while (((match_classes-->ct_1) ~= (+ the current group-together number+)) && ((match_classes-->ct_1) ~= -(+ the current group-together number+)))
                    ct_1++; !-).

    To if an/a/-- (earlier - nonexisting number variable) indistinguishable element is listed in (arr - a 0-based index based rulebook producing objects) begin -- end:
    (- for ({earlier}=ct_1-1 : {earlier}>=0 : --{earlier})
            if (Identical({arr}-->ct_1, {arr}-->{earlier})) -).

    [To choose (Nth - a number) element from (array - a 1-based index based rulebook producing word values):
        (- {-require-ctvs}ct_0 = {array}; ct_1 = {Nth}; -).]


    Chapter - Array-based Properties

    [ An object may have a property which is a 0-based array. These have their size in obj.#prop while the elements are pointed to by obj.&prop. However, the size returned is in bytes, not double-bytes, so a division is necessary. ]

    [ Useful for "name" and "add_to_scope" and "parser_inflection" properties only. Probably needs the ability to self-nest. ]
    To decide what [0-based index based rulebook producing] object is the hidden (prop - a value of kind K valued property) array of (O - an object):
    (- {O}.&{prop} -).

    To decide what number is the size of (prop - a word value valued property) of (O - an object):
    (- {O}.#{prop} -).

    To repeat through (prop - an [at least sometimes array] property) of (obj - an object) begin -- end:
    (- {-require-ctvs}
        ct_0 = {obj}.&{prop};
        cacheval1 = ({obj}.#{prop}) / WORDSIZE;
        for (ct_1=0 : ct_1 < cacheval1 : ++ct_1) -).

    [ This one needed to nest within the above repeat loop. ]
    To if (word - an understood word) is listed in (prop - an understood word valued property) of (obj - an object), (ph - phrase):
    (- {-require-ctvs}
        cacheval4 = {obj}.&{prop};
        cacheval3 = ({obj}.#{prop}) / WORDSIZE;
        cacheval2 = {word};
    #ifdef TARGET_ZCODE;
        @scan_table cacheval2 cacheval4 cacheval3 $82 -> cacheval5 ?~ScanTable{-counter:scantable};
    .ScanTable{-advance-counter:scantable};
        if (cacheval5) ! returns the address if found or 0 if not found
        { ! convert the address to the element #
            cacheval5 = (cacheval5 - cacheval4) / WORDSIZE;
            if (true) {ph}; }
        }
    #ifnot;
        @linearsearch cacheval2 WORDSIZE cacheval4 WORDSIZE cacheval3 0 $$0100 cacheval5;
        if (cacheval5 ~= -1)
            {ph}; }
    #endif; !-).

    [! for (ct_1 = 0 : ct_1 < cacheval1 : ++ct_1)
    ! if ({word} == ct_0-->ct_1) {ph}; ]

    Volume - Differences between the Z-machine and Glulx

    Book - (for Z-machine only)

    [holds an array or routine]
    To decide if (ad - a 0-based index based rulebook producing a word value) is currently a rule:
    (- (UnsignedCompare({ad}-->0, top_object) > 0) -).

    Include (-
    Constant LETTER_ARRAY_BASE = 1;
    [ DictionaryWordToVerbNum dword verbnum; return 255 - (dword->#dict_par2); ];
    -).

    To decide which understood word is the (Nth - number) -th/-- word --/element of (ar - a 1-based index based rulebook producing a struct):
    (- ({ar})-->(({Nth})*2-1) -).
    To decide which number is the (Nth - number) -th/-- word's length --/element of (ar - a 1-based index based rulebook producing a struct):
    (- ({ar})->({Nth}*4) -).
    To decide which number is the (Nth - number) -th/-- word's position --/element of (ar - a 1-based index based rulebook producing a struct):
    (- ({ar})->({Nth}*4+1) - LETTER_ARRAY_BASE -).
    To decide which number is the (Nth - number) -th/-- word's real position --/element of (ar - a 1-based index based rulebook producing a struct):
    (- ({ar})->({Nth}*4+1) -).

    To change (Nth - number) word of (arr - a 1-based index based rulebook producing structs) to (data - understood word):
    (- {arr}-->({Nth}*2-1) = {data}; -).
    To change (Nth - number) word's length of (arr - a 1-based index based rulebook producing structs) to (data - number):
    (- {arr}->({Nth}*4) = {data}; -).
    To change (Nth - number) word's position of (arr - a 1-based index based rulebook producing structs) to (data - number):
    (- {arr}->({Nth}*4+1) = {data}; -).

    [ Get the number of structs ]
    To decide what number is the number of words in/of (prs - a 1-based index based rulebook producing structs):
    (-({prs}->1)-).

    [ Get the number of letters/elements. ]
    To decide what number is the number of letters in/of (buf - a 1-based index based rulebook producing ZSCII letters):
    (-({buf}->1)-).

    To unpack (act - an action name variable) and whether (rev - truth state variable) from (packed data - 0-based index based rulebook producing grammar tokens):
    (- {act} = 256*({packed data}->0) + {packed data}->1;
        {rev} = (({act} & $400) ~= 0);
        {act} = {act} & $3ff; -).


    Book - (for Glulx only)

    [holds an array or routine]
    To decide if (ad - a 0-based index based rulebook producing a word value) is currently a rule:
    (- ((({ad}-->0)->0) ~= $70) -).

    Include (-
    Constant LETTER_ARRAY_BASE = 3;
    [ DictionaryWordToVerbNum dword verbnum;
        dword = dword + #dict_par2 - 1;
        @aloads dword 0 verbnum;
        return 65535 - verbnum;
    ];-).

    To decide which understood word is the (Nth - number) -th/-- word --/element of (ar - a 1-based index based rulebook producing a struct):
    (- {ar}-->(({Nth})*3-2) -).
    To decide which number is the (Nth - number) -th/-- word's length --/element of (ar - a 1-based index based rulebook producing a struct):
    (- {ar}-->(({Nth})*3-1) -).
    To decide which number is the (Nth - number) -th/-- word's position --/element of (ar - a 1-based index based rulebook producing a struct):
    (- ({ar}-->(({Nth})*3) - LETTER_ARRAY_BASE) -).
    To decide which number is the (Nth - number) -th/-- word's real position --/element of (ar - a 1-based index based rulebook producing a struct):
    (- {ar}-->(({Nth})*3) -).

    To change (Nth - number) word of (arr - a 1-based index based rulebook producing structs) to (data - understood word):
    (- {arr}-->({Nth}*3-2) = {data}; -).
    To change (Nth - number) word's length of (arr - a 1-based index based rulebook producing structs) to (data - number):
    (- {arr}-->({Nth}*3-1) = {data}; -).
    To change (Nth - number) word's position of (arr - a 1-based index based rulebook producing structs) to (data - number):
    (- {arr}-->({Nth}*3) = {data}; -).


    [ Get size of a ]
    To decide what number is the number of words/letters in/of (arr - a 1-based index based rulebook producing word values):
    (-({arr}-->0)-).

    To unpack (act - an action name variable) and whether (rev - truth state variable) from (packed data - 0-based index based rulebook producing grammar tokens):
    (- @aloads {packed data} 0 {act};
        {rev} = ((({packed data}->2) & 1) ~= 0); -).



    Original Parser ends here.

    ---- DOCUMENTATION ----

    Chapter: Introduction

    The parser is the part of software that takes the letters our player types into the game, and figures out to which action(s) and object(s) those words refer. This transformative step is useful to the game author because the author is in most cases only concerned with the player's intent, not how the intent was worded. For Inform, the parser is one of the oldest pieces of software within it, and since changes to it could interfere with players' long-ingrained typing habits, has not been rewritten with modern tools or sensibilities.

    If the parser isn't doing what an author expects or needs, they are welcome to change it anyway. But the language and techniques used to build the parser presents a technical obstacle even to the above-average Inform 7 author. And so, this extension presents the venerable parser in the rather more readable Inform 7 source code, but written in such a way that, upon pressing GO, Inform 7 will effectively re-create the original parser.

    This documentation walks us through the higher-level aspects of the parser while omitting many of the finer details of memory management, array access, and virtual machine differences that the original code makes extensive use of.

    Let's begin.

    Chapter: Parse

    Section: Enter the Parser

    Of the many rulebooks within Inform 7, one of the top-level ones is named "the turn sequence rules". (Click the Rules tab within the index. The "top level" section is near the bottom, below "light and darkness".) The first rule within the turn sequence rules is the parse command rule. This calls a to-phrase, "parse", which you'll find within this extension. This is our starting point, and is also the largest single part of this extension. Let's walk down this to-phrase line-by-line to see what the parser actually does.

    The first lines reset many variables to their initial state, such as "the parser's results" which will eventually be copied into "the actor", "the noun" and "the second noun" which we use regularly. (We'll see what "the ranking strategy" does much later. For now, it's being reset to its initial state as well.) But then we hit this curious line.
        if we have more to parse:

    If the player enters, "TAKE COIN. EXAMINE IT.", the parser will process the first command, leaving "EXAMINE IT" behind and setting the More To Parse truth state to true. So the first thing the parser does it look for something leftover from a previous parse. If found, it'll clear the state and use that as the next command to work on.

    But the usual case is the Otherwise, in which we'll carry out the READING A COMMAND activity. A peculiar While loop surrounds the activity because of the "reject the player's command" phrase we sometimes use:

        After reading a command:
            if the player's command includes "please":
                say "Please do not say please.";
                reject the player's command.

    The "while this isn't done" pairs with the following "do that again while the rule failed" so the lines in between them will be re-ran if the author used "reject the player's command".

    You'll notice the ranking strategy is initialized to 0 again. A brief digression: it's because of the line "(At this point we ReType)". Normally in a phrase, Inform performs the lines sequentially, barring the occasional "if" statement, which causes it to sometimes skip ahead, and the occasional "repeat" or "while" statement, which causes it to hop backward a few lines. The "at this point..." line marks a place to hop to from anywhere else in the phrase, specifically, from a line like "go to where...". Since "at this point" is a landing pad for such long-range jumps, the ranking strategy is re-initialized just in case it's been changed by the time the jump launched from where-ever.

    Now, we already now what "the location" means -- it's what room or container our player is currently in. "The actor's scopewise location" is effectively the same thing with one minor difference which I'll explain when we get to that. (Also, what is "in scope" and what is "visible" is also the same thing, with one minor difference, which we'll examine later.) For now, we initialize the variable.

    The lines within the "if handling" block act as a Last For Reading A Command rule, and it's the first line within which does most of the work. "The number of words typed in at the keyboard into.." is a phrase elsewhere in this extension which talks to the keyboard, redraws the status line and command prompt, and processes UNDO, OOPS, and the easier parts of AGAIN. (Its return value is unused, but could be.) It also breaks the string of letters into a string of words.

    As you know, interactive fiction is played on an 'interpreter', such as Gargoyle, Quixe, Frotz, Zoom, Spatterlight, and so on. One of the duties of the interpreter is, given a string of player-typed letters (which is in "the player's input buffer"), group them into words and look those words up in the game's dictionary. The interpreter gives back a list of dictionary references (which is in "the player's parsed command"). So a command like,

        EXAMINE THE DOOR

    comprises several letters, or three words, depending on how you look at it. This is also done within that sole line when it goes to the keyboard for information. So now, what to do with it?

    The next line puts good information into "the player's command", which is used by Inform 7 authors. If you don't know what a snippet is, now's the time to learn: it's a pair of numbers. The first number is the Nth word of a series of words, and the second number is the number of words in the series. (The first number is multiplied by a hundred and added to the second, so if a player should type in a command with more than 99 words in it, the parser will break.) Since "the player's command" is supposed to represent the whole command, it's set to the number of words in EXAMINE THE DOOR (3), plus 100 (because it always starts from word #1). Another snippet, "the matched text", might hold 201 to represent THE in the above example, or 202 to represent THE DOOR, or 102 to represent EXAMINE THE.

    The next line looks like a tautology: now the number of words in the command is the word count. Well, "the number of words in the command" is a number-that-varies, while "the word count" is a to-phrase that figures out how many words are in the current command. The reason we make a copy of it is for quick reference later: every time we need to know this information, it's slow if we have to ask the phrase to count them all up again. So this line is like writing down the answer on a little slip of paper for use later on.

    Anyway, outside that if-statement we have a landing pad, "at this point we reparse". We soon after initialize some variables -- including ones we just did a moment ago -- because who knows what has happened from where the jump launched. It might have even re-written the letters in the input buffer for all we know. We also issue the line, "PARSE the player's input buffer into the player's parsed command", which again asks the interpreter to turn the string of letters into a string of words.

    Now why, you might ask, would we do this again, especially if the same line already appeared within "if we have more to parse"? Partly it's because this old parser had features added to it piecemeal over a couple of decades, so it's safer when adding to it not to change anything that works already even if you're duplicating what work the software needs to do. Since later parts of the parser can rewrite what letter or words are in the player's input buffer, it needs the ability to do-over. When it needs a complete do-over, it lands at "we reparse". So in some sense parsing -- without caring where the input actually comes from -- actually starts here.

    That's the best explanation I can give, really. I don't know because I didn't write it, just translated it to this pseudo-English that is Inform 7.


    Section: Walking Down a Path of Words

    Here's a quick dictionary of the terms we'll see in the following lines.

    "The parser's current word position" is the most important. The parser will consider one-by-one each of the looked-up dictionary words that the interpreter gave it. This variable is which one it's currently on.

    "The verb's position" will be the Nth word where the verb is found, once we've found it. Usually it'll be one: EXAMINE ME, TAKE THE ROCK, CLIMB A TREE, all have the verb as the first word. But sometimes there's a DR HATHAWAY, EXAMINE ME which moves it a bit further down.

    "if trace..." is ignored except when the testing command TRACE has been issued. Go ahead, start playing your work-in-progress in the right-side pane of the Inform 7 environment, and type TRACE 6, followed by any legal command, such as EXAMINE ME. These if-trace statements will say a lot. If you modify the parser somehow and it breaks, the TRACE command is invaluable. So we'll see these "if trace..." statements sprinkled throughout.

    "The person asked" is a synonym for "the actor", which you are already familiar with.

    "The actor's scopewise location" is the same as "the location" except in a very specific circumstance.

    "The predicate's position" marks the beginning of a sentence's predicate, which for English, is where the verb is. However, the parser does have some vestigial support for non-English languages, which I have not taken the care to preserve, nor am convinced still function.

    "The scope decider" is likewise an old feature of the parser which Inform 7 supplies in a different way. It used to be that an object could decide for itself whether it was in scope or not, or put other objects into scope as it pleased, but the modern way is to use the Deciding The Scope activity. Still, this feature is mostly preserved as this rule-that-varies.

    "The action to be" is like "the action name part of the current action" (see chapter 12.20, Stored Actions), with the minor difference in that it's actually a lot simpler. Like "the noun" and "the second noun", this one is "the verb" except it is an action name -- the taking action, the inserting it into action -- rather than the specific synonym used -- like GET vs TAKE. Still, it's useful for Inform 7 work.

    And now, we're "at the point we BeginCommand".

    The "next word if any" is a phrase which takes a single step down the path of words we're to parse. If the word it says it finds is the error state "no more words left to parse", then, in this particular case, we throw a parser error. Since we just began parsing, there should be plenty of words to parse, but again, those landing pads we passed on the way here could've messed things up.

    But anyway, assuming nothing's broken, we ask if the word is AGAIN. Inside this "if the verb is AGAIN" is some pretty obvious code that looks like a series of Check rules followed by a Carry Out rule. The only new-ish bit is "copy the Again buffer into the player's buffer". Since AGAIN makes the parser do whatever the player typed-in previously, the parser keeps a copy of the previous command around. It keeps it in "the again buffer". The rest of the lines within this if-statement look familiar. And we also see our first "go back" launch. Since it says "back" the landing pad is earlier in the phrase; we saw it over by the Reading A Command activity, if you'll recall.

    Assuming the word wasn't AGAIN, then we make that copy into the Again buffer for next time.

    Beginning with "if the predicate's position is 0" is an interesting feature not exposed to Inform 7. If you have a German character who only speaks German, an author might want the player to give commands in German, like "DR VAN HELSING, ÜBERPRÜFEN SIE DIES." rather than "DR VAN HELSING, EXAMINE THIS". This block of code allows this. Since it isn't exposed to Inform 7 (and my ported code may not be completely correct), I'll explain it only briefly. Characters have a hidden property, a rule-that-varies, which is totally responsible for parsing the line of text. The rule produces an "alternative grammar result" (see elsewhere in this extension) which the following if-otherwise-if chain looks at. "This created action" means it's all done, so copy the answers to the appropriate locations and stop parsing. Other results mean never mind or error, as appropriate. That's all I'll say besides this: the only way that "the predicate's position" won't be zero is due to code we haven't even gotten to yet, which will launch & land before this if-block.

    The next block is within "if the verb is a word unknown by the game or the usages of the verb does not include being a verb". By 'verb' we probably still mean the first word, and now we're asking if that first word really is a verb. If it isn't, the code within this if-statement is going to see if that word is a compass direction. If it is, we'll copy the answers to the appropriate locations and then stop parsing: the command is simply "NORTH" or some such.

    A note about the "save.." and "restore..." lines. If we want to try out some code 'hypothetically', we save what that code might use or change with "save...", put into those variables what we want that code to see, use the code, then "restore.." what was changed. In this case, we made a new variable with "let pu be..." and called the code.

    So now let's briefly look at "noun domain".


    Section: A First Look at Noun Domain

    The phrase known as "noun domain" straddles that line between text and simulated space. When the player types "LOOK INSIDE THE DARK WELL", the parser at first sees merely a string of five words. But also, in an interactive fiction, the dark well is a simulated space, which can contain things including the player. And if the player is inside the well, what he can interact with is constrained by virtue of his location there. Moreover, the verb LOOK INSIDE itself expects a thing to look inside, so "noun domain" constrains what it understands in that way too. Consider the player entering "KISS ROCK". The kissing action says to expect a someone, so even if there's a perfectly serviceable rock at hand, "noun domain" will make a fuss because ROCK is not in the expected domain of 'someones'.

    Here, we're searching the "compass" place -- and I use the word place in a loose conceptual sense -- under the expectation of finding a 'something'. This is, I must admit, a bad example of Noun Domain, because Inform has for many years had a hidden game object called "compass" which 'contains' the directions. In other words, this is a dirty hack which Inform 7 could handle more elegantly if someone were to write a parser from first principles in Inform 7. The parser is old code, and again, altering it could muck with player's habits, so this odd way of determining directions has stayed this way for years.

    At the end of the day, know this: "noun domain" is in charge of parsing a noun phrase (see your English textbook for the definition of noun phrase) with the assumption of finding a fit within a particular domain, such as 'someone' or 'something inside' or whatever. The answer it gives back is called, in this code, a "pattern union" for lack of a better term, because it returns any number of meanings -- some kind of game object, various error codes, various commands to the parser itself, etc.

    So, back to the code at hand. Assuming what was parsed was NOT a direction, but "noun domain" says it IS a recognizable something-or-other, we'll skip right by both of those if-statements and out of the "if the verb... or the usages..." block we've been in for the last few paragraphs. And this brings us to conversation, and addressing other people.


    Section: Addressing Other Characters

    "if the player is the person asked" begins this larger if-statement, followed immediately by a search for commas in the player's command. So we're looking for commands of the form JANE, LOOK AT ME which has a person we're ordering around, a comma, and then the order in question.

    The "if ... the 'comma' is listed in the player's parsed command" is pretty obvious, but what's with the two ifs in front of it? Well, if we launched from further below, and landed above, we need to protect ourselves from that. Also we need to be aware of situations like JANE, BOB, LOOK AT ME, which is grammatically incorrect, but who knows what a player may enter. Anyway, this "is listed in" will set "where that word was found". (Inform considers a comma to be a word all on its own.) If there's no comma, we skip way down to "the point we do not have conversation". Otherwise, let's look at how Inform handles addressing other people.

    We'll call noun domain again, telling it to expect any 'someone' who's either in the player's location, or carried/worn by the player. (We make allowances for the fantasy genre.) Now we run through a series of if-statements that act much like Check rules, some of which look for strange erroneous input, and some enforce the rules of the virtual world. Once things check out mostly OK, we'll update the player's pronouns, update the position of the verb to be after the comma, and after one final check, set "the actor" to this object that noun domain gave us. We also update the location variable the parser uses, because commands like TAKE THIS depend on what the addressee can reach, not what the player-character can reach.

    If all went well, we launch back up to BeginCommand. If we find another person and a second comma, this block will be skipped entirely, so the Answering It That action will be called with the remainder of the command, multiple commas and all.


    Section: At This Point We Do Not Have Conversation

    A slight misnomer, this point, because even if we do have conversation, we'll jump way back up to the earlier part, filter our way back down (except for BOB, NORTH or somesuch), and then fall to here when the addressee portion above refuses to allow a second entrance.

    Or maybe there never was an addressee and we got here honestly. Either way, we always end up here except in the case of a compass direction. So let's look at the word that "the verb" is pointing to, either as position #1 or wherever is at the comma's position, plus one. If it isn't a verb, well, that might be ok. What if the author issued a line like this:

        Understand "[tool]" as using.

    A tool isn't a verb, but a lone noun is still OK by the game. Inform has a faux-verb it uses for those sorts of things, which we call "the missing verb for noun-only commands". But Inform proclaims things like NURSE, SCAPEL should go to the Answering It About action, so it does this by sending control down to "where we GiveError". Of course, invoking the Answering action isn't an error, so this is bad design, but again, this is old code which accumulated features over a very long time.

    Anyway, assuming everything is OK, and it usually is, we have a verb. It's interesting that out-of-worldness is attached to the verb, not to the action, but again, old code. Set the "action out of world" variable and ensure an NPC isn't trying to accomplish it.

    This brings us to understand lines and grammar tokens, which we'll digress for explanation.


    Section: Understand Lines, Grammar Tokens, and Types of Grammar Tokens

    Game authors write understand lines like so.

        Understand "steal [something]" as taking.
        Understand "toss [other things] in/inside/into [something]" as inserting it into.
        Understand "choose [things inside] from [something]" as removing it from.
        Understand "splash [available color]" as painting.
        Understand "go to [any room]" as going by name.

    Each of those understand lines is transformed into a machine-readable representation within the game file. Each element of each line will state 1) what the 'type' of the element is, and 2) the actual value. Let's look first at the umbrella types possible, then it will start to make sense.

    There's several token types in the parser, though Inform 7 uses only some of them. This extension frequently denotes a token type by angle brackets within single quotes, '<like this>', except for the simple ones like the end of line token. Using the sample game author code just above to illustrate, the relevant token types are:

        '<grammar token>' - like "[things inside]"
        '<understood word>' - like "choose" or "from"
        '<description of values>' - like "[available color]"
        'any <description of objects>' - like "[any room]"
        '<understand token>' - [ then tdata is a <Routine> that returns GPR_FAIL, GPR_PREPOSITION, etc.]
        the first slashed synonym - 'in' in "in/inside/into"
        a middle slashed synonym - 'inside' in "in/inside/into"
        the last slashed synonym - 'into' in "in/inside/into"

    (The slashed-synonym types aren't true types since they're just a variation on '<understood word>', but we'll talk of them as if they were.) Now remember each of the above is a type. Meaning, each of the above has a range of values that it can be, and the ranges for each are very different. For example, the 'any <description of objects>' token can have as its value any of the following:

        "[any room]"
        "[any person]"
        "[any thing touched by a snorgle]"

    ..and so on. Similarly, '<understood word>' covers words the player can type, like "steal", "toss", "choose", "from", "splash", and so on. An interesting token type is '<grammar token>', whose value can be one of these:

        'something'
        'something preferably held' [held token]
        'things' [multi token]
        'things preferably held' [multi-held token]
        'other things' [multi-except token] [ anything except the second noun ]
        'things inside' [multi-inside token]
        'someone' [ synonymously anyone, somebody, anybody but NOT 'a person' ]
        'understood word' [ used for TEST and SHOWME testing commands ]
        'number'
        'text' [ The Topic Understood ]

    (Notice these all have single quotes around them, but no angle brackets.) These are all of the tokens from chapter 16.4, "Standard Tokens of Grammar", of Writing With Inform. It's here in the parser that we actually have to make them work.

    So to recap, this line:
        Understand "toss [other things] in/inside/into [something]" as inserting it into.

    Becomes the machine-readable:
        '<understood word>' ---- "toss"
        '<grammar token>' ---- 'other things'
        first slashed synonym ---- "in"
        middle slashed synonym ---- "inside"
        last slashed synonym ---- "into"
        '<grammar token>' ---- 'something'
        end of line token ---- end of line token

    And likewise for all the other Understand lines in the author's game. In the code, "this grammar token" refers to one of the right-hand column values in the above example, and "this token's type" refers to the left-hand side. When "the understood command's current size" changes, it changes which row those two refer to.

    We're getting ready to repeat through many of these lines, looking to match the player's command. And for each of those lines, we are twice going to use another loop to step through the type and token elements in each line. Twice, because the first time is only preliminary needed for certain grammar tokens.


    Section: Repeating Through Understand-as Lines

    Right after the trace line "Parsing for the verb" we begin initializing many variables, then begin a repeat loop with "repeat through the understand-as lines for the verb", and initialize more variables which are used on each understand line we're going to try to match to the player's command. A dictionary of all these new variables may be rather boring to read, but it really will help understand what's going on in the code.

    Rather than list these in the order the code has them, we'll list them in order of relevance.

    "The chosen grammar" is the current line we're working on. A line begins where the previous line ended, hence this variable is set by referencing its old value.

    "The player's understood command" is what the parser is really all about. Just as the player's input buffer sees EXAMINE THE YELLOW TREE as a series of a few dozen letters, and the player's parsed command sees the same command as a series of four words, the player's understood command sees that command as a series of two components, one actionable verb followed by one game object. Its understanding of the input is the highest-level of the three, and it is in some senses the final product of any parser.

    "The understood command's current position" we use in conjunction with the above. When this is changed, then "this grammar token" and "this token's type" change in reaction.

    "The likely second noun" takes some explanation since it participates in a long solution to a non-obvious technical problem that arises during parsing two lesser-used tokens. Consider this line.

        Understand "put [other things] into [something]" as inserting it into.

    The token "[other things]" means anything except the "[something]" which will be the second noun. But the way our parser works is by stepping through a series of words from left to right, so when the parser first encounters "[other things]", it needs to know what the second noun is to process the first noun, but it always processes the first noun first. So, we'll soon see a rather large section of code that does nothing but put everything on pause and parses the whole rest of the line 'hypothetically' just to fill-in "the likely second noun", just to process the first token. The other token which causes this is "[things inside]".

    "The number of parameters in the command" simply notes now many noun phrases a verb expects. One for TAKE, two for INSERTING IT INTO, zero for JUMP, and so on.

    "The count of non-objects" is for commands like SET (THING) TO (NUMBER) in which not all parameters are game objects, but instead numbers or kinds-of-value.

    "Process the multiple object list" remembers whether or not we need do what it says. For commands like TAKE THE CANDLE AND THE APPLE this variable will become true.

    "Allow multiple objects unconditionally" only gets involved to catch an edge case for TAKE ALL. We'll detail it later.

    "The multiple-object list" is a list, of course, of multiple nouns in the same noun phrase, such as THE BOOK AND THE CANDLE in the command GIVE THE BOOK AND THE CANDLE TO ARIAL.

    "The kind of multi" is a grammar token. When an understand line uses "[things]" or "[things inside]" which allow a list of nouns, this variable is set to 'things' or 'things inside' or whatever, to remember which multiple-allowing token is in effect.

    "The indefinite article mode" remembers whether the player used THE or A/AN/SOME.

    "The pronoun used" holds which pronoun the player used in the command. It's a dictionary entry, like that used in the player's parsed command.

    "The pronoun reference object" is to what that pronoun refers to.

    "Where inferring the pattern from" remembers where the command trailed off, but the parser can still figure out the rest on its own. Or to put that another way, it decides whether the Clarifying The Parser's Choice activity is needed or not.

    "The best parser error so far" is of course one of the standard parser errors. The parser goes to some trouble to try to give the most specific (and hence, helpful) parser error it can. This variable aids in that.

    "The second-best parser error so far," likewise.

    "The latest parser error" is the real one. Inform 7 authors should already recognize it.

    "The numeric word" exists for commands like TAKE 50 COINS, a command of three words, the middle being the "numeric word". Also worth noting is that kind-of-values like color are treated like numbers by the parser, similar to how the words one, two, and three are treated as numbers. (Though in that case they actually are.)

    "How many parameters lookahead found" helps tell us when we can stop fast-forwarding and examine the current token. It's very temporary, appearing only three times in the whole code.


    Section: The Annoying Look-ahead Loop

    Upon finishing this slew of variables we find a while loop that stops on finding the end of line token. It does of course iterate through the elements of the current understand line, but it isn't processing what you think it is. This is the look-ahead loop, and its sole purpose in life is to find a value for "the likely second noun", so two tokens, "[other things]" and "[things inside]", will work correctly.

    As explained above, those two tokens when used in the first noun's slot need to know what the second noun is in order to work, but since we haven't parsed the second noun yet, we use this loop to fast-forward and take a look.

    This whole loop does nothing unless and until it finds "[other things]" or "[things inside]" in the slot for the first noun.

    So back to the code. We use "how many parameters lookahead found" to keep track if we're on noun number 1 or 2 (or more), and we also check to see if the current element's type is '<grammar token>', and if so, if its value is either the 'other things' or 'things inside' token we're interested in. Assuming this circumstance comes to pass, we enter the rather large if-statement.

    Incrementing "the understood command's current size" advances us off the 'other things' or 'things inside' token and onto the preposition for the line. (For example, onto the "in" in "toss [other things] in/inside/into [something]".) Now we advance our position in the player's command until it's at a preposition word, placed in "the word". But how does the parser know which words or even if what the player typed in is a preposition or not?

    Here's the scoop: even for a command like "examine [something] dawg", the word "dawg" is considered to be a preposition by the Inform compiler. If a required word isn't first, like EXAMINE is here, it's marked to be a preposition in the game's dictionary. If a word refers to an object, like BRASS for a RING, it won't be marked as a preposition. Simplistic, but good enough for this purpose.

    So the one-line "until" loop continues forward through the player's parsed command searching for a word that is marked as a preposition, because presumably, the second noun will appear sometime after that. Right?

    Well, usually. What about GIVE SNOW WHITE THE APPLE? About 90% of the time this method works. But since it's only used for two grammar tokens placed in a particular position, 90% of the instances of a rare situation is about 100% effective.

    Anyway, the look-ahead aborts if, as in the case of Snow White, the prepositions are missing.

    Assuming there's prepositions, now we enter a "while this isn't done" loop. (This kind of loop *always* allows entry. You'll follow its contents at least once no matter what.) Now we want to see if "the word" typed in matches the preposition(s) in the understand line. If not, it'll advance deeper down the player's parsed command, because maybe "dawg" was part of the noun and there's another "dawg" down the line. If so, the "next" statement jumps us back up to the "while this isn't done" to try again. But if there really is no match, the understand line doesn't match the player's input at all, so abort the whole thing, and get a new understand line if any.

    Back to the happy path: if one of the synonymous prepositions matched, (and we "do that again" in case the player typed a couple of prepositions in a row, like "up into" or whatever), then we've made it to where the second noun is at.

    (The way our one-liner "until" loop works, it always goes one word too far, so we back up a step.)

    Now, if the second noun is "[something]" we begin parsing the words here using two feature-rich pieces of code, "parsing descriptor words like..." and "noun domain", and if they are happy with the words here representing some game object in scope and all, we finally, finally set "the likely second noun" to that object. And "break" out of our While loop.

    It's worth noting we get different behavior for:

        Understand "choose [things inside] from [thing]" as removing it from.

    than for

        Understand "choose [things inside] from [something]" as removing it from.

    Specifically, if we want 'things inside' to function correctly, the second noun must be 'something', not 'thing' or 'someone' or anything more specific. Such is our parser.


    Section: When I Said TAKE ALL I Didn't Mean Me

    The TAKE ALL exception is yourself: you never want to take yourself. For the purposes of better error messages, the parser catches TAKE ALL trying to take one's self. The variable works similar to a DEFCON warning status. Initially zero, when each condition becomes true -- the taking action is desired, there's one parameter on the grammar line and it is 'things', the word ALL is used by the player -- the variable increases in value until it sounds the alarm... well, until an alarm is sounded, so the variable tells the error-reporting machinery not to complain about yourself being the only takable thing in the room, but not cooperating.

    Anyway, before we enter the next loop we go through our usual rash of variable initializations, the only interesting one being that we set the parser's current word position back to where it was before the lookahead loop futzed with it.

    Section: Choosing a Matching Understand-as Line

    Now we come to the business of looping through a great many Understand-as lines. Specifically, the understand-as lines which used the verb that the player used. (This means it could be a number of different actions. For example, TAKE could mean the taking action, or as TAKE OFF, the removing clothes action.)

    The repeat loop says "forever" but that's a slight exaggeration. If all goes according to plan and we find a grammar line which matches the string of verbs & objects, we'll certainly exit. The logic of this loop is a bit convoluted, again due to being old code accreting features over time. Fortunately it's one of the shorter loops in the extension.

    "The understood command's current position" acts as our cursor as we walk down the pieces of the understand-as line, but we'll copy what it's pointing to into "the token", as well as copy the next whatever into "the lookahead" and use those instead to mitigate the look-up time it takes to use the former. Also, the token it points to is actually a combination of two variables I introduced us to earlier: the current grammar token & current token type. Old code tends to try to squeeze as much data into as few pieces of computer memory as possible, and Inform has always followed suit. (How snippets combine two numbers into one are another fine example of this.) Hence the line now where we "split" the token into those two variables.

    The next line, which retrieves "the parsing of..", is a long and involved process. Suffice to say, how each of the token types work -- '<understood word>', '<grammar token>', etc. -- is found in there. It's also in charge of filling in the multiple object list when appropriate. But the reason the following code looks so opaque is that "the parsing of..." really want to return an object which contain several pieces of data all at once, but the code was written for a version of Inform in which objects weren't yet a feature of the language. This is also the reason that the code here uses variables like "pu" for pattern union, and "pr" for parse result. They are both wanna-be objects.

    And when "the parsing of.." finishes, it isn't necessarily finished. One of the things it can return is a command to run itself again, reparsing the inputs as one of the '<grammar tokens>', such as 'something' or 'someone'. some other assumption. The reason for that is because it uses the previously-mentioned "noun domain", which can call for a reparse of the noun phrase, and if so, "parsing of" needs to as well.

    Afterward, we do something different depending on whether the parse result "pr" indicates success, some kind of failure (there are several kinds), or some kind of parameter (indicated by "pr" not being a 'reparse' command). And if "pr" is a kind of parameter, whether that parameter is a kind-of-value (like colors), a number or time or time period or other "the whatever understood", the multiple object list (indicated by "resulting objects" and "bunch of objects" placeholders), or an honest-to-goodness game object (like THE YELLOW TREE). In each of those cases we may need to keep track of 1) how many parameters in the line we've used up and/or have yet to go, 2) how many of those parameters are game objects rather than kinds-of-value or numbers or units, and 3) indicate, via the "pu" variable (a pattern union, like what noun domain outputs) our overall status: pattern matched successfully (to a non-object like time or color, but keep on reading the line), noun domain's no match (so stop reading the line and fetch a new one to try), a bunch of objects (like success, but remember that's it's a whole list of successful objects), or just echo the game object in question if that was what "parsing of..." found.

    Ah yes, and when appropriate we also update "the parser's returned noun", which will eventually become Inform 7's "the noun" and/or "the second noun" which we know so well. We also update the "player's understood command", which is that view of the player's command EXAMINE THE YELLOW TREE as just two entities, one action and one object.

    This little section ends at the otherwise. Within that otherwise is what happens once we've reached the end of current understand-as line. We do a few final checks.

    First, have we used up all the words in the player's command? If the next unused word is THEN or a comma everything's OK. The command was something like EXAMINE THE TREE THEN CLIMB THE TREE, or, LOOK, WAIT, in which case we'll save it for the next time we parse -- checking if "we still have more to parse" is one of the very first things Inform does when it's time to parse. Otherwise we have an "I only understood you as far as" error, so we'll stop here and report it.

    If we have used up all the words, then we do something special for those troublesome grammar tokens 'things inside' and 'other things'. Remember how we had that whole annoying look-ahead loop in case they appeared as the first noun in the command? Well here we handle the case when they appear as the second noun. It's much simpler: the filter phrase just removes from the multiple-object list anything that shouldn't be there. We do need to ensure afterward, however, that we still have something in the multiple object list afterward. So for PUT EVERYTHING INTO MY RUCKSACK, when the only thing around is said rucksack, we will have only the rucksack in the multiple object list, which is promptly removed by this filter, will promptly causes a resulting "nothing to do" parser error.

    Next up is the TAKE ALL exception. Similar to the above rucksack case, we'd rather the parser throw a "nothing to do" error than start a take action guaranteed to fail because the only thing in the multiple object list is yourself.

    Assuming we've made it past that final gauntlet, we have finally, on the 300th line of the phrase, located which understand-as line matches the player's input. The all-caps line itself is a phrase which copies the parser's results to the Inform 7 variables, calls the Clarifying The Parser's Choice activity if needed, swaps the noun and the second noun if they're "(with nouns reversed)" (see chapter 16.2 in Writing With Inform), and sets pronouns.

    The final bit before the STOP will move the rest of the command, from the word THEN through to the end, leftward, so EXAMINE THE TREE THEN CLIMB THE TREE becomes just CLIMB THE TREE. A problem is only presented if there's no THEN or comma, but since we checked for that earlier, there shouldn't be any problems.


    Section: When Lines Don't Match

    So what now when none of the lines match? Or one line got very close but then throws an "I only understood you as far as" error?

    Let's take the last question first: we update the "best parser error so far" and possibly the "second best parser error so far" according to what the designer considers 'best', and get ready to try the next line. But if the error so far is "nothing to do" because of the TAKE ALL exception, we immediately stop checking lines. Why? It's because in that case the whole line matched just fine -- the action and the object(s) the player mentioned are all spot-on -- but we already know doing the action will be pointless. And while Inform 7 authors can write "Instead of taking yourself", they cannot write "Instead of taking a list of things which includes only yourself", meaning they can't reword the response to something like, "You're the only thing around here worth taking, dear player." Instead, we stop everything and send to "nothing to do" and let the author rewrite that.

    Anyway, un-indenting one more time to bring us within arm's reach of that left margin is a landing point, GiveError, which is where we go when no understand-as line match... or anything else goes wrong in the phrase, really. It calls another phrase, "give a parser error" which appears about two screens below that point in the source. That phrase either 1) says the parser succeeded after all, as an Answering It That action, or 2) calls the printing a parser error activity. There is a third case unused by Inform 7: the variable "the latest parser error" can be used like a rule-that-varies (or a say-phrase, or text that includes a say-phrase), and if set such, the contents will be followed instead. But the contents can't turn failure into success so it can't do anything that the activity couldn't -- less, in fact. But, again, old code.

    The "give a parser error" phrase returns and, assuming we aren't to do the Answering It That action, launches us to the beginning of this long, long phrase: ReType will re-run the Reading A Command activity while AlmostReParse will start just after that, re-using the player's words for another attempt. (The latter is only used for NPC-specific grammars.)

    And thus, "to parse" ends with it scrambling to figure out how it ended up there.

    But stay tuned: "parse" used a couple of heavy-duty phrases which need some explaining themselves.


    Chapter: Noun Domain

    As stated previously, the phrase known as "noun domain" straddles the line between simulated space and just-a-bunch-of-words. It's in charge of matching words to one or more nouns given that the associated game objects are "in" the simulated space where we're looking. (So even if Noun Domain knows what a doorknob is, it may pretend it doesn't if an action like KISS says to only recognize a person.) But the most interesting parts of it are actually farmed out to sub-phrases like "search the scope", "adjudicate", and "rank". It is notable for being the home of the Which Did You Mean activity, and the peculiar mode the parser is in while waiting for an answer.

    Noun Domain uses "the match list", which is a list of game objects which match the current words the parser is pointing at. The "search the scope" phrase populates this list as the first step in Noun Domain. So right away we have zero or more items in the match list, and we've used up at least one word from the command so far. The rules of Noun Domain state that if exactly one object was matched (and we haven't tried to use more words than there actually are), then Noun Domain is already done: it returns that object. If there isn't a singular match and we haven't used up all the words left, then we better have hit the end of the command; the next grammar token should be '<end of line>' and not '<understood word>' or '<grammar token>' or what-have-you. Otherwise we have a problem: no object matches all the words leftover in the command even though one or more objects matches some of the words -- but Inform's stance is always "understand it all or complain" so a no-match result is returned.

    Anyway, if we either 1) have multiple matches and no more words to disambiguate, or 2) we have one match but there's a connector word (AND, THEN, etc.) after it, we're going to have to think a bit.

    Truth to tell, if there's exactly one match it'll still get returned in short order as "the likely choice", but we do take a detour down here to inform the Clarifying the Parser's Choice activity of this one object should later matters need to know. But never mind that: most of the code from here on down deals with disambiguation.

    If there's at least two matched objects, we have to decide between them if we can. First we find out if they indistinguishable, such as a multitude of otherwise identical coins. (We don't *use* that information here other than to turn off the Clarifying activity.) We call the "adjudicate" phrase to decide on one of the match list's items. (Adjudicate uses lots of pieces of information to make decisions: this "assume all objects indistinguishable" variable, the grammar token ('someone'? 'things inside'?), the Decide Whether All Includes activity, whether A/AN/SOME or THE was used, the location of each item, whether a pronoun stands for any of these items, descriptor words like THAT, HIS, and the special-cased adjectives LIT and UNLIT, and even what was handled very recently.) If it can't make a good decision, it may return null (no idea), "noun domain's bunch of objects" (if the verb OKs multiple objects) "a bunch of objects" (if the verb doesn't), or a single object (if it successfully figured it all out from all that information it has).

    If we have a singular object at this point -- either because Adjudicate found it or Noun Domain itself found it up by those connector words -- we'll return it either before or after informing the Clarifying activity of the object we're deciding on, as appropriate.

    Otherwise we're going to ask the player one of two questions. One question is of the form "what do you want to <verb>" and the other is "which did you mean...". The first is triggered by EXAMINE THE, but only when it isn't followed by connector words (or absolutely anything else), so it's something of a special case. That question is handled by the "incomplete noun under the context" phrase located elsewhere in the source. But the usual case is the hallowed Which Did You Mean activity, which uses some of the information that Adjudicate figured out to pare down the items to list. (For example, Adjudicate groups multiple indisinguishables together so the activity need only say "a coin" once.)

    It's unfortunate that Adjudicate groups things together in such an ad-hoc way, as it makes the default behavior of this activity a bit more complicated than need be, but a special-cased "repeat through the first item of each group" phrase, and a special-case "say the group as a/an/some" phrase, hide this bit. (To write your own activity rule, you'll likely need to use these phrases and hew pretty closely to the code shown here.) The activity only asks "who" when every single item is a person, otherwise "which" is it. It also handles commas and connector manually rather than using a list writer, because, again, it isn't using a proper list, but an ad-hoc list of groups, which Inform has no support for elsewhere in the language.

    Now we get ready to parse the player's answer to this question. We're going to put the answer into the "second parsed command" so we don't overwrite the original command. And that's important, because we're about to re-use the original command. First things first: clear out that second buffer with spaces so it doesn't confuse the later code.

    It's a little-known fact that we can answer this question with words like "all of them" or "both of them" and the parser will respond appropriately. (It only looks at the first word anyway.) And if the appropriate response is "you can only have one" then it will stay in this loop, asking again and again for the player to either enter a word that, when put together with the words from the original command, might solve the issue. If the verb actually allows multiple objects (as evidenced by the grammar token being 'things' or similar), then we move all the objects from the match list to the multiple-object list, and we're done.

    Typically, the player will type in RED or HIS or some other word. Or he might type in a new command. Well, if the command contains a comma, it's likely a BOB, DO THIS sort of thing, so destroy the original command with whatever's freshly typed. Or if the first word is a known verb, destroy the original command with the fresh typing. Otherwise we insert into the original the new word or words.

    Regardless which way that went, we re-parse, reset the important variables, call the After Reading a Command rules so it can do any magic it normally does, and start all over. It's interesting that that rulebook is called alone, as it's supposed to be indivisible from the rest of the Reading a Command activity, and it's possible it'll introduce bugs into an author's work who uses the Before Reading a Command rules to reset variables that the After rules mess with. But so far, any problems from this semi-parsing in the shadow of Which Did You Mean have centered around entering or exiting this quasi-mode.



    Chapter: The Parsing of Tokens

    Section: Will the Real ParseToken Please Stand Up?

    So the parse phrase called another phrase called "the parsing of..." which I had promised did a lot of work. So we go there now to examine this auspicious phrase which holds more secrets of the Inform parsing machinery, and we find this:

        To decide what parser result is the parsing of (y - a grammar token) as (x - token type) if any:
            if the parser's current word position > the word count, decide on parse fails;
            decide on the parsing of y as x at 0 under 'something'.

    How anti-climatic. If we ran out of words, the parse fails. Otherwise... do the same thing? Well, there's another "parsing of..." phrase which it's using, which takes a couple more bits of input than just the grammar token and its type. So we'll go there now -- it's just three lines below onscreen -- and find a phrase that begins with a lot of "save" phrases, calls yet another "parsing of..." phrase, and call a lot of "restore" phrases in the precisely reversed order as the saves.

    Programmer wisdom says not to use global variables. A value-that-varies should always belong to a function ("let x be...") or to objects ("A thing has a text called x.") or whatever. When this advice isn't followed, phrases like the one we're seeing now eventually appear. Let us skip this blight and find "the actual parsing of..."

    Section: I Came Here Looking For A...

    Earlier in this documentation I gave examples of the valid token types.

        '<grammar token>' - like "[things inside]"
        '<understood word>' - like "choose" or "from"
        '<description of values>' - like "[available color]"
        'any <description of objects>' - like "[any room]"
        the first slashed synonym - 'in' in "in/inside/into"

    In "the actual parsing of..." we look at which one the understand-as line gave us, and act appropriately. Some of these are easy: interpret the next word(s) as a number / color / dictionary word / topic understood / general parsing rule, set a variable or run the rule, and return success.

    The ones that don't usually refer to one or more objects, with one or more restrictions. And not all of these are even relevant to Inform 7 anymore. For example, '<adjective>' would have allowed us to write

        Understand "lean on [fixed in place]" as waiting.

    instead of

        Understand "lean on [something fixed in place]" as waiting.

    But it wouldn't work any differently, and the latter is far more flexible, so Inform 7 doesn't explicitly support '<adjective>'. The old code just sits here, a fossil from another age.

    Others are added for Inform 7's benefit. It's easier to say

        Understand "go to [any room]" as going by name.

    ..than it is to create a scope decider rule and set the scope decider variable to it. "Scope decider" being an old feature which can do this and much more, its use isn't obvious to most of us. At least, not as obvious as the word "any" placed in front of a kind.

    Some are allegedly exposed to Inform 7 but with little explanation, such as with '<understand token>' and chapter 25 page 22 of Writing With Inform, "Inform 6 Understand tokens". With no example, nor even the type existing that the rule is supposed to produce, you'd need an understanding of the parser equal to this extension to even begin to explore the feature.

    But for those grammar tokens and grammar token types which don't immediately find their answer and return, we find ourselves on the last line of this phrase, which looks a lot like this phrase's name, and the names of the two phrases before us. Unlike numbers or colors or rules-which-handle-it-all, most of the stuff that comes down here will be actual game objects. And Inform supports more features with objects than it does other things, such as multiple object lists.

    I didn't need to break off the phrase here and call the immediately following, but could have joined them into a single phrase. Indeed, that is what the original code does. But I wanted to emphasize that the only bit of data the two share is just the current grammar token -- 'someone', 'things inside', etc. -- and nothing else.

    Section: The Myriad Ways of Specifying Many Objects

    The very first line of "the parsed token as a.." tells us whether we're expecting (and hence, allowing) a list of objects or not. That's not to say we can ignore any possibility of a list when the action doesn't allow it. A player may give a list where one isn't allowed, and we would need to explicitly respond. But anyway, this is where we parse list-related words like AND, BUT, EXCEPT, ALL, EVERY, and the like.

    Let's initialize some variables.

    "The singular object" will be this phrase's result if there isn't a list.

    "May make inferences" refers to the Clarifying the Parser's Choice activity.

    "Accept multiple objects" remembers whether or not the verb allows a list of objects like THE BOOK, THE CANDLE, AND THE QUILL. Some verbs cannot be used with a multitude.

    "Have many objects in the command" remembers whether or not the player is giving us a list of objects. This is assumed to be no until we find a connective word like AND or BUT, or a comma, or an encompassing word like EVERYTHING. Obviously there's a potential for conflict between this and the "accept multiple objects" command.

    It's worth noting that just because there isn't an AND, BUT, or comma, there still may be a list. Consider THREE COINS, in which case there's three objects, but a single call to Noun Domain will package them up as the returned value "noun domain's bunch of objects." So in the case of THE BOOK, THREE COINS, AND A KEY, there's going to be some merging of lists: the list Noun Domain made for the coins, and the list we're making here in Parse Token for the book, candle, etc.

    "The number of items wanted" is usually "all items are wanted" except in the case of (any) THREE COINS, in which three are wanted even if more are available.

    "Assume AND means add objects" handles the difference between taking THE BOOK AND THE CANDLE versus EVERYTHING EXCEPT THE BOOK AND THE CANDLE. In the latter case, AND means removing things from the list of objects to which the verb will apply.

    Now we start the list by entering the While loop. The body of this loop will read words and match it to a game object. If, at the place where the words for that object end, there is an AND, a comma, or the like, the loop assumes it's a list of objects and acts appropriately. But what about the very first object? There isn't a comma or AND in front of it. Well, we neatly solve that by initializing "the word" to a comma, allowing us to enter the loop for the first object.

    So we're inside the loop. First thing we're going to do is replace any pronoun with what it stands for. Since the "what (a word) stands for" phrase won't change anything if the word isn't a pronoun, we're going to use it unconditionally: set the pronoun variables to nothing, use it, then see if the pronoun variables have been changed. If so, the player really did use a pronoun after all, so act appropriately. There is a case that the word IS a pronoun, but the parser doesn't know what that pronoun stands for, which will bring everything to a screeching halt. There's also the case caused by the pronoun ME not being considered a true pronoun by Inform. (If you've ever used the PRONOUNS out-of-world command in someone's game, notice that "me" and "you" are absent. Inform knows the words, but not as pronouns.) However, ME is always the player, so it isn't a big deal.

    Now we get ready to parse determiner words. Determiners include articles (a/an/the), possessives (my/his/her/its/their), demonstratives (this/that/these/those), quantifiers (all/other), and numbers in the sense of TAKE 3 APPLES (but not in the sense of The Three Stooges). The phrase will also eat the word "of" if at least one determiner was found already, so the phrase as a whole matches ALL OF THE and FIVE OF THE and similar constructions. The phrase sets global variables according to what it found, including storing the number the player used if such appeared.

    Numbers aren't always a determiner, such as a restaurant named Fifty Coins, but we'll assume they are here, and that's usually right, and the parser can backtrack if we were wrong because we also record where the determiners started. The only problem the determiners phrase can have is running out of words to parse.

    Once those are out of the way, we use noun domain to match the player's words to a game object. Assuming Noun Domain doesn't demand a reparse, we look at the grammar token we're expected to match. For 'something preferably held', we know a list isn't allowed, so set the singular object to what noun domain returned. (There's some error-checking just beforehand, to backtrack regarding those determiners if needed, or to try, TryAgain if we already retried the determiners.)

    Otherwise, the grammar token isn't 'something preferably held', so we have a lot of different situations to consider. First up: if neither Noun Domain nor Parse Token have found anything, not even determiners, then we're going to assume some sort of list. Second, if our verb allows a list of objects and we haven't looked at the official multiple-object list, we make a mental note that we should process that in the near future. (Incidentally, we also fix the just-in-case parser error, because a "can't use multiple objects" error would be obviously inappropriate when our verb certainly allows such.)

    Third is an actual test case rather than set-up. If Noun Domain doesn't recognize the player's words, we backtrack, depending upon whether we've already used the "maybe THREE was part of the name and not a descriptor" excuse. Fourth, (after a debugging TRACE statement,) if Noun Domain found several objects (such as THREE COINS, which yielded three indistinguishable objects), we officially set the "have many objects in the command" variable I mentioned a few paragraphs ago, and if it's not the first time Noun Domain has said this, we merge this list of coins (or whatever) with the list of fourteen Dragon Balls (or whatever) that was also in the player's command SIX KNIVES, 14 DRAGON BALLS, THREE COINS, AND ONE FOR THE ROAD.

    Otherwise, we have the usual case of Noun Domain finding a single object. But there's edge cases even here: if Noun Domain assumed an object without using any words, well, that's a failed parse, but we'll try our usual excuse of re-interpreting descriptors as actual names anyway. Otherwise we've actually, finally, successfully parsed something simple, like THE BOOK, whether or not it's embedded in a larger list of things. We ensure the expected grammar token is satisfied -- really, the only one that's picky at this point is 'someone', since most other tokens are appeased in the at-where-or-in-what-are-we-looking-for-stuff phase -- and then put our book (whatever) into where-ever it is the returned object should go: if the whole noun phrase is a single object, it goes in the "singular object" variable, otherwise the multiple-object list is the destination. (And even then, not necessarily adding it, depending on the current definition of AND meaning to include or to exclude.)

    Here, the long if-statement for whether the token is 'something preferably held' (or not) finally ends.

    Now we update the parser's current word position to be at whatever word is behind THE CANDLE (or whatever), and we look at this word to see if it's a connective like AND, BUT, a comma, and so on. If so, does the verb even accept multiple objects? If not, well, the whole command might still be something like TAKE THE CANDLE AND LIGHT IT, so thank goodness for the "process the multiple object list" variable and the "break" statement. (Otherwise, TryAgain.) If the verb OKs multitudes, then words like BUT and EXCEPT change the meaning of later ANDs and commas between inclusive and exclusive behavior, as previously mentioned. Next up, if this is the first time we've found a connective, we'll set "have many objects in the command" and setup the multiple object list with the first object (currently in "the singular object", but quickly remedied). Finally, as a later note to the Clarifying the Parser's Choice of Something, we're going to refrain from inferring an object from scant data, since we might have several objects next to one another and finding where one's name ends and the next begins is hard enough without trying to be all fancy about it and inferring things like a fortuneteller.

    Here, the even-longer 'while' loop that continues to spin as long as connectives are found, ends. Since that last word was *not* an AND, BUT, or comma, we back up the parser's current word position to point to this mysterious next word once more, and we'll leave it there for the rest of Parse Token. So now that we've finished the compound noun phrase, we get to the business of returning what we've found to the calling phrase. The "resulting objects" is just a message to the caller to look in the multiple-object list for our results. "The singular object" is the variable that holds the actual BOOK (or whatever) we found for the simple case of no lists at all were involved. We do do a final check in case multitudes were wanted but we didn't find any. "What the token fails as" is a phrase which appears a few lines below, which if it decided it mis-parsed a singular as a plural (which in turn meant it assumed multitudes were meant even if they weren't -- perhaps the player wanted only one Three Stooge) it'll TryAgain, otherwise, to quote Yoda: failed it has.