I6 Template Layer

Inform 7 6M62ContentsIntroductionFunction IndexRules Index

Parser.i6t

Parser contents

Grammar Line Variables.

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

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

29Global best_etype; ! Preferred error number so far 30Global nextbest_etype; ! Preferred one, if ASKSCOPE_PE disallowed 31 32Global parser_inflection; ! A property (usually "name") to find object names in 33 34Array pattern --> 32; ! For the current pattern match 35Global pcount; ! and a marker within it 36Array pattern2 --> 32; ! And another, which stores the best match 37Global pcount2; ! so far 38 39Array line_ttype-->32; ! For storing an analysed grammar line 40Array line_tdata-->32; 41Array line_token-->32; 42 43Global nsns; ! Number of special_numbers entered so far 44 45Global params_wanted; ! Number of parameters needed (which may change in parsing) 46 47Global inferfrom; ! The point from which the rest of the command must be inferred 48Global inferword; ! And the preposition inferred 49Global dont_infer; ! Another dull flag 50 51Global cobj_flag = 0; 52 53Global oops_from; ! The "first mistake" word number 54Global saved_oops; ! Used in working this out 55Array oops_workspace -> 64; ! Used temporarily by "oops" routine 56 57Global held_back_mode; ! Flag: is there some input from last time 58Global hb_wn; ! left over? (And a save value for wn.) 59                                    ! (Used for full stops and "then".) 60 61Global usual_grammar_after; ! Point from which usual grammar is parsed (it may vary from 62                                    ! the above if user's routines match multi-word verbs)

Grammar Token Variables.

More globals, but dealing at the level of individual tokens now.

68Constant PATTERN_NULL = $ffff; ! Entry for a token producing no text 69 70Global found_ttype; ! Used to break up tokens into type 71Global found_tdata; ! and data (by AnalyseToken) 72Global token_filter; ! For noun filtering by user routines 73 74Global length_of_noun; ! Set by NounDomain to no of words in noun 75 76Global lookahead; ! The token after the one now being matched 77 78Global multi_mode; ! Multiple mode 79Global multi_wanted; ! Number of things needed in multitude 80Global multi_had; ! Number of things actually found 81Global multi_context; ! What token the multi-obj was accepted for 82 83Global indef_mode; ! "Indefinite" mode - ie, "take a brick" 84                                    ! is in this mode 85Global indef_type; ! Bit-map holding types of specification 86Global indef_wanted; ! Number of items wanted (INDEF_ALL_WANTED for all) 87Constant INDEF_ALL_WANTED = 32767; 88Global indef_guess_p; ! Plural-guessing flag 89Global indef_owner; ! Object which must hold these items 90Global indef_cases; ! Possible gender and numbers of them 91Global indef_possambig; ! Has a possibly dangerous assumption 92                                    ! been made about meaning of a descriptor? 93Global indef_nspec_at; ! Word at which a number like "two" was parsed 94                                    ! (for backtracking) 95Global allow_plurals; ! Whether plurals presently allowed or not 96 97Global take_all_rule; ! Slightly different rules apply to "take all" than other uses 98                                    ! of multiple objects, to make adjudication produce more 99                                    ! pragmatically useful results 100                                    ! (Not a flag: possible values 0, 1, 2) 101 102Global dict_flags_of_noun; ! Of the noun currently being parsed 103                                    ! (a bitmap in #dict_par1 format) 104Global pronoun__word; ! Saved value 105Global pronoun__obj; ! Saved value 106 107Constant comma_word = 'comma,'; ! An "untypeable word" used to substitute 108                                    ! for commas in parse buffers

Match List Variables.

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

117Array match_list --> MATCH_LIST_WORDS; ! An array of matched objects so far 118Array match_classes --> MATCH_LIST_WORDS; ! An array of equivalence classes for them 119Array match_scores --> MATCH_LIST_WORDS; ! An array of match scores for them 120Global number_matched; ! How many items in it? (0 means none) 121Global number_of_classes; ! How many equivalence classes? 122Global match_length; ! How many words long are these matches? 123Global match_from; ! At what word of the input do they begin?

Words.

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

The actual text of each word is stored as a sequence of ZSCII values in a -> (byte) array, with address WordAddress(x) and length WordLength(x).

We picture the command as a stream of words to be read one at a time, with the global variable wn being the "current word" marker. NextWord, which takes no arguments, returns: (a) 0 if the word at wn is unrecognised by the dictionary or wn is out of range, (b) comma_word if the word was a comma, (c) THEN1__WD if it was a full stop (because of the Infocom tradition that a full stop abbreviates for the word "then": e.g., TAKE BOX. EAST was read as two commands in succession), (d) or the dictionary address if the word was recognised.

The current word marker wn is always advanced.

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

152#Ifdef TARGET_ZCODE; 153[ WordCount; return parse->1; ]; 154[ WordAddress wordnum; return buffer + parse->(wordnum*4+1); ]; 155[ WordLength wordnum; return parse->(wordnum*4); ]; 156#Ifnot; 157[ WordCount; return parse-->0; ]; 158[ WordAddress wordnum; return buffer + parse-->(wordnum*3); ]; 159[ WordLength wordnum; return parse-->(wordnum*3-1); ]; 160#Endif; 161 162[ WordFrom w p i j wc; 163    #Ifdef TARGET_ZCODE; wc = p->1; i = w*2-1; 164    #Ifnot; wc = p-->0; i = w*3-2; #Endif; 165    if ((w < 1) || (w > wc)) return 0; 166    j = p-->i; 167    if (j == ',//') j = comma_word; 168    if (j == './/') j = THEN1__WD; 169    return j; 170]; 171 172[ NextWord i j wc; 173    #Ifdef TARGET_ZCODE; wc = parse->1; i = wn*2-1; 174    #Ifnot; wc = parse-->0; i = wn*3-2; #Endif; 175    wn++; 176    if ((wn < 2) || (wn > wc+1)) return 0; 177    j = parse-->i; 178    if (j == ',//') j = comma_word; 179    if (j == './/') j = THEN1__WD; 180    return j; 181]; 182 183[ NextWordStopped wc; 184    #Ifdef TARGET_ZCODE; wc = parse->1; #Ifnot; wc = parse-->0; #Endif; 185    if ((wn < 1) || (wn > wc)) { wn++; return -1; } 186    return NextWord(); 187];

Snippets.

Although the idea is arguably implicit in I6, the formal concept of "snippet" is new in I7. A snippet is a value which represents a word range in the command most recently typed by the player. These words number consecutively upwards from 1, as noted above. The correspondence between (w1, w2), the word range, and V, the number used to represent it as an I6 value, is:

V = 100w1 + (w2-w1+1)

so that the remainder mod 100 is the number of words in the range. We require that 1 ≤ w1 ≤ w2 ≤ N, where N is the number of words in the current player's command. The entire command is therefore represented by:

C = 100 + N

203[ PrintSnippet snip from to i w1 w2; 204    w1 = snip/100; w2 = w1 + (snip%100) - 1; 205    if ((w2<w1) || (w1<1) || (w2>WordCount())) { 206        if ((w1 == 1) && (w2 == 0)) rfalse; 207        return RunTimeProblem(RTP_SAYINVALIDSNIPPET, w1, w2); 208    } 209    from = WordAddress(w1); to = WordAddress(w2) + WordLength(w2) - 1; 210    for (i=from: i<=to: i++) print (char) i->0; 211]; 212 213[ SpliceSnippet snip t i w1 w2 nextw at endsnippet newlen; 214    w1 = snip/100; w2 = w1 + (snip%100) - 1; 215    if ((w2<w1) || (w1<1)) { 216        if ((w1 == 1) && (w2 == 0)) return; 217        return RunTimeProblem(RTP_SPLICEINVALIDSNIPPET, w1, w2); 218    } 219    @push say__p; @push say__pc; 220    nextw = w2 + 1; 221    at = WordAddress(w1) - buffer; 222    if (nextw <= WordCount()) endsnippet = 100*nextw + (WordCount() - nextw + 1); 223    buffer2-->0 = 120; 224    newlen = VM_PrintToBuffer(buffer2, 120, SpliceSnippet__TextPrinter, t, endsnippet); 225    for (i=0: (i<newlen) && (at+i<120): i++) buffer->(at+i) = buffer2->(WORDSIZE+i); 226    #Ifdef TARGET_ZCODE; buffer->1 = at+i; #ifnot; buffer-->0 = at+i; #endif; 227    for (:at+i<120:i++) buffer->(at+i) = ' '; 228    VM_Tokenise(buffer, parse); 229    players_command = 100 + WordCount(); 230    @pull say__pc; @pull say__p; 231]; 232 233[ SpliceSnippet__TextPrinter t endsnippet; 234    TEXT_TY_Say(t); 235    if (endsnippet) { print " "; PrintSnippet(endsnippet); } 236]; 237 238[ SnippetIncludes test snippet w1 w2 wlen i j; 239    w1 = snippet/100; w2 = w1 + (snippet%100) - 1; 240    if ((w2<w1) || (w1<1)) { 241        if ((w1 == 1) && (w2 == 0)) rfalse; 242        return RunTimeProblem(RTP_INCLUDEINVALIDSNIPPET, w1, w2); 243    } 244    if (metaclass(test) == Routine) { 245        wlen = snippet%100; 246        for (i=w1, j=wlen: j>0: i++, j--) { 247            if (((test)(i, 0)) ~= GPR_FAIL) return i*100+wn-i; 248        } 249    } 250    rfalse; 251]; 252 253[ SnippetMatches snippet topic_gpr rv; 254    wn=1; 255    if (topic_gpr == 0) rfalse; 256    if (metaclass(topic_gpr) == Routine) { 257        rv = (topic_gpr)(snippet/100, snippet%100); 258        if (rv ~= GPR_FAIL) rtrue; 259        rfalse; 260    } 261    RunTimeProblem(RTP_BADTOPIC); 262    rfalse; 263];

Unpacking Grammar Lines.

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

272[ UnpackGrammarLine line_address i size; 273    for (i=0 : i<32 : i++) { 274        line_token-->i = ENDIT_TOKEN; 275        line_ttype-->i = ELEMENTARY_TT; 276        line_tdata-->i = ENDIT_TOKEN; 277    } 278#Ifdef TARGET_ZCODE; 279    action_to_be = 256*(line_address->0) + line_address->1; 280    action_reversed = ((action_to_be & $400) ~= 0); 281    action_to_be = action_to_be & $3ff; 282    line_address--; 283    size = 3; 284#Ifnot; ! GLULX 285    @aloads line_address 0 action_to_be; 286    action_reversed = (((line_address->2) & 1) ~= 0); 287    line_address = line_address - 2; 288    size = 5; 289#Endif; 290    params_wanted = 0; 291    for (i=0 : : i++) { 292        line_address = line_address + size; 293        if (line_address->0 == ENDIT_TOKEN) break; 294        line_token-->i = line_address; 295        AnalyseToken(line_address); 296        if (found_ttype ~= PREPOSITION_TT) params_wanted++; 297        line_ttype-->i = found_ttype; 298        line_tdata-->i = found_tdata; 299    } 300    return line_address + 1; 301]; 302 303[ AnalyseToken token; 304    if (token == ENDIT_TOKEN) { 305        found_ttype = ELEMENTARY_TT; 306        found_tdata = ENDIT_TOKEN; 307        return; 308    } 309    found_ttype = (token->0) & $$1111; 310    found_tdata = (token+1)-->0; 311];

Extracting Verb Numbers.

A long tale of woe lies behind the following. Infocom games stored verb numbers in a single byte in dictionary entries, but they did so counting downwards, so that verb number 0 was stored as 255, 1 as 254, and so on. Inform followed suit so that debugging of Inform 1 could be aided by using the then-available tools for dumping dictionaries from Infocom story files; by using the Infocom format for dictionary tables, Inform's life was easier.

But there was an implicit restriction there of 255 distinct verbs (not 256 since not all words were verbs). When Glulx raised almost all of the Z-machine limits, it made space for 65535 verbs instead of 255, but it appears that nobody remembered to implement this in I6-for-Glulx and the Glulx form of the I6 library. This was only put right in March 2009, and the following routine was added to concentrate lookups of this field in one place.

329[ DictionaryWordToVerbNum dword verbnum; 330#Ifdef TARGET_ZCODE; 331    verbnum = $ff-(dword->#dict_par2); 332#Ifnot; ! GLULX 333    dword = dword + #dict_par2 - 1; 334    @aloads dword 0 verbnum; 335    verbnum = $ffff-verbnum; 336#Endif; 337    return verbnum; 338];

Keyboard Primitive.

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

348[ KeyboardPrimitive a_buffer a_table; 349#Ifdef DEBUG; #Iftrue ({-value:NUMBER_CREATED(test_scenario)} > 0); 350    return TestKeyboardPrimitive(a_buffer, a_table); 351#Endif; #Endif; 352    return VM_ReadKeyboard(a_buffer, a_table); 353];

Reading the Command.

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

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

The return value is the number of words typed.

376[ Keyboard a_buffer a_table nw i w w2 x1 x2; 377    sline1 = score; sline2 = turns; 378 379    while (true) { 380        ! Save the start of the buffer, in case "oops" needs to restore it 381        for (i=0 : i<64 : i++) oops_workspace->i = a_buffer->i; 382     383        ! In case of an array entry corruption that shouldn't happen, but would be 384        ! disastrous if it did: 385        #Ifdef TARGET_ZCODE; 386        a_buffer->0 = INPUT_BUFFER_LEN; 387        a_table->0 = 15; ! Allow to split input into this many words 388        #Endif; ! TARGET_ 389     390        ! Print the prompt, and read in the words and dictionary addresses 391        PrintPrompt(); 392        DrawStatusLine(); 393        KeyboardPrimitive(a_buffer, a_table); 394     395        ! Set nw to the number of words 396        #Ifdef TARGET_ZCODE; nw = a_table->1; #Ifnot; nw = a_table-->0; #Endif; 397     398        ! If the line was blank, get a fresh line 399        if (nw == 0) { 400            @push etype; etype = BLANKLINE_PE; 401            players_command = 100; 402            BeginActivity(PRINTING_A_PARSER_ERROR_ACT); 403            if (ForActivity(PRINTING_A_PARSER_ERROR_ACT) == false) { 404                PARSER_ERROR_INTERNAL_RM('X', noun); new_line; 405            } 406            EndActivity(PRINTING_A_PARSER_ERROR_ACT); 407            @pull etype; 408            continue; 409        } 410     411        ! Unless the opening word was OOPS, return 412        ! Conveniently, a_table-->1 is the first word on both the Z-machine and Glulx 413     414        w = a_table-->1; 415        if (w == OOPS1__WD or OOPS2__WD or OOPS3__WD) { 416            if (oops_from == 0) { PARSER_COMMAND_INTERNAL_RM('A'); new_line; continue; } 417            if (nw == 1) { PARSER_COMMAND_INTERNAL_RM('B'); new_line; continue; } 418            if (nw > 2) { PARSER_COMMAND_INTERNAL_RM('C'); new_line; continue; } 419         420            ! So now we know: there was a previous mistake, and the player has 421            ! attempted to correct a single word of it. 422         423            for (i=0 : i<INPUT_BUFFER_LEN : i++) buffer2->i = a_buffer->i; 424            #Ifdef TARGET_ZCODE; 425            x1 = a_table->9; ! Start of word following "oops" 426            x2 = a_table->8; ! Length of word following "oops" 427            #Ifnot; ! TARGET_GLULX 428            x1 = a_table-->6; ! Start of word following "oops" 429            x2 = a_table-->5; ! Length of word following "oops" 430            #Endif; ! TARGET_ 431         432            ! Repair the buffer to the text that was in it before the "oops" 433            ! was typed: 434            for (i=0 : i<64 : i++) a_buffer->i = oops_workspace->i; 435            VM_Tokenise(a_buffer,a_table); 436         437            ! Work out the position in the buffer of the word to be corrected: 438            #Ifdef TARGET_ZCODE; 439            w = a_table->(4*oops_from + 1); ! Start of word to go 440            w2 = a_table->(4*oops_from); ! Length of word to go 441            #Ifnot; ! TARGET_GLULX 442            w = a_table-->(3*oops_from); ! Start of word to go 443            w2 = a_table-->(3*oops_from - 1); ! Length of word to go 444            #Endif; ! TARGET_ 445         446            ! Write spaces over the word to be corrected: 447            for (i=0 : i<w2 : i++) a_buffer->(i+w) = ' '; 448         449            if (w2 < x2) { 450                ! If the replacement is longer than the original, move up... 451                for (i=INPUT_BUFFER_LEN-1 : i>=w+x2 : i--) 452                    a_buffer->i = a_buffer->(i-x2+w2); 453         454                ! ...increasing buffer size accordingly. 455                #Ifdef TARGET_ZCODE; 456                a_buffer->1 = (a_buffer->1) + (x2-w2); 457                #Ifnot; ! TARGET_GLULX 458                a_buffer-->0 = (a_buffer-->0) + (x2-w2); 459                #Endif; ! TARGET_ 460            } 461         462            ! Write the correction in: 463            for (i=0 : i<x2 : i++) a_buffer->(i+w) = buffer2->(i+x1); 464         465            VM_Tokenise(a_buffer, a_table); 466            #Ifdef TARGET_ZCODE; nw = a_table->1; #Ifnot; nw = a_table-->0; #Endif; 467         468            return nw; 469        } 470 471        ! Undo handling 472     473        if ((w == UNDO1__WD or UNDO2__WD or UNDO3__WD) && (nw==1)) { 474            Perform_Undo(); 475            continue; 476        } 477        i = VM_Save_Undo(); 478        #ifdef PREVENT_UNDO; undo_flag = 0; #endif; 479        #ifndef PREVENT_UNDO; undo_flag = 2; #endif; 480        if (i == -1) undo_flag = 0; 481        if (i == 0) undo_flag = 1; 482        if (i == 2) { 483            VM_RestoreWindowColours(); 484            VM_Style(SUBHEADER_VMSTY); 485            SL_Location(); print "^"; 486            ! print (name) location, "^"; 487            VM_Style(NORMAL_VMSTY); 488            IMMEDIATELY_UNDO_RM('E'); new_line; 489            continue; 490        } 491        return nw; 492    } 493];

Parser Proper.

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

(A) Get the input, do OOPS and AGAIN (B) Is it a direction, and so an implicit GO? If so go to (K) (C) Is anyone being addressed? (D) Get the command verb: try all the syntax lines for that verb (E) Break down a syntax line into analysed tokens (F) Look ahead for advance warning for multiexcept/multiinside (G) Parse each token in turn (calling ParseToken to do most of the work) (H) Cheaply parse otherwise unrecognised conversation and return (I) Print best possible error message (J) Retry the whole lot (K) Last thing: check for THEN and further instructions(s), return.

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

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

520[ Parser__parse 521    syntax line num_lines line_address i j k token l m inferred_go; 522    cobj_flag = 0; 523    parser_results-->ACTION_PRES = 0; 524    parser_results-->NO_INPS_PRES = 0; 525    parser_results-->INP1_PRES = 0; 526    parser_results-->INP2_PRES = 0; 527    meta = false;

Parser Letter A.

Get the input, do OOPS and AGAIN.

533    if (held_back_mode) { 534        held_back_mode = false; wn = hb_wn; 535        if (verb_wordnum > 0) i = WordAddress(verb_wordnum); else i = WordAddress(1); 536        j = WordAddress(wn); 537        if (i<=j) for (: i<j : i++) i->0 = ' '; 538        i = NextWord(); 539        if (i == AGAIN1__WD or AGAIN2__WD or AGAIN3__WD) { 540            ! Delete the words "then again" from the again buffer, 541            ! in which we have just realised that it must occur: 542            ! prevents an infinite loop on "i. again" 543 544            i = WordAddress(wn-2)-buffer; 545            if (wn > num_words) j = INPUT_BUFFER_LEN-1; 546            else j = WordAddress(wn)-buffer; 547            for (: i<j : i++) buffer3->i = ' '; 548        } 549 550        VM_Tokenise(buffer, parse); 551        jump ReParse; 552    } 553 554  .ReType; 555 556    cobj_flag = 0; 557    actors_location = ScopeCeiling(player); 558    BeginActivity(READING_A_COMMAND_ACT); if (ForActivity(READING_A_COMMAND_ACT)==false) { 559        Keyboard(buffer,parse); 560        num_words = WordCount(); players_command = 100 + num_words; 561    } if (EndActivity(READING_A_COMMAND_ACT)) jump ReType; 562 563  .ReParse; 564 565    parser_inflection = name; 566 567    ! Initially assume the command is aimed at the player, and the verb 568    ! is the first word 569 570    num_words = WordCount(); players_command = 100 + num_words; 571    wn = 1; inferred_go = false; 572 573    #Ifdef LanguageToInformese; 574    LanguageToInformese(); 575    ! Re-tokenise: 576    VM_Tokenise(buffer,parse); 577    #Endif; ! LanguageToInformese 578 579    num_words = WordCount(); players_command = 100 + num_words; 580 581    k=0; 582    #Ifdef DEBUG; 583    if (parser_trace >= 2) { 584        print "[ "; 585        for (i=0 : i<num_words : i++) { 586 587            #Ifdef TARGET_ZCODE; 588            j = parse-->(i*2 + 1); 589            #Ifnot; ! TARGET_GLULX 590            j = parse-->(i*3 + 1); 591            #Endif; ! TARGET_ 592            k = WordAddress(i+1); 593            l = WordLength(i+1); 594            print "~"; for (m=0 : m<l : m++) print (char) k->m; print "~ "; 595 596            if (j == 0) print "?"; 597            else { 598                #Ifdef TARGET_ZCODE; 599                if (UnsignedCompare(j, HDR_DICTIONARY-->0) >= 0 && 600                    UnsignedCompare(j, HDR_HIGHMEMORY-->0) < 0) 601                     print (address) j; 602                else print j; 603                #Ifnot; ! TARGET_GLULX 604                if (j->0 == $60) print (address) j; 605                else print j; 606                #Endif; ! TARGET_ 607            } 608            if (i ~= num_words-1) print " / "; 609        } 610        print " ]^"; 611    } 612    #Endif; ! DEBUG 613    verb_wordnum = 1; 614    actor = player; 615    actors_location = ScopeCeiling(player); 616    usual_grammar_after = 0; 617 618  .AlmostReParse; 619 620    scope_token = 0; 621    action_to_be = NULL; 622 623    ! Begin from what we currently think is the verb word 624 625  .BeginCommand; 626 627    wn = verb_wordnum; 628    verb_word = NextWordStopped(); 629 630    ! If there's no input here, we must have something like "person,". 631 632    if (verb_word == -1) { 633        best_etype = STUCK_PE; jump GiveError; 634    } 635    if (verb_word == comma_word) { 636        best_etype = COMMABEGIN_PE; jump GiveError; 637    } 638 639    ! Now try for "again" or "g", which are special cases: don't allow "again" if nothing 640    ! has previously been typed; simply copy the previous text across 641 642    if (verb_word == AGAIN2__WD or AGAIN3__WD) verb_word = AGAIN1__WD; 643    if (verb_word == AGAIN1__WD) { 644        if (actor ~= player) { 645            best_etype = ANIMAAGAIN_PE; 646            jump GiveError; 647        } 648        #Ifdef TARGET_ZCODE; 649        if (buffer3->1 == 0) { 650            PARSER_COMMAND_INTERNAL_RM('D'); new_line; 651            jump ReType; 652        } 653        #Ifnot; ! TARGET_GLULX 654        if (buffer3-->0 == 0) { 655            PARSER_COMMAND_INTERNAL_RM('D'); new_line; 656            jump ReType; 657        } 658        #Endif; ! TARGET_ 659        for (i=0 : i<INPUT_BUFFER_LEN : i++) buffer->i = buffer3->i; 660        VM_Tokenise(buffer,parse); 661        num_words = WordCount(); players_command = 100 + num_words; 662        jump ReParse; 663    } 664 665    ! Save the present input in case of an "again" next time 666 667    if (verb_word ~= AGAIN1__WD) 668        for (i=0 : i<INPUT_BUFFER_LEN : i++) buffer3->i = buffer->i; 669 670    if (usual_grammar_after == 0) { 671        j = verb_wordnum; 672        i = RunRoutines(actor, grammar); 673        #Ifdef DEBUG; 674        if (parser_trace >= 2 && actor.grammar ~= 0 or NULL) 675            print " [Grammar property returned ", i, "]^"; 676        #Endif; ! DEBUG 677 678        if ((i ~= 0 or 1) && (VM_InvalidDictionaryAddress(i))) { 679            usual_grammar_after = verb_wordnum; i=-i; 680        } 681 682        if (i == 1) { 683            parser_results-->ACTION_PRES = action; 684            parser_results-->NO_INPS_PRES = 0; 685            parser_results-->INP1_PRES = noun; 686            parser_results-->INP2_PRES = second; 687            if (noun) parser_results-->NO_INPS_PRES = 1; 688            if (second) parser_results-->NO_INPS_PRES = 2; 689            rtrue; 690        } 691        if (i ~= 0) { verb_word = i; wn--; verb_wordnum--; } 692        else { wn = verb_wordnum; verb_word = NextWord(); } 693    } 694    else usual_grammar_after = 0;

Parser Letter B.

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

700    #Ifdef LanguageIsVerb; 701    if (verb_word == 0) { 702        i = wn; verb_word = LanguageIsVerb(buffer, parse, verb_wordnum); 703        wn = i; 704    } 705    #Endif; ! LanguageIsVerb 706 707    ! If the first word is not listed as a verb, it must be a direction 708    ! or the name of someone to talk to 709 710    if (verb_word == 0 || ((verb_word->#dict_par1) & 1) == 0) { 711 712        ! So is the first word an object contained in the special object "compass" 713        ! (i.e., a direction)? This needs use of NounDomain, a routine which 714        ! does the object matching, returning the object number, or 0 if none found, 715        ! or REPARSE_CODE if it has restructured the parse table so the whole parse 716        ! must be begun again... 717 718        wn = verb_wordnum; indef_mode = false; token_filter = 0; parameters = 0; 719        @push actor; @push action; @push action_to_be; 720        actor = player; meta = false; action = ##Go; action_to_be = ##Go; 721        l = NounDomain(compass, 0, 0); 722        @pull action_to_be; @pull action; @pull actor; 723        if (l == REPARSE_CODE) jump ReParse; 724 725        ! If it is a direction, send back the results: 726        ! action=GoSub, no of arguments=1, argument 1=the direction. 727 728        if ((l~=0) && (l ofclass K3_direction)) { 729            parser_results-->ACTION_PRES = ##Go; 730            parser_results-->NO_INPS_PRES = 1; 731            parser_results-->INP1_PRES = l; 732            inferred_go = true; 733            jump LookForMore; 734        } 735 736    } ! end of first-word-not-a-verb

Parser Letter C.

Is anyone being addressed?

742    ! Only check for a comma (a "someone, do something" command) if we are 743    ! not already in the middle of one. (This simplification stops us from 744    ! worrying about "robot, wizard, you are an idiot", telling the robot to 745    ! tell the wizard that she is an idiot.) 746     747    if (actor == player) { 748        for (j=2 : j<=num_words : j++) { 749            i=NextWord(); 750            if (i == comma_word) jump Conversation; 751        } 752    } 753    jump NotConversation; 754     755    ! NextWord nudges the word number wn on by one each time, so we've now 756    ! advanced past a comma. (A comma is a word all on its own in the table.) 757     758    .Conversation; 759     760    j = wn - 1; 761     762    ! Use NounDomain (in the context of "animate creature") to see if the 763    ! words make sense as the name of someone held or nearby 764     765    wn = 1; lookahead = HELD_TOKEN; 766    scope_reason = TALKING_REASON; 767    l = NounDomain(player,actors_location,6); 768    scope_reason = PARSING_REASON; 769    if (l == REPARSE_CODE) jump ReParse; 770    if (l == 0) { 771        if (verb_word && ((verb_word->#dict_par1) & 1)) jump NotConversation; 772        best_etype = MISSINGPERSON_PE; jump GiveError; 773    } 774     775    .Conversation2; 776     777    ! The object addressed must at least be "talkable" if not actually "animate" 778    ! (the distinction allows, for instance, a microphone to be spoken to, 779    ! without the parser thinking that the microphone is human). 780     781    if (l hasnt animate && l hasnt talkable) { 782         best_etype = ANIMALISTEN_PE; noun = l; jump GiveError; 783    } 784     785    ! Check that there aren't any mystery words between the end of the person's 786    ! name and the comma (eg, throw out "dwarf sdfgsdgs, go north"). 787     788    if (wn ~= j) { 789        if (verb_word && ((verb_word->#dict_par1) & 1)) jump NotConversation; 790        best_etype = TOTALK_PE; jump GiveError; 791    } 792     793    ! The player has now successfully named someone. Adjust "him", "her", "it": 794     795    PronounNotice(l); 796     797    ! Set the global variable "actor", adjust the number of the first word, 798    ! and begin parsing again from there. 799     800    verb_wordnum = j + 1; 801     802    ! Stop things like "me, again": 803     804    if (l == player) { 805        wn = verb_wordnum; 806        if (NextWordStopped() == AGAIN1__WD or AGAIN2__WD or AGAIN3__WD) { 807            best_etype = ANIMAAGAIN_PE; 808            jump GiveError; 809        } 810    } 811     812    actor = l; 813    actors_location = ScopeCeiling(l); 814    #Ifdef DEBUG; 815    if (parser_trace >= 1) 816        print "[Actor is ", (the) actor, " in ", (name) actors_location, "]^"; 817    #Endif; ! DEBUG 818    jump BeginCommand;

Parser Letter D.

Get the verb: try all the syntax lines for that verb.

824    .NotConversation; 825    if (verb_word == 0 || ((verb_word->#dict_par1) & 1) == 0) { 826        verb_word = UnknownVerb(verb_word); 827        if (verb_word ~= 0) jump VerbAccepted; 828        best_etype = VERB_PE; 829        jump GiveError; 830    } 831    .VerbAccepted; 832 833    ! We now definitely have a verb, not a direction, whether we got here by the 834    ! "take ..." or "person, take ..." method. Get the meta flag for this verb: 835 836    meta = ((verb_word->#dict_par1) & 2)/2; 837 838    ! You can't order other people to "full score" for you, and so on... 839 840    if (meta == 1 && actor ~= player) { 841        best_etype = VERB_PE; 842        meta = 0; 843        jump GiveError; 844    } 845 846    ! Now let i be the corresponding verb number... 847 848    i = DictionaryWordToVerbNum(verb_word); 849 850    ! ...then look up the i-th entry in the verb table, whose address is at word 851    ! 7 in the Z-machine (in the header), so as to get the address of the syntax 852    ! table for the given verb... 853 854    #Ifdef TARGET_ZCODE; 855    syntax = (HDR_STATICMEMORY-->0)-->i; 856    #Ifnot; ! TARGET_GLULX 857    syntax = (#grammar_table)-->(i+1); 858    #Endif; ! TARGET_ 859 860    ! ...and then see how many lines (ie, different patterns corresponding to the 861    ! same verb) are stored in the parse table... 862 863    num_lines = (syntax->0) - 1; 864 865    ! ...and now go through them all, one by one. 866    ! To prevent pronoun_word 0 being misunderstood, 867 868    pronoun_word = NULL; pronoun_obj = NULL; 869 870    #Ifdef DEBUG; 871    if (parser_trace >= 1) 872        print "[Parsing for the verb ", (address) verb_word, " (", num_lines+1, " lines)]^"; 873    #Endif; ! DEBUG 874 875    best_etype = STUCK_PE; nextbest_etype = STUCK_PE; 876    multiflag = false; 877 878    ! "best_etype" is the current failure-to-match error - it is by default 879    ! the least informative one, "don't understand that sentence". 880    ! "nextbest_etype" remembers the best alternative to having to ask a 881    ! scope token for an error message (i.e., the best not counting ASKSCOPE_PE). 882    ! multiflag is used here to prevent inappropriate MULTI_PE errors 883    ! in addition to its unrelated duties passing information to action routines

Parser Letter E.

Break down a syntax line into analysed tokens.

889    line_address = syntax + 1; 890 891    for (line=0 : line<=num_lines : line++) { 892 893        ! Unpack the syntax line from Inform format into three arrays; ensure that 894        ! the sequence of tokens ends in an ENDIT_TOKEN. 895 896        line_address = UnpackGrammarLine(line_address); 897 898        #Ifdef DEBUG; 899        if (parser_trace >= 1) { 900            if (parser_trace >= 2) new_line; 901            print "[line ", line; DebugGrammarLine(); 902            print "]^"; 903        } 904        #Endif; ! DEBUG 905 906        ! We aren't in "not holding" or inferring modes, and haven't entered 907        ! any parameters on the line yet, or any special numbers; the multiple 908        ! object is still empty. 909 910        inferfrom = 0; 911        parameters = 0; 912        nsns = 0; special_word = 0; 913        multiple_object-->0 = 0; 914        multi_context = 0; 915        etype = STUCK_PE; 916 917        ! Put the word marker back to just after the verb 918 919        wn = verb_wordnum+1;

Parser Letter F.

Look ahead for advance warning for multiexcept/multiinside.

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

multiexcept <one or more prepositions> noun

and similarly for multiinside.

935        advance_warning = -1; indef_mode = false; 936        for (i=0,m=false,pcount=0 : line_token-->pcount ~= ENDIT_TOKEN : pcount++) { 937            scope_token = 0; 938 939            if (line_ttype-->pcount ~= PREPOSITION_TT) i++; 940 941            if (line_ttype-->pcount == ELEMENTARY_TT) { 942                if (line_tdata-->pcount == MULTI_TOKEN) m = true; 943                if (line_tdata-->pcount == MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN && i == 1) { 944                    ! First non-preposition is "multiexcept" or 945                    ! "multiinside", so look ahead. 946 947                    #Ifdef DEBUG; 948                    if (parser_trace >= 2) print " [Trying look-ahead]^"; 949                    #Endif; ! DEBUG 950 951                    ! We need this to be followed by 1 or more prepositions. 952 953                    pcount++; 954                    if (line_ttype-->pcount == PREPOSITION_TT) { 955                        ! skip ahead to a preposition word in the input 956                        do { 957                            l = NextWord(); 958                        } until ((wn > num_words) || 959                                 (l && (l->#dict_par1) & 8 ~= 0)); 960                         961                        if (wn > num_words) { 962                            #Ifdef DEBUG; 963                            if (parser_trace >= 2) 964                                print " [Look-ahead aborted: prepositions missing]^"; 965                            #Endif; 966                            jump EmptyLine; 967                        } 968                         969                        do { 970                            if (PrepositionChain(l, pcount) ~= -1) { 971                                ! advance past the chain 972                                if ((line_token-->pcount)->0 & $20 ~= 0) { 973                                    pcount++; 974                                    while ((line_token-->pcount ~= ENDIT_TOKEN) && 975                                           ((line_token-->pcount)->0 & $10 ~= 0)) 976                                        pcount++; 977                                } else { 978                                    pcount++; 979                                } 980                            } else { 981                                ! try to find another preposition word 982                                do { 983                                    l = NextWord(); 984                                } until ((wn >= num_words) || 985                                         (l && (l->#dict_par1) & 8 ~= 0)); 986                                 987                                if (l && (l->#dict_par1) & 8) continue; 988                                 989                                ! lookahead failed 990                                #Ifdef DEBUG; 991                                if (parser_trace >= 2) 992                                    print " [Look-ahead aborted: prepositions dont match]^"; 993                                #endif; 994                                jump LineFailed; 995                            } 996                            if (wn <= num_words) l = NextWord(); 997                        } until (line_ttype-->pcount ~= PREPOSITION_TT); 998                         999                        .EmptyLine; 1000                        ! put back the non-preposition we just read 1001                        wn--; 1002 1003                        if ((line_ttype-->pcount == ELEMENTARY_TT) && 1004                            (line_tdata-->pcount == NOUN_TOKEN)) { 1005                            l = Descriptors(); ! skip past THE etc 1006                            if (l~=0) etype=l; ! don't allow multiple objects 1007                            k = parser_results-->INP1_PRES; @push k; @push parameters; 1008                            parameters = 1; parser_results-->INP1_PRES = 0; 1009                            l = NounDomain(actors_location, actor, NOUN_TOKEN, true); 1010                            @pull parameters; @pull k; parser_results-->INP1_PRES = k; 1011                            #Ifdef DEBUG; 1012                            if (parser_trace >= 2) { 1013                                print " [Advanced to ~noun~ token: "; 1014                                if (l == REPARSE_CODE) print "re-parse request]^"; 1015                                else { 1016                                    if (l == 1) print "but multiple found]^"; 1017                                    if (l == 0) print "error ", etype, "]^"; 1018                                    if (l >= 2) print (the) l, "]^"; 1019                                } 1020                            } 1021                            #Endif; ! DEBUG 1022                            if (l == REPARSE_CODE) jump ReParse; 1023                            if (l >= 2) advance_warning = l; 1024                        } 1025                    } 1026                    break; 1027                } 1028            } 1029        } 1030 1031        ! Slightly different line-parsing rules will apply to "take multi", to 1032        ! prevent "take all" behaving correctly but misleadingly when there's 1033        ! nothing to take. 1034 1035        take_all_rule = 0; 1036        if (m && params_wanted == 1 && action_to_be == ##Take) 1037            take_all_rule = 1; 1038 1039        ! And now start again, properly, forearmed or not as the case may be. 1040        ! As a precaution, we clear all the variables again (they may have been 1041        ! disturbed by the call to NounDomain, which may have called outside 1042        ! code, which may have done anything!). 1043 1044        inferfrom = 0; 1045        parameters = 0; 1046        nsns = 0; special_word = 0; 1047        multiple_object-->0 = 0; 1048        etype = STUCK_PE; 1049        wn = verb_wordnum+1;

Parser Letter G.

Parse each token in turn (calling ParseToken to do most of the work).

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

1058        m = true; 1059        for (pcount=1 : : pcount++) 1060            if (line_token-->(pcount-1) == ENDIT_TOKEN) { 1061                if (pcount >= 2) { 1062                    while ((((line_token-->(pcount-2))->0) & $10) ~= 0) pcount--; 1063                    AnalyseToken(line_token-->(pcount-2)); 1064                    if (found_ttype == PREPOSITION_TT) { 1065                        l = -1; 1066                        while (true) { 1067                            m = NextWordStopped(); 1068                            if (m == -1) break; 1069                            l = m; 1070                        } 1071                        if (PrepositionChain(l, pcount-2) == -1) { 1072                            m = false; 1073                            #Ifdef DEBUG; 1074                            if (parser_trace >= 2) 1075                                print "[line rejected for not ending with correct preposition]^"; 1076                            #Endif; ! DEBUG 1077                        } else m = true; 1078                       } 1079                } 1080                break; 1081            } 1082        wn = verb_wordnum+1; 1083 1084        if (m) for (pcount=1 : : pcount++) { 1085            pattern-->pcount = PATTERN_NULL; scope_token = 0; 1086 1087            token = line_token-->(pcount-1); 1088            lookahead = line_token-->pcount; 1089 1090            #Ifdef DEBUG; 1091            if (parser_trace >= 2) 1092                print " [line ", line, " token ", pcount, " word ", wn, " : ", (DebugToken) token, 1093                  "]^"; 1094            #Endif; ! DEBUG 1095 1096            if (token ~= ENDIT_TOKEN) { 1097                scope_reason = PARSING_REASON; 1098                AnalyseToken(token); 1099 1100                l = ParseToken(found_ttype, found_tdata, pcount-1, token); 1101                while ((l >= GPR_NOUN) && (l < -1)) l = ParseToken(ELEMENTARY_TT, l + 256); 1102                scope_reason = PARSING_REASON; 1103 1104                if (l == GPR_PREPOSITION) { 1105                    if (found_ttype~=PREPOSITION_TT && (found_ttype~=ELEMENTARY_TT || 1106                        found_tdata~=TOPIC_TOKEN)) params_wanted--; 1107                    l = true; 1108                } 1109                else 1110                    if (l < 0) l = false; 1111                    else 1112                        if (l ~= GPR_REPARSE) { 1113                            if (l == GPR_NUMBER) { 1114                                if (nsns == 0) special_number1 = parsed_number; 1115                                else special_number2 = parsed_number; 1116                                nsns++; l = 1; 1117                            } 1118                            if (l == GPR_MULTIPLE) l = 0; 1119                            parser_results-->(parameters+INP1_PRES) = l; 1120                            parameters++; 1121                            pattern-->pcount = l; 1122                            l = true; 1123                        } 1124 1125                #Ifdef DEBUG; 1126                if (parser_trace >= 3) { 1127                    print " [token resulted in "; 1128                    if (l == REPARSE_CODE) print "re-parse request]^"; 1129                    if (l == 0) print "failure with error type ", etype, "]^"; 1130                    if (l == 1) print "success]^"; 1131                } 1132                #Endif; ! DEBUG 1133 1134                if (l == REPARSE_CODE) jump ReParse; 1135                if (l == false) break; 1136            } 1137            else { 1138 1139                ! If the player has entered enough already but there's still 1140                ! text to wade through: store the pattern away so as to be able to produce 1141                ! a decent error message if this turns out to be the best we ever manage, 1142                ! and in the mean time give up on this line 1143 1144                ! However, if the superfluous text begins with a comma or "then" then 1145                ! take that to be the start of another instruction 1146 1147                if (wn <= num_words) { 1148                    l = NextWord(); 1149                    if (l == THEN1__WD or THEN2__WD or THEN3__WD or comma_word) { 1150                        held_back_mode = true; hb_wn = wn-1; 1151                    } else { 1152                        for (m=0 : m<32 : m++) pattern2-->m = pattern-->m; 1153                        pcount2 = pcount; 1154                        etype = UPTO_PE; 1155                        break; 1156                    } 1157                } 1158 1159                ! Now, we may need to revise the multiple object because of the single one 1160                ! we now know (but didn't when the list was drawn up). 1161 1162                if (parameters >= 1) { 1163                    if (parser_results-->INP1_PRES == 0) { 1164                        l = ReviseMulti(parser_results-->INP2_PRES); 1165                        if (l ~= 0) { etype = l; parser_results-->ACTION_PRES = action_to_be; break; } 1166                    } 1167                } 1168                if (parameters >= 2) { 1169                    if (parser_results-->INP2_PRES == 0) { 1170                        l = ReviseMulti(parser_results-->INP1_PRES); 1171                        if (l ~= 0) { etype = l; break; } 1172                    } else { 1173                        k = parser_results-->INP1_PRES; l = parser_results-->INP2_PRES; 1174                        if (k && l) { 1175                             if ((multi_context==MULTIEXCEPT_TOKEN && k == l) || 1176                                ((multi_context==MULTIINSIDE_TOKEN && k notin l && l notin k))) { 1177                                best_etype = NOTHING_PE; 1178                                parser_results-->ACTION_PRES = action_to_be; jump GiveError; 1179                            } 1180                        } 1181                    } 1182                } 1183 1184                ! To trap the case of "take all" inferring only "yourself" when absolutely 1185                ! nothing else is in the vicinity... 1186 1187                if (take_all_rule == 2 && parser_results-->INP1_PRES == actor) { 1188                    best_etype = NOTHING_PE; 1189                    jump GiveError; 1190                } 1191 1192                #Ifdef DEBUG; 1193                if (parser_trace >= 1) print "[Line successfully parsed]^"; 1194                #Endif; ! DEBUG 1195 1196                ! The line has successfully matched the text. Declare the input error-free... 1197 1198                oops_from = 0; 1199 1200                ! ...explain any inferences made (using the pattern)... 1201 1202                if (inferfrom ~= 0) { 1203                    PrintInferredCommand(inferfrom); 1204                    ClearParagraphing(20); 1205                } 1206 1207                ! ...copy the action number, and the number of parameters... 1208 1209                parser_results-->ACTION_PRES = action_to_be; 1210                parser_results-->NO_INPS_PRES = parameters; 1211 1212                ! ...reverse first and second parameters if need be... 1213 1214                if (action_reversed && parameters == 2) { 1215                    i = parser_results-->INP1_PRES; 1216                    parser_results-->INP1_PRES = parser_results-->INP2_PRES; 1217                    parser_results-->INP2_PRES = i; 1218                    if (nsns == 2) { 1219                        i = special_number1; special_number1 = special_number2; 1220                        special_number2 = i; 1221                    } 1222                } 1223 1224                ! ...and to reset "it"-style objects to the first of these parameters, if 1225                ! there is one (and it really is an object)... 1226 1227                if (parameters > 0 && parser_results-->INP1_PRES >= 2) 1228                    PronounNotice(parser_results-->INP1_PRES); 1229 1230                ! ...and return from the parser altogether, having successfully matched 1231                ! a line. 1232 1233                if (held_back_mode) { 1234                    wn=hb_wn; 1235                    jump LookForMore; 1236                } 1237                rtrue; 1238 1239            } ! end of if(token ~= ENDIT_TOKEN) else 1240        } ! end of for(pcount++) 1241 1242        .LineFailed; 1243        ! The line has failed to match. 1244        ! We continue the outer "for" loop, trying the next line in the grammar. 1245 1246        if (etype > best_etype) best_etype = etype; 1247        if (etype ~= ASKSCOPE_PE && etype > nextbest_etype) nextbest_etype = etype; 1248 1249        ! ...unless the line was something like "take all" which failed because 1250        ! nothing matched the "all", in which case we stop and give an error now. 1251 1252        if (take_all_rule == 2 && etype==NOTHING_PE) break; 1253 1254    } ! end of for(line++) 1255 1256    ! The grammar is exhausted: every line has failed to match.

Parser Letter H.

Cheaply parse otherwise unrecognised conversation and return.

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

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

1278  .GiveError; 1279 1280    etype = best_etype; 1281    if (actor ~= player) { 1282        if (usual_grammar_after ~= 0) { 1283            verb_wordnum = usual_grammar_after; 1284            jump AlmostReParse; 1285        } 1286        wn = verb_wordnum; 1287        special_word = NextWord(); 1288        if (special_word == comma_word) { 1289            special_word = NextWord(); 1290            verb_wordnum++; 1291        } 1292        parser_results-->ACTION_PRES = ##Answer; 1293        parser_results-->NO_INPS_PRES = 2; 1294        parser_results-->INP1_PRES = actor; 1295        parser_results-->INP2_PRES = 1; special_number1 = special_word; 1296        actor = player; 1297        consult_from = verb_wordnum; consult_words = num_words-consult_from+1; 1298        rtrue; 1299    }

Parser Letter I.

Print best possible error message.

1305    ! If the player was the actor (eg, in "take dfghh") the error must be printed, 1306    ! and fresh input called for. In three cases the oops word must be jiggled. 1307 1308    if ((etype ofclass Routine) || (etype ofclass String)) { 1309        if (ParserError(etype) ~= 0) jump ReType; 1310    } else { 1311        if (verb_wordnum == 0 && etype == CANTSEE_PE) etype = VERB_PE; 1312        players_command = 100 + WordCount(); ! The snippet variable "player's command" 1313        BeginActivity(PRINTING_A_PARSER_ERROR_ACT); 1314        if (ForActivity(PRINTING_A_PARSER_ERROR_ACT)) jump SkipParserError; 1315    } 1316    pronoun_word = pronoun__word; pronoun_obj = pronoun__obj; 1317 1318    if (etype == STUCK_PE) { PARSER_ERROR_INTERNAL_RM('A'); new_line; oops_from = 1; } 1319    if (etype == UPTO_PE) { 1320        if (inferred_go) PARSER_ERROR_INTERNAL_RM('C'); 1321        else PARSER_ERROR_INTERNAL_RM('B'); 1322        for (m=0 : m<32 : m++) pattern-->m = pattern2-->m; 1323        pcount = pcount2; PrintCommand(0); 1324        print ".^"; 1325    } 1326    if (etype == NUMBER_PE) { PARSER_ERROR_INTERNAL_RM('D'); new_line; } 1327    if (etype == CANTSEE_PE) { PARSER_ERROR_INTERNAL_RM('E'); new_line; oops_from=saved_oops; } 1328    if (etype == TOOLIT_PE) { PARSER_ERROR_INTERNAL_RM('F'); new_line; } 1329    if (etype == NOTHELD_PE) { PARSER_ERROR_INTERNAL_RM('G'); new_line; oops_from=saved_oops; } 1330    if (etype == MULTI_PE) { PARSER_ERROR_INTERNAL_RM('H'); new_line; } 1331    if (etype == MMULTI_PE) { PARSER_ERROR_INTERNAL_RM('I'); new_line; } 1332    if (etype == VAGUE_PE) { PARSER_ERROR_INTERNAL_RM('J'); new_line; } 1333    if (etype == ITGONE_PE) { 1334        if (pronoun_obj == NULL) { PARSER_ERROR_INTERNAL_RM('J'); new_line; } 1335        else { PARSER_ERROR_INTERNAL_RM('K', noun); new_line; } 1336    } 1337    if (etype == EXCEPT_PE) { PARSER_ERROR_INTERNAL_RM('L'); new_line; } 1338    if (etype == ANIMA_PE) { PARSER_ERROR_INTERNAL_RM('M'); new_line; } 1339    if (etype == VERB_PE) { PARSER_ERROR_INTERNAL_RM('N'); new_line; } 1340    if (etype == SCENERY_PE) { PARSER_ERROR_INTERNAL_RM('O'); new_line; } 1341    if (etype == JUNKAFTER_PE) { PARSER_ERROR_INTERNAL_RM('P'); new_line; } 1342    if (etype == TOOFEW_PE) { PARSER_ERROR_INTERNAL_RM('Q', multi_had); new_line; } 1343    if (etype == NOTHING_PE) { 1344        if (parser_results-->ACTION_PRES == ##Remove && 1345            parser_results-->INP2_PRES ofclass Object) { 1346            noun = parser_results-->INP2_PRES; ! ensure valid for messages 1347            if (noun has animate) { PARSER_N_ERROR_INTERNAL_RM('C', noun); new_line; } 1348            else if (noun hasnt container or supporter) { PARSER_N_ERROR_INTERNAL_RM('D', noun); new_line; } 1349            else if (noun has container && noun hasnt open) { PARSER_N_ERROR_INTERNAL_RM('E', noun); new_line; } 1350            else if (children(noun)==0) { PARSER_N_ERROR_INTERNAL_RM('F', noun); new_line; } 1351            else parser_results-->ACTION_PRES = 0; 1352        } 1353        if (parser_results-->ACTION_PRES ~= ##Remove) { 1354            if (multi_wanted==100) { PARSER_N_ERROR_INTERNAL_RM('A'); new_line; } 1355            else { PARSER_N_ERROR_INTERNAL_RM('B'); new_line; } 1356        } 1357    } 1358    if (etype == NOTINCONTEXT_PE) { PARSER_ERROR_INTERNAL_RM('R'); new_line; } 1359    if (etype == ANIMAAGAIN_PE) { PARSER_ERROR_INTERNAL_RM('S'); new_line; } 1360    if (etype == COMMABEGIN_PE) { PARSER_ERROR_INTERNAL_RM('T'); new_line; } 1361    if (etype == MISSINGPERSON_PE) { PARSER_ERROR_INTERNAL_RM('U'); new_line; } 1362    if (etype == ANIMALISTEN_PE) { PARSER_ERROR_INTERNAL_RM('V', noun); new_line; } 1363    if (etype == TOTALK_PE) { PARSER_ERROR_INTERNAL_RM('W'); new_line; } 1364    if (etype == ASKSCOPE_PE) { 1365        scope_stage = 3; 1366        if (indirect(scope_error) == -1) { 1367            best_etype = nextbest_etype; 1368            if (~~((etype ofclass Routine) || (etype ofclass String))) 1369                EndActivity(PRINTING_A_PARSER_ERROR_ACT); 1370            jump GiveError; 1371        } 1372    } 1373 1374    .SkipParserError; 1375    if ((etype ofclass Routine) || (etype ofclass String)) jump ReType; 1376    say__p = 1; 1377    EndActivity(PRINTING_A_PARSER_ERROR_ACT);

Parser Letter J.

Retry the whole lot.

1383    ! And go (almost) right back to square one... 1384 1385    jump ReType; 1386 1387    ! ...being careful not to go all the way back, to avoid infinite repetition 1388    ! of a deferred command causing an error.

Parser Letter K.

Last thing: check for THEN and further instructions(s), return.

1394    ! At this point, the return value is all prepared, and we are only looking 1395    ! to see if there is a "then" followed by subsequent instruction(s). 1396 1397  .LookForMore; 1398 1399    if (wn > num_words) rtrue; 1400 1401    i = NextWord(); 1402    if (i == THEN1__WD or THEN2__WD or THEN3__WD or comma_word) { 1403        if (wn > num_words) { 1404           held_back_mode = false; 1405           return; 1406        } 1407        hb_wn = wn; 1408        held_back_mode = true; 1409       return; 1410    } 1411    best_etype = UPTO_PE; 1412    jump GiveError;

End of Parser Proper.

1417]; ! end of Parser__parse

Internal Rule.

As a hook on which to hang responses.

1423[ PARSER_ERROR_INTERNAL_R; ]; 1424[ PARSER_N_ERROR_INTERNAL_R; ]; 1425[ PARSER_COMMAND_INTERNAL_R; ];

Parse Token.

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

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

The return values are: (a) GPR_REPARSE for "I have rewritten the command, please re-parse from scratch"; (b) GPR_PREPOSITION for "token accepted with no result"; (c) -256 + x for "please parse ParseToken(ELEMENTARY_TT, x) instead"; (d) 0 for "token accepted, result is the multiple object list"; (e) 1 for "token accepted, result is the number in parsed_number"; (f) an object number for "token accepted with this object as result"; (g) -1 for "token rejected".

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

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

(A) Analyse the token; handle all tokens not involving object lists and break down others into elementary tokens (B) Begin parsing an object list (C) Parse descriptors (articles, pronouns, etc.) in the list (D) Parse an object name (E) Parse connectives (AND, BUT, etc.) and go back to (C) (F) Return the conclusion of parsing an object list

1463[ ParseTokenStopped x y; 1464    if (wn>WordCount()) return GPR_FAIL; 1465    return ParseToken(x,y); 1466]; 1467 1468Global parsetoken_nesting = 0; 1469[ ParseToken given_ttype given_tdata token_n token i t rv; 1470    if (parsetoken_nesting > 0) { 1471        ! save match globals 1472        @push match_from; @push token_filter; @push match_length; 1473        @push number_of_classes; @push oops_from; 1474        for (i=0: i<number_matched: i++) { 1475            t = match_list-->i; @push t; 1476            t = match_classes-->i; @push t; 1477            t = match_scores-->i; @push t; 1478        } 1479        @push number_matched; 1480     } 1481 1482    parsetoken_nesting++; 1483    rv = ParseToken__(given_ttype, given_tdata, token_n, token); 1484    parsetoken_nesting--; 1485 1486    if (parsetoken_nesting > 0) { 1487        ! restore match globals 1488        @pull number_matched; 1489        for (i=number_matched-1: i>=0: i--) { 1490             @pull t; match_scores-->i = t; 1491            @pull t; match_classes-->i = t; 1492            @pull t; match_list-->i = t; 1493           } 1494        @pull oops_from; @pull number_of_classes; 1495        @pull match_length; @pull token_filter; @pull match_from; 1496    } 1497    return rv; 1498]; 1499 1500[ ParseToken__ given_ttype given_tdata token_n token 1501    l o i j k and_parity single_object desc_wn many_flag 1502    token_allows_multiple prev_indef_wanted;

Parse Token Letter A.

Analyse token; handle all not involving object lists, break down others.

1508    token_filter = 0; 1509    parser_inflection = name; 1510 1511    switch (given_ttype) { 1512      ELEMENTARY_TT: 1513        switch (given_tdata) { 1514          SPECIAL_TOKEN: 1515            l = TryNumber(wn); 1516            special_word = NextWord(); 1517            #Ifdef DEBUG; 1518            if (l ~= -1000) 1519                if (parser_trace >= 3) print " [Read special as the number ", l, "]^"; 1520            #Endif; ! DEBUG 1521            if (l == -1000) { 1522                #Ifdef DEBUG; 1523                if (parser_trace >= 3) print " [Read special word at word number ", wn, "]^"; 1524                #Endif; ! DEBUG 1525                l = special_word; 1526            } 1527            parsed_number = l; 1528            return GPR_NUMBER; 1529 1530          NUMBER_TOKEN: 1531            l=TryNumber(wn++); 1532            if (l == -1000) { 1533                etype = NUMBER_PE; 1534                return GPR_FAIL; 1535            } 1536            #Ifdef DEBUG; 1537            if (parser_trace>=3) print " [Read number as ", l, "]^"; 1538            #Endif; ! DEBUG 1539            parsed_number = l; 1540            return GPR_NUMBER; 1541 1542          CREATURE_TOKEN: 1543            if (action_to_be == ##Answer or ##Ask or ##AskFor or ##Tell) 1544                scope_reason = TALKING_REASON; 1545 1546          TOPIC_TOKEN: 1547            consult_from = wn; 1548            if ((line_ttype-->(token_n+1) ~= PREPOSITION_TT) && 1549               (line_token-->(token_n+1) ~= ENDIT_TOKEN)) { 1550                   RunTimeProblem(RTP_TEXTTOKENTOOHARD); 1551                   return GPR_PREPOSITION; 1552            } 1553            do o = NextWordStopped(); 1554            until (o == -1 || PrepositionChain(o, token_n+1) ~= -1); 1555            wn--; 1556            consult_words = wn-consult_from; 1557            if (consult_words == 0) return GPR_FAIL; 1558            if (action_to_be == ##Ask or ##Answer or ##Tell) { 1559                o = wn; wn = consult_from; parsed_number = NextWord(); 1560                wn = o; return 1; 1561            } 1562            if (o==-1 && (line_ttype-->(token_n+1) == PREPOSITION_TT)) 1563                return GPR_FAIL; ! don't infer if required preposition is absent 1564            return GPR_PREPOSITION; 1565        } 1566 1567      PREPOSITION_TT: 1568        ! Is it an unnecessary alternative preposition, when a previous choice 1569        ! has already been matched? 1570        if ((token->0) & $10) return GPR_PREPOSITION; 1571 1572        ! If we've run out of the player's input, but still have parameters to 1573        ! specify, we go into "infer" mode, remembering where we are and the 1574        ! preposition we are inferring... 1575 1576        if (wn > num_words) { 1577            if (inferfrom==0 && parameters<params_wanted) { 1578                inferfrom = pcount; inferword = token; 1579                pattern-->pcount = REPARSE_CODE + VM_DictionaryAddressToNumber(given_tdata); 1580            } 1581 1582            ! If we are not inferring, then the line is wrong... 1583 1584            if (inferfrom == 0) return -1; 1585 1586            ! If not, then the line is right but we mark in the preposition... 1587 1588            pattern-->pcount = REPARSE_CODE + VM_DictionaryAddressToNumber(given_tdata); 1589            return GPR_PREPOSITION; 1590        } 1591 1592        o = NextWord(); 1593 1594        pattern-->pcount = REPARSE_CODE + VM_DictionaryAddressToNumber(o); 1595 1596        ! Whereas, if the player has typed something here, see if it is the 1597        ! required preposition... if it's wrong, the line must be wrong, 1598        ! but if it's right, the token is passed (jump to finish this token). 1599 1600        if (o == given_tdata) return GPR_PREPOSITION; 1601        if (PrepositionChain(o, token_n) ~= -1) return GPR_PREPOSITION; 1602        return -1; 1603 1604      GPR_TT: 1605        l = indirect(given_tdata); 1606        #Ifdef DEBUG; 1607        if (parser_trace >= 3) print " [Outside parsing routine returned ", l, "]^"; 1608        #Endif; ! DEBUG 1609        return l; 1610 1611      SCOPE_TT: 1612        scope_token = given_tdata; 1613        scope_stage = 1; 1614        #Ifdef DEBUG; 1615        if (parser_trace >= 3) print " [Scope routine called at stage 1]^"; 1616        #Endif; ! DEBUG 1617        l = indirect(scope_token); 1618        #Ifdef DEBUG; 1619        if (parser_trace >= 3) print " [Scope routine returned multiple-flag of ", l, "]^"; 1620        #Endif; ! DEBUG 1621        if (l == 1) given_tdata = MULTI_TOKEN; else given_tdata = NOUN_TOKEN; 1622 1623      ATTR_FILTER_TT: 1624        token_filter = 1 + given_tdata; 1625        given_tdata = NOUN_TOKEN; 1626 1627      ROUTINE_FILTER_TT: 1628        token_filter = given_tdata; 1629        given_tdata = NOUN_TOKEN; 1630 1631    } ! end of switch(given_ttype) 1632 1633    token = given_tdata;

Parse Token Letter B.

Begin parsing an object list.

1639    ! There are now three possible ways we can be here: 1640    ! parsing an elementary token other than "special" or "number"; 1641    ! parsing a scope token; 1642    ! parsing a noun-filter token (either by routine or attribute). 1643    ! 1644    ! In each case, token holds the type of elementary parse to 1645    ! perform in matching one or more objects, and 1646    ! token_filter is 0 (default), an attribute + 1 for an attribute filter 1647    ! or a routine address for a routine filter. 1648 1649    token_allows_multiple = false; 1650    if (token == MULTI_TOKEN or MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN) 1651        token_allows_multiple = true; 1652 1653    many_flag = false; and_parity = true; dont_infer = false;

Parse Token Letter C.

Parse descriptors (articles, pronouns, etc.) in the list.

1659    ! We expect to find a list of objects next in what the player's typed. 1660 1661  .ObjectList; 1662 1663    #Ifdef DEBUG; 1664    if (parser_trace >= 3) print " [Object list from word ", wn, "]^"; 1665    #Endif; ! DEBUG 1666 1667    ! Take an advance look at the next word: if it's "it" or "them", and these 1668    ! are unset, set the appropriate error number and give up on the line 1669    ! (if not, these are still parsed in the usual way - it is not assumed 1670    ! that they still refer to something in scope) 1671 1672    o = NextWord(); wn--; 1673 1674    pronoun_word = NULL; pronoun_obj = NULL; 1675    l = PronounValue(o); 1676    if (l ~= 0) { 1677        pronoun_word = o; pronoun_obj = l; 1678        if (l == NULL) { 1679            ! Don't assume this is a use of an unset pronoun until the 1680            ! descriptors have been checked, because it might be an 1681            ! article (or some such) instead 1682 1683            for (l=1 : l<=LanguageDescriptors-->0 : l=l+4) 1684                if (o == LanguageDescriptors-->l) jump AssumeDescriptor; 1685            pronoun__word = pronoun_word; pronoun__obj = pronoun_obj; 1686            etype = VAGUE_PE; 1687            if (parser_trace >= 3) print " [Stop: unset pronoun]^"; 1688            return GPR_FAIL; 1689        } 1690    } 1691 1692  .AssumeDescriptor; 1693 1694    if (o == ME1__WD or ME2__WD or ME3__WD) { pronoun_word = o; pronoun_obj = player; } 1695 1696    allow_plurals = true; desc_wn = wn; 1697 1698  .TryAgain; 1699 1700    ! First, we parse any descriptive words (like "the", "five" or "every"): 1701    l = Descriptors(token_allows_multiple); 1702    if (l ~= 0) { etype = l; return 0; } 1703 1704  .TryAgain2;

Parse Token Letter D.

Parse an object name.

1710    ! This is an actual specified object, and is therefore where a typing error 1711    ! is most likely to occur, so we set: 1712 1713    oops_from = wn; 1714 1715    ! So, two cases. Case 1: token not equal to "held" (so, no implicit takes) 1716    ! but we may well be dealing with multiple objects 1717 1718    ! In either case below we use NounDomain, giving it the token number as 1719    ! context, and two places to look: among the actor's possessions, and in the 1720    ! present location. (Note that the order depends on which is likeliest.) 1721 1722    if (token ~= HELD_TOKEN) { 1723        i = multiple_object-->0; 1724        #Ifdef DEBUG; 1725        if (parser_trace >= 3) print " [Calling NounDomain on location and actor]^"; 1726        #Endif; ! DEBUG 1727        l = NounDomain(actors_location, actor, token); 1728        if (l == REPARSE_CODE) return l; ! Reparse after Q&A 1729        if (indef_wanted == INDEF_ALL_WANTED && l == 0 && number_matched == 0) 1730            l = 1; ! ReviseMulti if TAKE ALL FROM empty container 1731 1732        if (token_allows_multiple && ~~multiflag) { 1733            if (best_etype==MULTI_PE) best_etype=STUCK_PE; 1734            multiflag = true; 1735        } 1736        if (l == 0) { 1737            if (indef_possambig) { 1738                ResetDescriptors(); 1739                wn = desc_wn; 1740                jump TryAgain2; 1741            } 1742            if (etype == MULTI_PE && multiflag) etype = STUCK_PE; 1743            etype=CantSee(); 1744            jump FailToken; 1745        } ! Choose best error 1746 1747        #Ifdef DEBUG; 1748        if (parser_trace >= 3) { 1749            if (l > 1) print " [ND returned ", (the) l, "]^"; 1750            else { 1751                print " [ND appended to the multiple object list:^"; 1752                k = multiple_object-->0; 1753                for (j=i+1 : j<=k : j++) 1754                    print " Entry ", j, ": ", (The) multiple_object-->j, 1755                          " (", multiple_object-->j, ")^"; 1756                print " List now has size ", k, "]^"; 1757            } 1758        } 1759        #Endif; ! DEBUG 1760 1761        if (l == 1) { 1762            if (~~many_flag) many_flag = true; 1763            else { ! Merge with earlier ones 1764                k = multiple_object-->0; ! (with either parity) 1765                multiple_object-->0 = i; 1766                for (j=i+1 : j<=k : j++) { 1767                    if (and_parity) MultiAdd(multiple_object-->j); 1768                    else MultiSub(multiple_object-->j); 1769                } 1770                #Ifdef DEBUG; 1771                if (parser_trace >= 3) 1772                    print " [Merging ", k-i, " new objects to the ", i, " old ones]^"; 1773                #Endif; ! DEBUG 1774            } 1775        } 1776        else { 1777            ! A single object was indeed found 1778 1779            if (match_length == 0 && indef_possambig) { 1780                ! So the answer had to be inferred from no textual data, 1781                ! and we know that there was an ambiguity in the descriptor 1782                ! stage (such as a word which could be a pronoun being 1783                ! parsed as an article or possessive). It's worth having 1784                ! another go. 1785 1786                ResetDescriptors(); 1787                wn = desc_wn; 1788                jump TryAgain2; 1789            } 1790 1791            if ((token == CREATURE_TOKEN) && (CreatureTest(l) == 0)) { 1792                etype = ANIMA_PE; 1793                jump FailToken; 1794            } ! Animation is required 1795 1796            if (~~many_flag) single_object = l; 1797            else { 1798                if (and_parity) MultiAdd(l); else MultiSub(l); 1799                #Ifdef DEBUG; 1800                if (parser_trace >= 3) print " [Combining ", (the) l, " with list]^"; 1801                #Endif; ! DEBUG 1802            } 1803        } 1804    } 1805 1806    else { 1807 1808    ! Case 2: token is "held" (which fortunately can't take multiple objects) 1809    ! and may generate an implicit take 1810 1811        l = NounDomain(actor,actors_location,token); ! Same as above... 1812        if (l == REPARSE_CODE) return l; 1813        if (l == 0) { 1814            if (indef_possambig) { 1815                ResetDescriptors(); 1816                wn = desc_wn; 1817                jump TryAgain2; 1818            } 1819            etype = CantSee(); jump FailToken; ! Choose best error 1820        } 1821 1822        ! ...until it produces something not held by the actor. Then an implicit 1823        ! take must be tried. If this is already happening anyway, things are too 1824        ! confused and we have to give up (but saving the oops marker so as to get 1825        ! it on the right word afterwards). 1826        ! The point of this last rule is that a sequence like 1827        ! 1828        ! > read newspaper 1829        ! (taking the newspaper first) 1830        ! The dwarf unexpectedly prevents you from taking the newspaper! 1831        ! 1832        ! should not be allowed to go into an infinite repeat - read becomes 1833        ! take then read, but take has no effect, so read becomes take then read... 1834        ! Anyway for now all we do is record the number of the object to take. 1835 1836        o = parent(l); 1837        if (o ~= actor) { 1838            #Ifdef DEBUG; 1839            if (parser_trace >= 3) print " [Allowing object ", (the) l, " for now]^"; 1840            #Endif; ! DEBUG 1841        } 1842        single_object = l; 1843    } ! end of if (token ~= HELD_TOKEN) else 1844 1845    ! The following moves the word marker to just past the named object... 1846     1847! if (match_from ~= oops_from) print match_from, " vs ", oops_from, "^"; 1848 1849! wn = oops_from + match_length; 1850    wn = match_from + match_length;

Parse Token Letter E.

Parse connectives (AND, BUT, etc.) and go back to (C).

1856    ! Object(s) specified now: is that the end of the list, or have we reached 1857    ! "and", "but" and so on? If so, create a multiple-object list if we 1858    ! haven't already (and are allowed to). 1859 1860  .NextInList; 1861 1862    o = NextWord(); 1863 1864    if (o == AND1__WD or AND2__WD or AND3__WD or BUT1__WD or BUT2__WD or BUT3__WD or comma_word) { 1865 1866        #Ifdef DEBUG; 1867        if (parser_trace >= 3) print " [Read connective ", (address) o, "]^"; 1868        #Endif; ! DEBUG 1869 1870        if (~~token_allows_multiple) { 1871            if (multiflag) jump PassToken; ! give UPTO_PE error 1872            etype=MULTI_PE; 1873            jump FailToken; 1874        } 1875 1876        if (o == BUT1__WD or BUT2__WD or BUT3__WD) and_parity = 1-and_parity; 1877 1878        if (~~many_flag) { 1879            multiple_object-->0 = 1; 1880            multiple_object-->1 = single_object; 1881            many_flag = true; 1882            #Ifdef DEBUG; 1883            if (parser_trace >= 3) print " [Making new list from ", (the) single_object, "]^"; 1884            #Endif; ! DEBUG 1885        } 1886        dont_infer = true; inferfrom=0; ! Don't print (inferences) 1887        jump ObjectList; ! And back around 1888    } 1889 1890    wn--; ! Word marker back to first not-understood word

Parse Token Letter F.

Return the conclusion of parsing an object list.

1896    ! Happy or unhappy endings: 1897 1898  .PassToken; 1899    if (many_flag) { 1900        single_object = GPR_MULTIPLE; 1901        multi_context = token; 1902    } 1903    else { 1904        if (indef_mode == 1 && indef_type & PLURAL_BIT ~= 0) { 1905              if (token == MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN) multi_context = token; 1906            if (indef_wanted < INDEF_ALL_WANTED && indef_wanted > 1) { 1907                multi_had = 1; multi_wanted = indef_wanted; 1908                etype = TOOFEW_PE; 1909                jump FailToken; 1910            } 1911        } 1912    } 1913    return single_object; 1914 1915  .FailToken; 1916 1917    ! If we were only guessing about it being a plural, try again but only 1918    ! allowing singulars (so that words like "six" are not swallowed up as 1919    ! Descriptors) 1920 1921    if (allow_plurals && indef_guess_p == 1) { 1922        #Ifdef DEBUG; 1923        if (parser_trace >= 4) print " [Retrying singulars after failure ", etype, "]^"; 1924        #Endif; 1925        prev_indef_wanted = indef_wanted; 1926        allow_plurals = false; 1927        wn = desc_wn; 1928        jump TryAgain; 1929    } 1930 1931    if ((indef_wanted > 0 || prev_indef_wanted > 0) && (~~multiflag)) etype = MULTI_PE; 1932 1933    return GPR_FAIL; 1934 1935]; ! end of ParseToken__

Descriptors.

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

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

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

1954Constant OTHER_BIT = 1; ! These will be used in Adjudicate() 1955Constant MY_BIT = 2; ! to disambiguate choices 1956Constant THAT_BIT = 4; 1957Constant PLURAL_BIT = 8; 1958Constant LIT_BIT = 16; 1959Constant UNLIT_BIT = 32; 1960 1961[ ResetDescriptors; 1962    indef_mode = 0; indef_type = 0; indef_wanted = 0; indef_guess_p = 0; 1963    indef_possambig = false; 1964    indef_owner = nothing; 1965    indef_cases = $$111111111111; 1966    indef_nspec_at = 0; 1967]; 1968 1969[ ArticleDescriptors o x flag cto type n; 1970    if (wn > num_words) return 0; 1971 1972    for (flag=true : flag :) { 1973        o = NextWordStopped(); flag = false; 1974 1975       for (x=1 : x<=LanguageDescriptors-->0 : x=x+4) 1976            if (o == LanguageDescriptors-->x) { 1977                type = LanguageDescriptors-->(x+2); 1978                if (type == DEFART_PK or INDEFART_PK) flag = true; 1979            } 1980    } 1981    wn--; 1982    return 0; 1983];

Parsing Descriptors.

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

1992[ Descriptors o x flag cto type n; 1993    ResetDescriptors(); 1994    if (wn > num_words) return 0; 1995 1996    for (flag=true : flag :) { 1997        o = NextWordStopped(); flag = false; 1998 1999       for (x=1 : x<=LanguageDescriptors-->0 : x=x+4) 2000            if (o == LanguageDescriptors-->x) { 2001                flag = true; 2002                type = LanguageDescriptors-->(x+2); 2003                if (type ~= DEFART_PK) indef_mode = true; 2004                indef_possambig = true; 2005                indef_cases = indef_cases & (LanguageDescriptors-->(x+1)); 2006 2007                if (type == POSSESS_PK) { 2008                    cto = LanguageDescriptors-->(x+3); 2009                    switch (cto) { 2010                      0: indef_type = indef_type | MY_BIT; 2011                      1: indef_type = indef_type | THAT_BIT; 2012                      default: 2013                        indef_owner = PronounValue(cto); 2014                        if (indef_owner == NULL) indef_owner = InformParser; 2015                    } 2016                } 2017 2018                if (type == light) indef_type = indef_type | LIT_BIT; 2019                if (type == -light) indef_type = indef_type | UNLIT_BIT; 2020            } 2021 2022        if (o == OTHER1__WD or OTHER2__WD or OTHER3__WD) { 2023            indef_mode = 1; flag = 1; 2024            indef_type = indef_type | OTHER_BIT; 2025        } 2026        if (o == ALL1__WD or ALL2__WD or ALL3__WD or ALL4__WD or ALL5__WD) { 2027            indef_mode = 1; flag = 1; indef_wanted = INDEF_ALL_WANTED; 2028            if (take_all_rule == 1) take_all_rule = 2; 2029            indef_type = indef_type | PLURAL_BIT; 2030        } 2031        if (allow_plurals) { 2032            if (NextWordStopped() ~= -1 or THEN1__WD) { wn--; n = TryNumber(wn-1); } else { n=0; wn--; } 2033            if (n == 1) { indef_mode = 1; flag = 1; } 2034            if (n > 1) { 2035                indef_guess_p = 1; 2036                indef_mode = 1; flag = 1; indef_wanted = n; 2037                indef_nspec_at = wn-1; 2038                indef_type = indef_type | PLURAL_BIT; 2039            } 2040        } 2041        if (flag == 1 && NextWordStopped() ~= OF1__WD or OF2__WD or OF3__WD or OF4__WD) 2042            wn--; ! Skip 'of' after these 2043    } 2044    wn--; 2045    return 0; 2046]; 2047 2048[ SafeSkipDescriptors; 2049    @push indef_mode; @push indef_type; @push indef_wanted; 2050    @push indef_guess_p; @push indef_possambig; @push indef_owner; 2051    @push indef_cases; @push indef_nspec_at; 2052     2053    Descriptors(); 2054     2055    @pull indef_nspec_at; @pull indef_cases; 2056    @pull indef_owner; @pull indef_possambig; @pull indef_guess_p; 2057    @pull indef_wanted; @pull indef_type; @pull indef_mode; 2058];

Preposition Chain.

A small utility for runs of prepositions.

2064[ PrepositionChain wd index; 2065    if (line_tdata-->index == wd) return wd; 2066    if ((line_token-->index)->0 & $20 == 0) return -1; 2067    do { 2068        if (line_tdata-->index == wd) return wd; 2069        index++; 2070    } until ((line_token-->index == ENDIT_TOKEN) || (((line_token-->index)->0 & $10) == 0)); 2071    return -1; 2072];

Creature.

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

2079[ CreatureTest obj; 2080    if (obj has animate) rtrue; 2081    if (obj hasnt talkable) rfalse; 2082    if (action_to_be == ##Ask or ##Answer or ##Tell or ##AskFor) rtrue; 2083    rfalse; 2084];

Noun Domain.

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

(a) 0 if no match at all could be made, (b) 1 if a multiple object was made, (c) k if object k was the one decided upon, (d) REPARSE_CODE if it asked a question of the player and consequently rewrote the player's input, so that the whole parser should start again on the rewritten input.

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

2104[ NounDomain domain1 domain2 context dont_ask 2105    first_word i j k l answer_words marker; 2106    #Ifdef DEBUG; 2107    if (parser_trace >= 4) { 2108        print " [NounDomain called at word ", wn, "^"; 2109        print " "; 2110        if (indef_mode) { 2111            print "seeking indefinite object: "; 2112            if (indef_type & OTHER_BIT) print "other "; 2113            if (indef_type & MY_BIT) print "my "; 2114            if (indef_type & THAT_BIT) print "that "; 2115            if (indef_type & PLURAL_BIT) print "plural "; 2116            if (indef_type & LIT_BIT) print "lit "; 2117            if (indef_type & UNLIT_BIT) print "unlit "; 2118            if (indef_owner ~= 0) print "owner:", (name) indef_owner; 2119            new_line; 2120            print " number wanted: "; 2121            if (indef_wanted == INDEF_ALL_WANTED) print "all"; else print indef_wanted; 2122            new_line; 2123            print " most likely GNAs of names: ", indef_cases, "^"; 2124        } 2125        else print "seeking definite object^"; 2126    } 2127    #Endif; ! DEBUG 2128 2129    match_length = 0; number_matched = 0; match_from = wn; 2130 2131    SearchScope(domain1, domain2, context); 2132 2133    #Ifdef DEBUG; 2134    if (parser_trace >= 4) print " [ND made ", number_matched, " matches]^"; 2135    #Endif; ! DEBUG 2136 2137    wn = match_from+match_length; 2138 2139    ! If nothing worked at all, leave with the word marker skipped past the 2140    ! first unmatched word... 2141 2142    if (number_matched == 0) { wn++; rfalse; } 2143 2144    ! Suppose that there really were some words being parsed (i.e., we did 2145    ! not just infer). If so, and if there was only one match, it must be 2146    ! right and we return it... 2147 2148    if (match_from <= num_words) { 2149        if (number_matched == 1) { 2150            i=match_list-->0; 2151            return i; 2152        } 2153 2154        ! ...now suppose that there was more typing to come, i.e. suppose that 2155        ! the user entered something beyond this noun. If nothing ought to follow, 2156        ! then there must be a mistake, (unless what does follow is just a full 2157        ! stop, and or comma) 2158 2159        if (wn <= num_words) { 2160            i = NextWord(); wn--; 2161            if (i ~= AND1__WD or AND2__WD or AND3__WD or comma_word 2162                   or THEN1__WD or THEN2__WD or THEN3__WD 2163                   or BUT1__WD or BUT2__WD or BUT3__WD) { 2164                if (lookahead == ENDIT_TOKEN) rfalse; 2165            } 2166        } 2167    } 2168 2169    ! Now look for a good choice, if there's more than one choice... 2170 2171    number_of_classes = 0; 2172 2173    if (number_matched == 1) { 2174        i = match_list-->0; 2175        if (indef_mode == 1 && indef_type & PLURAL_BIT ~= 0) { 2176            if (context == MULTI_TOKEN or MULTIHELD_TOKEN or 2177                MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN or 2178                NOUN_TOKEN or HELD_TOKEN or CREATURE_TOKEN) { 2179                BeginActivity(DECIDING_WHETHER_ALL_INC_ACT, i); 2180                if ((ForActivity(DECIDING_WHETHER_ALL_INC_ACT, i)) && 2181                    (RulebookFailed())) rfalse; 2182                EndActivity(DECIDING_WHETHER_ALL_INC_ACT, i); 2183            } 2184        } 2185    } 2186    if (number_matched > 1) { 2187        i = true; 2188        if (number_matched > 1) 2189            for (j=0 : j<number_matched-1 : j++) 2190                if (Identical(match_list-->j, match_list-->(j+1)) == false) 2191                    i = false; 2192        if (i) dont_infer = true; 2193        i = Adjudicate(context); 2194        if (i == -1) rfalse; 2195        if (i == 1) rtrue; ! Adjudicate has made a multiple 2196                             ! object, and we pass it on 2197    } 2198 2199    ! If i is non-zero here, one of two things is happening: either 2200    ! (a) an inference has been successfully made that object i is 2201    ! the intended one from the user's specification, or 2202    ! (b) the user finished typing some time ago, but we've decided 2203    ! on i because it's the only possible choice. 2204    ! In either case we have to keep the pattern up to date, 2205    ! note that an inference has been made and return. 2206    ! (Except, we don't note which of a pile of identical objects.) 2207 2208    if (i ~= 0) { 2209        if (dont_infer) return i; 2210        if (inferfrom == 0) inferfrom=pcount; 2211        pattern-->pcount = i; 2212        return i; 2213    } 2214 2215    if (dont_ask) return match_list-->0; 2216 2217    ! If we get here, there was no obvious choice of object to make. If in 2218    ! fact we've already gone past the end of the player's typing (which 2219    ! means the match list must contain every object in scope, regardless 2220    ! of its name), then it's foolish to give an enormous list to choose 2221    ! from - instead we go and ask a more suitable question... 2222 2223    if (match_from > num_words) jump Incomplete; 2224 2225    ! Now we print up the question, using the equivalence classes as worked 2226    ! out by Adjudicate() so as not to repeat ourselves on plural objects... 2227 2228    BeginActivity(ASKING_WHICH_DO_YOU_MEAN_ACT); 2229    if (ForActivity(ASKING_WHICH_DO_YOU_MEAN_ACT)) jump SkipWhichQuestion; 2230    j = 1; marker = 0; 2231    for (i=1 : i<=number_of_classes : i++) { 2232        while (((match_classes-->marker) ~= i) && ((match_classes-->marker) ~= -i)) 2233            marker++; 2234        if (match_list-->marker hasnt animate) j = 0; 2235    } 2236    if (j) PARSER_CLARIF_INTERNAL_RM('A'); 2237    else PARSER_CLARIF_INTERNAL_RM('B'); 2238 2239    j = number_of_classes; marker = 0; 2240    for (i=1 : i<=number_of_classes : i++) { 2241        while (((match_classes-->marker) ~= i) && ((match_classes-->marker) ~= -i)) marker++; 2242        k = match_list-->marker; 2243 2244        if (match_classes-->marker > 0) print (the) k; else print (a) k; 2245 2246        if (i < j-1) print ", "; 2247        if (i == j-1) { 2248            #Ifdef SERIAL_COMMA; 2249            if (j ~= 2) print ","; 2250            #Endif; ! SERIAL_COMMA 2251            PARSER_CLARIF_INTERNAL_RM('H'); 2252        } 2253    } 2254    print "?^"; 2255 2256    .SkipWhichQuestion; EndActivity(ASKING_WHICH_DO_YOU_MEAN_ACT); 2257 2258    ! ...and get an answer: 2259 2260  .WhichOne; 2261    #Ifdef TARGET_ZCODE; 2262    for (i=2 : i<INPUT_BUFFER_LEN : i++) buffer2->i = ' '; 2263    #Endif; ! TARGET_ZCODE 2264    answer_words=Keyboard(buffer2, parse2); 2265 2266    ! Conveniently, parse2-->1 is the first word in both ZCODE and GLULX. 2267    first_word = (parse2-->1); 2268 2269    ! Take care of "all", because that does something too clever here to do 2270    ! later on: 2271 2272    if (first_word == ALL1__WD or ALL2__WD or ALL3__WD or ALL4__WD or ALL5__WD) { 2273        if (context == MULTI_TOKEN or MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN) { 2274            l = multiple_object-->0; 2275            for (i=0 : i<number_matched && l+i<MATCH_LIST_WORDS : i++) { 2276                k = match_list-->i; 2277                multiple_object-->(i+1+l) = k; 2278            } 2279            multiple_object-->0 = i+l; 2280            rtrue; 2281        } 2282        PARSER_CLARIF_INTERNAL_RM('C'); 2283        jump WhichOne; 2284    } 2285 2286    ! Look for a comma, and interpret this as a fresh conversation command 2287    ! if so: 2288 2289    for (i=1 : i<=answer_words : i++) 2290        if (WordFrom(i, parse2) == comma_word) { 2291            VM_CopyBuffer(buffer, buffer2); 2292            jump RECONSTRUCT_INPUT; 2293        } 2294 2295    ! If the first word of the reply can be interpreted as a verb, then 2296    ! assume that the player has ignored the question and given a new 2297    ! command altogether. 2298    ! (This is one time when it's convenient that the directions are 2299    ! not themselves verbs - thus, "north" as a reply to "Which, the north 2300    ! or south door" is not treated as a fresh command but as an answer.) 2301 2302    #Ifdef LanguageIsVerb; 2303    if (first_word == 0) { 2304        j = wn; first_word = LanguageIsVerb(buffer2, parse2, 1); wn = j; 2305    } 2306    #Endif; ! LanguageIsVerb 2307    if (first_word ~= 0) { 2308        j = first_word->#dict_par1; 2309        if ((0 ~= j&1) && ~~LanguageVerbMayBeName(first_word)) { 2310            VM_CopyBuffer(buffer, buffer2); 2311            jump RECONSTRUCT_INPUT; 2312        } 2313    } 2314 2315    ! Now we insert the answer into the original typed command, as 2316    ! words additionally describing the same object 2317    ! (eg, > take red button 2318    ! Which one, ... 2319    ! > music 2320    ! becomes "take music red button". The parser will thus have three 2321    ! words to work from next time, not two.) 2322 2323    #Ifdef TARGET_ZCODE; 2324    k = WordAddress(match_from) - buffer; l=buffer2->1+1; 2325    for (j=buffer + buffer->0 - 1 : j>=buffer+k+l : j--) j->0 = 0->(j-l); 2326    for (i=0 : i<l : i++) buffer->(k+i) = buffer2->(2+i); 2327    buffer->(k+l-1) = ' '; 2328    buffer->1 = buffer->1 + l; 2329    if (buffer->1 >= (buffer->0 - 1)) buffer->1 = buffer->0; 2330    #Ifnot; ! TARGET_GLULX 2331    k = WordAddress(match_from) - buffer; 2332    l = (buffer2-->0) + 1; 2333    for (j=buffer+INPUT_BUFFER_LEN-1 : j>=buffer+k+l : j--) j->0 = j->(-l); 2334    for (i=0 : i<l : i++) buffer->(k+i) = buffer2->(WORDSIZE+i); 2335    buffer->(k+l-1) = ' '; 2336    buffer-->0 = buffer-->0 + l; 2337    if (buffer-->0 > (INPUT_BUFFER_LEN-WORDSIZE)) buffer-->0 = (INPUT_BUFFER_LEN-WORDSIZE); 2338    #Endif; ! TARGET_ 2339 2340    ! Having reconstructed the input, we warn the parser accordingly 2341    ! and get out. 2342 2343    .RECONSTRUCT_INPUT; 2344 2345    num_words = WordCount(); players_command = 100 + num_words; 2346    wn = 1; 2347    #Ifdef LanguageToInformese; 2348    LanguageToInformese(); 2349    ! Re-tokenise: 2350    VM_Tokenise(buffer,parse); 2351    #Endif; ! LanguageToInformese 2352    num_words = WordCount(); players_command = 100 + num_words; 2353    actors_location = ScopeCeiling(player); 2354    FollowRulebook(Activity_after_rulebooks-->READING_A_COMMAND_ACT); 2355 2356    return REPARSE_CODE; 2357 2358    ! Now we come to the question asked when the input has run out 2359    ! and can't easily be guessed (eg, the player typed "take" and there 2360    ! were plenty of things which might have been meant). 2361 2362  .Incomplete; 2363 2364    if (context == CREATURE_TOKEN) PARSER_CLARIF_INTERNAL_RM('D', actor); 2365    else PARSER_CLARIF_INTERNAL_RM('E', actor); 2366    new_line; 2367 2368    #Ifdef TARGET_ZCODE; 2369    for (i=2 : i<INPUT_BUFFER_LEN : i++) buffer2->i=' '; 2370    #Endif; ! TARGET_ZCODE 2371    answer_words = Keyboard(buffer2, parse2); 2372 2373    ! Look for a comma, and interpret this as a fresh conversation command 2374    ! if so: 2375 2376    for (i=1 : i<=answer_words : i++) 2377        if (WordFrom(i, parse2) == comma_word) { 2378            VM_CopyBuffer(buffer, buffer2); 2379            jump RECONSTRUCT_INPUT; 2380        } 2381 2382    first_word=(parse2-->1); 2383    #Ifdef LanguageIsVerb; 2384    if (first_word==0) { 2385        j = wn; first_word=LanguageIsVerb(buffer2, parse2, 1); wn = j; 2386    } 2387    #Endif; ! LanguageIsVerb 2388 2389    ! Once again, if the reply looks like a command, give it to the 2390    ! parser to get on with and forget about the question... 2391 2392    if (first_word ~= 0) { 2393        j = first_word->#dict_par1; 2394        if ((0 ~= j&1) && ~~LanguageVerbMayBeName(first_word)) { 2395            VM_CopyBuffer(buffer, buffer2); 2396            jump RECONSTRUCT_INPUT; 2397        } 2398    } 2399 2400    ! ...but if we have a genuine answer, then: 2401    ! 2402    ! (1) we must glue in text suitable for anything that's been inferred. 2403 2404    if (inferfrom ~= 0) { 2405        for (j=inferfrom : j<pcount : j++) { 2406            if (pattern-->j == PATTERN_NULL) continue; 2407            #Ifdef TARGET_ZCODE; 2408            i = 2+buffer->1; (buffer->1)++; buffer->(i++) = ' '; 2409            #Ifnot; ! TARGET_GLULX 2410            i = WORDSIZE + buffer-->0; 2411            (buffer-->0)++; buffer->(i++) = ' '; 2412            #Endif; ! TARGET_ 2413 2414            #Ifdef DEBUG; 2415            if (parser_trace >= 5) 2416                print "[Gluing in inference with pattern code ", pattern-->j, "]^"; 2417            #Endif; ! DEBUG 2418 2419            ! Conveniently, parse2-->1 is the first word in both ZCODE and GLULX. 2420 2421            parse2-->1 = 0; 2422 2423            ! An inferred object. Best we can do is glue in a pronoun. 2424            ! (This is imperfect, but it's very seldom needed anyway.) 2425 2426            if (pattern-->j >= 2 && pattern-->j < REPARSE_CODE) { 2427                PronounNotice(pattern-->j); 2428                for (k=1 : k<=LanguagePronouns-->0 : k=k+3) 2429                    if (pattern-->j == LanguagePronouns-->(k+2)) { 2430                        parse2-->1 = LanguagePronouns-->k; 2431                        #Ifdef DEBUG; 2432                        if (parser_trace >= 5) 2433                            print "[Using pronoun ", (address) parse2-->1, "]^"; 2434                        #Endif; ! DEBUG 2435                        break; 2436                    } 2437            } 2438            else { 2439                ! An inferred preposition. 2440                parse2-->1 = VM_NumberToDictionaryAddress(pattern-->j - REPARSE_CODE); 2441                #Ifdef DEBUG; 2442                if (parser_trace >= 5) 2443                    print "[Using preposition ", (address) parse2-->1, "]^"; 2444                #Endif; ! DEBUG 2445            } 2446 2447            ! parse2-->1 now holds the dictionary address of the word to glue in. 2448 2449            if (parse2-->1 ~= 0) { 2450                k = buffer + i; 2451                #Ifdef TARGET_ZCODE; 2452                @output_stream 3 k; 2453                 print (address) parse2-->1; 2454                @output_stream -3; 2455                k = k-->0; 2456                for (l=i : l<i+k : l++) buffer->l = buffer->(l+2); 2457                i = i + k; buffer->1 = i-2; 2458                #Ifnot; ! TARGET_GLULX 2459                k = Glulx_PrintAnyToArray(buffer+i, INPUT_BUFFER_LEN-i, parse2-->1); 2460                i = i + k; buffer-->0 = i - WORDSIZE; 2461                #Endif; ! TARGET_ 2462            } 2463        } 2464    } 2465 2466    ! (2) we must glue the newly-typed text onto the end. 2467 2468    #Ifdef TARGET_ZCODE; 2469    i = 2+buffer->1; (buffer->1)++; buffer->(i++) = ' '; 2470    for (j=0 : j<buffer2->1 : i++,j++) { 2471        buffer->i = buffer2->(j+2); 2472        (buffer->1)++; 2473        if (buffer->1 == INPUT_BUFFER_LEN) break; 2474    } 2475    #Ifnot; ! TARGET_GLULX 2476    i = WORDSIZE + buffer-->0; 2477    (buffer-->0)++; buffer->(i++) = ' '; 2478    for (j=0 : j<buffer2-->0 : i++,j++) { 2479        buffer->i = buffer2->(j+WORDSIZE); 2480        (buffer-->0)++; 2481        if (buffer-->0 == INPUT_BUFFER_LEN) break; 2482    } 2483    #Endif; ! TARGET_ 2484 2485    ! (3) we fill up the buffer with spaces, which is unnecessary, but may 2486    ! help incorrectly-written interpreters to cope. 2487 2488    #Ifdef TARGET_ZCODE; 2489    for (: i<INPUT_BUFFER_LEN : i++) buffer->i = ' '; 2490    #Endif; ! TARGET_ZCODE 2491 2492    jump RECONSTRUCT_INPUT; 2493 2494]; ! end of NounDomain 2495 2496[ PARSER_CLARIF_INTERNAL_R; ];

Adjudicate.

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

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

(a) if only one obvious choice is found, that is returned; (b) if we are in indefinite mode (don't care which) one of the obvious choices is returned, or if there is no obvious choice then an unobvious one is made; (c) at this stage, we work out whether the objects are distinguishable from each other or not: if they are all indistinguishable from each other, then choose one, it doesn't matter which; (d) otherwise, 0 (meaning, unable to decide) is returned (but remember that the equivalence classes we've just worked out will be needed by other routines to clear up this mess, so we can't economise on working them out).

Adjudicate returns -1 if an error occurred.

2521[ Adjudicate context i j k good_ones last n ultimate flag offset; 2522    #Ifdef DEBUG; 2523    if (parser_trace >= 4) { 2524        print " [Adjudicating match list of size ", number_matched, 2525            " in context ", context, "^"; 2526        print " "; 2527        if (indef_mode) { 2528            print "indefinite type: "; 2529            if (indef_type & OTHER_BIT) print "other "; 2530            if (indef_type & MY_BIT) print "my "; 2531            if (indef_type & THAT_BIT) print "that "; 2532            if (indef_type & PLURAL_BIT) print "plural "; 2533            if (indef_type & LIT_BIT) print "lit "; 2534            if (indef_type & UNLIT_BIT) print "unlit "; 2535            if (indef_owner ~= 0) print "owner:", (name) indef_owner; 2536            new_line; 2537            print " number wanted: "; 2538            if (indef_wanted == INDEF_ALL_WANTED) print "all"; else print indef_wanted; 2539            new_line; 2540            print " most likely GNAs of names: ", indef_cases, "^"; 2541        } 2542        else print "definite object^"; 2543    } 2544    #Endif; ! DEBUG 2545 2546    j = number_matched-1; good_ones = 0; last = match_list-->0; 2547    for (i=0 : i<=j : i++) { 2548        n = match_list-->i; 2549        match_scores-->i = good_ones; 2550        ultimate = ScopeCeiling(n); 2551 2552        if (context==HELD_TOKEN && parent(n)==actor) 2553        { good_ones++; last=n; } 2554        if (context==MULTI_TOKEN && ultimate==ScopeCeiling(actor) 2555            && n~=actor && n hasnt concealed && n hasnt scenery) 2556        { good_ones++; last=n; } 2557        if (context==MULTIHELD_TOKEN && parent(n)==actor) 2558        { good_ones++; last=n; } 2559 2560        if (context==MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN) 2561        { if (advance_warning==-1) 2562            { if (context==MULTIEXCEPT_TOKEN) 2563                { good_ones++; last=n; 2564                 } 2565                if (context==MULTIINSIDE_TOKEN) 2566                { if (parent(n)~=actor) { good_ones++; last=n; } 2567                 } 2568            } 2569            else 2570            { if (context==MULTIEXCEPT_TOKEN && n~=advance_warning) 2571                { good_ones++; last=n; } 2572                if (context==MULTIINSIDE_TOKEN && n in advance_warning) 2573                { good_ones++; last=n; } 2574            } 2575         } 2576        if (context==CREATURE_TOKEN && CreatureTest(n)==1) 2577        { good_ones++; last=n; } 2578         2579        match_scores-->i = 1000*(good_ones - match_scores-->i); 2580    } 2581    if (good_ones == 1) { 2582        if (indef_mode == 1 && indef_type & PLURAL_BIT ~= 0 && 2583            context == MULTI_TOKEN or MULTIHELD_TOKEN or 2584                MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN) { 2585            BeginActivity(DECIDING_WHETHER_ALL_INC_ACT, last); 2586            if ((ForActivity(DECIDING_WHETHER_ALL_INC_ACT, last)) && 2587                (RulebookFailed())) good_ones = 0; 2588            EndActivity(DECIDING_WHETHER_ALL_INC_ACT, last); 2589            if (good_ones == 1) return last; 2590        } else { 2591            return last; 2592        } 2593    } 2594 2595    ! If there is ambiguity about what was typed, but it definitely wasn't 2596    ! animate as required, then return anything; higher up in the parser 2597    ! a suitable error will be given. (This prevents a question being asked.) 2598 2599    if (context == CREATURE_TOKEN && good_ones == 0) return match_list-->0; 2600 2601    if (indef_mode == 0) indef_type=0; 2602 2603    ScoreMatchL(context); 2604    if (number_matched == 0) return -1; 2605 2606    if (indef_mode == 0) { 2607        ! Is there now a single highest-scoring object? 2608        i = SingleBestGuess(); 2609        if (i >= 0) { 2610 2611            #Ifdef DEBUG; 2612            if (parser_trace >= 4) print " Single best-scoring object returned.]^"; 2613            #Endif; ! DEBUG 2614            return i; 2615        } 2616    } 2617 2618    if (indef_mode == 1 && indef_type & PLURAL_BIT ~= 0) { 2619        if (context ~= MULTI_TOKEN or MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN 2620                     or MULTIINSIDE_TOKEN) { 2621            etype = MULTI_PE; 2622            return -1; 2623        } 2624        i = 0; offset = multiple_object-->0; 2625        for (j=BestGuess(): j~=-1 && i<indef_wanted && i+offset<MATCH_LIST_WORDS-1: 2626            j=BestGuess()) { 2627            flag = 0; 2628            BeginActivity(DECIDING_WHETHER_ALL_INC_ACT, j); 2629            if ((ForActivity(DECIDING_WHETHER_ALL_INC_ACT, j)) == 0) { 2630                if (j hasnt concealed && j hasnt worn) flag = 1; 2631                if (context == MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN && parent(j) ~= actor) 2632                    flag = 0; 2633 2634                if (action_to_be == ##Take or ##Remove && parent(j) == actor) 2635                    flag = 0; 2636 2637                k = ChooseObjects(j, flag); 2638 2639                if (k == 1) 2640                    flag = 1; 2641                else { 2642                    if (k == 2) flag = 0; 2643                } 2644            } else { 2645                flag = 0; if (RulebookSucceeded()) flag = 1; 2646            } 2647            EndActivity(DECIDING_WHETHER_ALL_INC_ACT, j); 2648            if (flag == 1) { 2649                i++; multiple_object-->(i+offset) = j; 2650                #Ifdef DEBUG; 2651                if (parser_trace >= 4) print " Accepting it^"; 2652                #Endif; ! DEBUG 2653            } 2654            else { 2655                i = i; 2656                #Ifdef DEBUG; 2657                if (parser_trace >= 4) print " Rejecting it^"; 2658                #Endif; ! DEBUG 2659            } 2660        } 2661        if (i < indef_wanted && indef_wanted < INDEF_ALL_WANTED) { 2662            etype = TOOFEW_PE; multi_wanted = indef_wanted; 2663             if (parser_trace >= 4) print "Too few found^"; 2664            multi_had=i; 2665            return -1; 2666        } 2667        multiple_object-->0 = i+offset; 2668        multi_context = context; 2669        #Ifdef DEBUG; 2670        if (parser_trace >= 4) 2671            print " Made multiple object of size ", i, "]^"; 2672        #Endif; ! DEBUG 2673        return 1; 2674    } 2675 2676    for (i=0 : i<number_matched : i++) match_classes-->i = 0; 2677 2678    n = 1; 2679    for (i=0 : i<number_matched : i++) 2680        if (match_classes-->i == 0) { 2681            match_classes-->i = n++; flag = 0; 2682            for (j=i+1 : j<number_matched : j++) 2683                if (match_classes-->j == 0 && Identical(match_list-->i, match_list-->j) == 1) { 2684                    flag=1; 2685                    match_classes-->j = match_classes-->i; 2686                } 2687            if (flag == 1) match_classes-->i = 1-n; 2688        } 2689     n--; number_of_classes = n; 2690 2691    #Ifdef DEBUG; 2692    if (parser_trace >= 4) { 2693        print " Grouped into ", n, " possibilities by name:^"; 2694        for (i=0 : i<number_matched : i++) 2695            if (match_classes-->i > 0) 2696                print " ", (The) match_list-->i, " (", match_list-->i, ") --- group ", 2697                  match_classes-->i, "^"; 2698    } 2699    #Endif; ! DEBUG 2700 2701    if (indef_mode == 0) { 2702        if (n > 1) { 2703            k = -1; 2704            for (i=0 : i<number_matched : i++) { 2705                if (match_scores-->i > k) { 2706                    k = match_scores-->i; 2707                    j = match_classes-->i; j = j*j; 2708                    flag = 0; 2709                } 2710                else 2711                    if (match_scores-->i == k) { 2712                        if ((match_classes-->i) * (match_classes-->i) ~= j) 2713                            flag = 1; 2714                    } 2715            } 2716 2717        if (flag) { 2718            #Ifdef DEBUG; 2719            if (parser_trace >= 4) print " Unable to choose best group, so ask player.]^"; 2720            #Endif; ! DEBUG 2721            return 0; 2722        } 2723        #Ifdef DEBUG; 2724        if (parser_trace >= 4) print " Best choices are all from the same group.^"; 2725        #Endif; ! DEBUG 2726        } 2727    } 2728 2729    ! When the player is really vague, or there's a single collection of 2730    ! indistinguishable objects to choose from, choose the one the player 2731    ! most recently acquired, or if the player has none of them, then 2732    ! the one most recently put where it is. 2733 2734    if (n == 1) dont_infer = true; 2735    return BestGuess(); 2736 2737]; ! Adjudicate

ReviseMulti.

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

2746[ ReviseMulti second_p i low; 2747    #Ifdef DEBUG; 2748    if (parser_trace >= 4) 2749        print " Revising multiple object list of size ", multiple_object-->0, 2750         " with 2nd ", (name) second_p, "^"; 2751    #Endif; ! DEBUG 2752 2753    if (multi_context == MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN) { 2754        for (i=1,low=0 : i<=multiple_object-->0 : i++) { 2755            if ( (multi_context==MULTIEXCEPT_TOKEN && multiple_object-->i ~= second_p) || 2756                 (multi_context==MULTIINSIDE_TOKEN && multiple_object-->i in second_p)) { 2757                low++; 2758                multiple_object-->low = multiple_object-->i; 2759            } 2760        } 2761        multiple_object-->0 = low; 2762    } 2763 2764    if (multi_context == MULTI_TOKEN && action_to_be == ##Take) { 2765        #Ifdef DEBUG; 2766        if (parser_trace >= 4) print " Token 2 plural case: number with actor ", low, "^"; 2767        #Endif; ! DEBUG 2768        if (take_all_rule == 2) { 2769            for (i=1,low=0 : i<=multiple_object-->0 : i++) { 2770                if (ScopeCeiling(multiple_object-->i) == ScopeCeiling(actor)) { 2771                    low++; 2772                    multiple_object-->low = multiple_object-->i; 2773                } 2774            } 2775            multiple_object-->0 = low; 2776        } 2777    } 2778 2779    i = multiple_object-->0; 2780    #Ifdef DEBUG; 2781    if (parser_trace >= 4) print " Done: new size ", i, "^"; 2782    #Endif; ! DEBUG 2783    if (i == 0) return NOTHING_PE; 2784    return 0; 2785];

Match List.

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

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

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

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

2810[ MakeMatch obj quality i; 2811    #Ifdef DEBUG; 2812    if (parser_trace >= 6) print " Match with quality ",quality,"^"; 2813    #Endif; ! DEBUG 2814    if (token_filter ~= 0 && ConsultNounFilterToken(obj) == 0) { 2815        #Ifdef DEBUG; 2816        if (parser_trace >= 6) print " Match filtered out: token filter ", token_filter, "^"; 2817        #Endif; ! DEBUG 2818        rtrue; 2819    } 2820    if (quality < match_length) rtrue; 2821    if (quality > match_length) { match_length = quality; number_matched = 0; } 2822    else { 2823        if (number_matched >= MATCH_LIST_WORDS) rtrue; 2824        for (i=0 : i<number_matched : i++) 2825            if (match_list-->i == obj) rtrue; 2826    } 2827    match_list-->number_matched++ = obj; 2828    #Ifdef DEBUG; 2829    if (parser_trace >= 6) print " Match added to list^"; 2830    #Endif; ! DEBUG 2831]; 2832 2833[ ConsultNounFilterToken obj sn rv; 2834    if (token_filter ofclass Routine) { 2835        sn = noun; 2836        noun = obj; 2837        rv = indirect(token_filter); 2838        noun = sn; 2839        return rv; 2840    } 2841    if (obj has (token_filter-1)) rtrue; 2842    rfalse; 2843];

ScoreMatchL.

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

2853Constant SCORE__CHOOSEOBJ = 1000; 2854Constant SCORE__IFGOOD = 500; 2855Constant SCORE__UNCONCEALED = 100; 2856Constant SCORE__BESTLOC = 60; 2857Constant SCORE__NEXTBESTLOC = 40; 2858Constant SCORE__NOTCOMPASS = 20; 2859Constant SCORE__NOTSCENERY = 10; 2860Constant SCORE__NOTACTOR = 5; 2861Constant SCORE__GNA = 1; 2862Constant SCORE__DIVISOR = 20; 2863 2864Constant PREFER_HELD; 2865[ ScoreMatchL context its_owner its_score obj i j threshold met a_s l_s; 2866! if (indef_type & OTHER_BIT ~= 0) threshold++; 2867    if (indef_type & MY_BIT ~= 0) threshold++; 2868    if (indef_type & THAT_BIT ~= 0) threshold++; 2869    if (indef_type & LIT_BIT ~= 0) threshold++; 2870    if (indef_type & UNLIT_BIT ~= 0) threshold++; 2871    if (indef_owner ~= nothing) threshold++; 2872 2873    #Ifdef DEBUG; 2874    if (parser_trace >= 4) print " Scoring match list: indef mode ", indef_mode, " type ", 2875      indef_type, ", satisfying ", threshold, " requirements:^"; 2876    #Endif; ! DEBUG 2877 2878    #ifdef PREFER_HELD; 2879    a_s = SCORE__BESTLOC; l_s = SCORE__NEXTBESTLOC; 2880    if (action_to_be == ##Take or ##Remove) { 2881        a_s = SCORE__NEXTBESTLOC; l_s = SCORE__BESTLOC; 2882    } 2883    context = context; ! silence warning 2884    #ifnot; 2885    a_s = SCORE__NEXTBESTLOC; l_s = SCORE__BESTLOC; 2886    if (context == HELD_TOKEN or MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN) { 2887        a_s = SCORE__BESTLOC; l_s = SCORE__NEXTBESTLOC; 2888    } 2889    #endif; ! PREFER_HELD 2890 2891    for (i=0 : i<number_matched : i++) { 2892        obj = match_list-->i; its_owner = parent(obj); its_score=0; met=0; 2893 2894        ! if (indef_type & OTHER_BIT ~= 0 2895        ! && obj ~= itobj or himobj or herobj) met++; 2896        if (indef_type & MY_BIT ~= 0 && its_owner == actor) met++; 2897        if (indef_type & THAT_BIT ~= 0 && its_owner == actors_location) met++; 2898        if (indef_type & LIT_BIT ~= 0 && obj has light) met++; 2899        if (indef_type & UNLIT_BIT ~= 0 && obj hasnt light) met++; 2900        if (indef_owner ~= 0 && its_owner == indef_owner) met++; 2901 2902        if (met < threshold) { 2903            #Ifdef DEBUG; 2904            if (parser_trace >= 4) 2905                print " ", (The) match_list-->i, " (", match_list-->i, ") in ", 2906                    (the) its_owner, " is rejected (doesnt match descriptors)^"; 2907            #Endif; ! DEBUG 2908            match_list-->i = -1; 2909        } 2910        else { 2911            its_score = 0; 2912            if (obj hasnt concealed) its_score = SCORE__UNCONCEALED; 2913 2914            if (its_owner == actor) its_score = its_score + a_s; 2915            else 2916                if (its_owner == actors_location) its_score = its_score + l_s; 2917                else 2918                    if (its_owner ~= compass) its_score = its_score + SCORE__NOTCOMPASS; 2919 2920            its_score = its_score + SCORE__CHOOSEOBJ * ChooseObjects(obj, 2); 2921 2922            if (obj hasnt scenery) its_score = its_score + SCORE__NOTSCENERY; 2923            if (obj ~= actor) its_score = its_score + SCORE__NOTACTOR; 2924 2925            ! A small bonus for having the correct GNA, 2926            ! for sorting out ambiguous articles and the like. 2927 2928            if (indef_cases & (PowersOfTwo_TB-->(GetGNAOfObject(obj)))) 2929                its_score = its_score + SCORE__GNA; 2930 2931            match_scores-->i = match_scores-->i + its_score; 2932            #Ifdef DEBUG; 2933            if (parser_trace >= 4) print " ", (The) match_list-->i, " (", match_list-->i, 2934              ") in ", (the) its_owner, " : ", match_scores-->i, " points^"; 2935            #Endif; ! DEBUG 2936        } 2937     } 2938 2939    for (i=0 : i<number_matched : i++) { 2940        while (match_list-->i == -1) { 2941            if (i == number_matched-1) { number_matched--; break; } 2942            for (j=i : j<number_matched-1 : j++) { 2943                match_list-->j = match_list-->(j+1); 2944                match_scores-->j = match_scores-->(j+1); 2945            } 2946            number_matched--; 2947        } 2948    } 2949];

BestGuess.

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

2958[ BestGuess earliest its_score best i; 2959    earliest = 0; best = -1; 2960    for (i=0 : i<number_matched : i++) { 2961        if (match_list-->i >= 0) { 2962            its_score = match_scores-->i; 2963            if (its_score > best) { best = its_score; earliest = i; } 2964        } 2965    } 2966    #Ifdef DEBUG; 2967    if (parser_trace >= 4) 2968      if (best < 0) print " Best guess ran out of choices^"; 2969      else print " Best guess ", (the) match_list-->earliest, 2970          " (", match_list-->earliest, ")^"; 2971    #Endif; ! DEBUG 2972    if (best < 0) return -1; 2973    i = match_list-->earliest; 2974    match_list-->earliest = -1; 2975    return i; 2976];

SingleBestGuess.

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

2983[ SingleBestGuess earliest its_score best i; 2984    earliest = -1; best = -1000; 2985    for (i=0 : i<number_matched : i++) { 2986        its_score = match_scores-->i; 2987        if (its_score == best) earliest = -1; 2988        if (its_score > best) { best = its_score; earliest = match_list-->i; } 2989    } 2990    return earliest; 2991];

Identical.

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

3002[ Identical o1 o2 p1 p2 n1 n2 i j flag; 3003    if (o1 == o2) rtrue; ! This should never happen, but to be on the safe side 3004    if (o1 == 0 || o2 == 0) rfalse; ! Similarly 3005    if (o1 ofclass K3_direction || o2 ofclass K3_direction) rfalse; ! Saves time 3006 3007    ! What complicates things is that o1 or o2 might have a parsing routine, 3008    ! so the parser can't know from here whether they are or aren't the same. 3009    ! If they have different parsing routines, we simply assume they're 3010    ! different. If they have the same routine (which they probably got from 3011    ! a class definition) then the decision process is as follows: 3012    ! 3013    ! the routine is called (with self being o1, not that it matters) 3014    ! with noun and second being set to o1 and o2, and action being set 3015    ! to the fake action TheSame. If it returns -1, they are found 3016    ! identical; if -2, different; and if >=0, then the usual method 3017    ! is used instead. 3018 3019    if (o1.parse_name ~= 0 || o2.parse_name ~= 0) { 3020      if (o1.parse_name ~= o2.parse_name) rfalse; 3021      parser_action = ##TheSame; parser_one = o1; parser_two = o2; 3022      j = wn; i = RunRoutines(o1,parse_name); wn = j; 3023      if (i == -1) rtrue; 3024      if (i == -2) rfalse; 3025    } 3026 3027    ! This is the default algorithm: do they have the same words in their 3028    ! "name" (i.e. property no. 1) properties. (Note that the following allows 3029    ! for repeated words and words in different orders.) 3030 3031    p1 = o1.&1; n1 = (o1.#1)/WORDSIZE; 3032    p2 = o2.&1; n2 = (o2.#1)/WORDSIZE; 3033 3034    ! for (i=0 : i<n1 : i++) { print (address) p1-->i, " "; } new_line; 3035    ! for (i=0 : i<n2 : i++) { print (address) p2-->i, " "; } new_line; 3036 3037    for (i=0 : i<n1 : i++) { 3038        flag = 0; 3039        for (j=0 : j<n2 : j++) 3040            if (p1-->i == p2-->j) flag = 1; 3041        if (flag == 0) rfalse; 3042    } 3043 3044    for (j=0 : j<n2 : j++) { 3045        flag = 0; 3046        for (i=0 : i<n1 : i++) 3047            if (p1-->i == p2-->j) flag = 1; 3048        if (flag == 0) rfalse; 3049    } 3050 3051    ! print "Which are identical!^"; 3052    rtrue; 3053];

Print Command.

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

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

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

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

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

3074[ PrintInferredCommand from singleton_noun; 3075    singleton_noun = FALSE; 3076    if ((from ~= 0) && (from == pcount-1) && 3077        (pattern-->from > 1) && (pattern-->from < REPARSE_CODE)) 3078            singleton_noun = TRUE; 3079 3080    if (singleton_noun) { 3081        BeginActivity(CLARIFYING_PARSERS_CHOICE_ACT, pattern-->from); 3082        if (ForActivity(CLARIFYING_PARSERS_CHOICE_ACT, pattern-->from) == 0) { 3083            print "("; PrintCommand(from); print ")^"; 3084        } 3085        EndActivity(CLARIFYING_PARSERS_CHOICE_ACT, pattern-->from); 3086    } else { 3087        print "("; PrintCommand(from); print ")^"; 3088    } 3089]; 3090 3091[ PrintCommand from i k spacing_flag; 3092    if (from == 0) { 3093        i = verb_word; 3094        if (LanguageVerb(i) == 0) 3095            if (PrintVerb(i) == 0) print (address) i; 3096        from++; spacing_flag = true; 3097    } 3098    for (k=from : k<pcount : k++) { 3099        i = pattern-->k; 3100        if (i == PATTERN_NULL) continue; 3101        if (spacing_flag) print (char) ' '; 3102        if (i == 0) { PARSER_CLARIF_INTERNAL_RM('F'); jump TokenPrinted; } 3103        if (i == 1) { PARSER_CLARIF_INTERNAL_RM('G'); jump TokenPrinted; } 3104        if (i >= REPARSE_CODE) 3105            print (address) VM_NumberToDictionaryAddress(i-REPARSE_CODE); 3106        else 3107            if (i ofclass K3_direction) 3108                print (LanguageDirection) i; ! the direction name as adverb 3109            else 3110                print (the) i; 3111      .TokenPrinted; 3112        spacing_flag = true; 3113    } 3114];

CantSee.

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

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

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

3131[ CantSee i w e; 3132    saved_oops=oops_from; 3133 3134    if (scope_token ~= 0) { 3135        scope_error = scope_token; return ASKSCOPE_PE; 3136    } 3137 3138    wn--; w = NextWord(); 3139    e = CANTSEE_PE; 3140    if (w == pronoun_word) { 3141        w = NextWordStopped(); wn--; 3142        if ((w == -1) || (line_token-->(pcount) ~= ENDIT_TOKEN)) { 3143            if (pcount > 0) AnalyseToken(line_token-->(pcount-1)); 3144            if ((pcount > 0) && (found_ttype == ROUTINE_FILTER_TT or ATTR_FILTER_TT)) 3145                e = NOTINCONTEXT_PE; 3146            else { 3147                pronoun__word = pronoun_word; pronoun__obj = pronoun_obj; 3148                e = ITGONE_PE; 3149            } 3150        } 3151    } 3152     3153    if (etype > e) return etype; 3154    return e; 3155];

Multiple Object List.

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

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

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

3171[ MultiAdd o i j; 3172    i = multiple_object-->0; 3173    if (i == MATCH_LIST_WORDS-1) { toomany_flag = 1; rtrue; } 3174    for (j=1 : j<=i : j++) 3175        if (o == multiple_object-->j) rtrue; 3176    i++; 3177    multiple_object-->i = o; 3178    multiple_object-->0 = i; 3179]; 3180 3181[ MultiSub o i j k; 3182    i = multiple_object-->0; 3183    for (j=1 : j<=i : j++) 3184        if (o == multiple_object-->j) { 3185            for (k=j : k<=i : k++) multiple_object-->k = multiple_object-->(k+1); 3186            multiple_object-->0 = --i; 3187            return 0; 3188        } 3189    return VAGUE_PE; 3190]; 3191 3192[ MultiFilter attr i j o; 3193    .MFiltl; 3194    i = multiple_object-->0; 3195    for (j=1 : j<=i : j++) { 3196        o = multiple_object-->j; 3197        if (o hasnt attr) { MultiSub(o); jump Mfiltl; } 3198    } 3199];

Scope.

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

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

The scope machinery is built on a number of levels, each making use only of lower levels: (0) Either NounDomain, TestScope or LoopOverScope makes one or more calls to SearchScope (on level 1). The point of making multiple calls is to influence the order in which items in scope are visited, which improves the quality of "take all"-style multiple object lists, for instance. (1) SearchScope searches for the objects in scope which are within first one domain, and then another: for instance, first within the current room but not within the current actor, and then within the current actor. It can be called either from level 0, or externally from the choose-objects machinery, but is not recursive. It works within the context of a given token in the parser (when called for PARSING_REASON) and in particular the multiinside token, and also handles testing commands, scope tokens, scope in darkness, and intervention by the I7 "deciding the scope of" activity. Most of its actual searches are delegated to ScopeWithin (level 2), but it also uses DoScopeActionAndRecurse (level 3) and DoScopeAction (level 4) as necessary. (2) ScopeWithin iterates through the objects in scope which are within one supplied domain, but not within another. It can be called either from level 1, or independently from rules in the "deciding the scope of" activity via the I7 "place the contents of X in scope" phrase. It calls DoScopeActionAndRecurse (level 3) on any unconcealed objects it finds. (3) DoScopeActionAndRecurse visits a given object by calling down to DoScopeAction (level 4), and recurses to all unconcealed object-tree contents and component parts of the object. The I7 phrase "place X in scope" uses this routine. (4) DoScopeAction simply visits a single object, taking whatever action is needed there – which will depend on the scope_reason. The only use made by the parser of TryGivenObject, which tries to match command text against the name of a given object, is from here. The I7 phrase "place X in scope, but not its contents" uses this routine.

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

3253[ PlaceInScope O opts ws; ! If opts is set, do not place contents in scope 3254    ws = wn; wn = match_from; 3255    if (opts == false) DoScopeActionAndRecurse(O); 3256    else DoScopeAction(O); 3257    wn = ws; return; 3258]; 3259 3260[ AddToScope obj; 3261    if (ats_flag >= 2) DoScopeActionAndRecurse(obj, 0, ats_flag-2); 3262    if (ats_flag == 1) { if (HasLightSource(obj)==1) ats_hls = 1; } 3263];

Scope Level 0.

The two ways of starting up the scope machinery other than via the parser code above.

3270[ TestScope obj act a al sr x y; 3271    x = parser_one; y = parser_two; 3272    parser_one = obj; parser_two = 0; a = actor; al = actors_location; 3273    sr = scope_reason; scope_reason = TESTSCOPE_REASON; 3274    if (act == 0) actor = player; else actor = act; 3275    actors_location = ScopeCeiling(actor); 3276    SearchScope(actors_location, actor, 0); scope_reason = sr; actor = a; 3277    actors_location = al; parser_one = x; x = parser_two; parser_two = y; 3278    return x; 3279]; 3280 3281[ LoopOverScope routine act x y a al; 3282    x = parser_one; y = scope_reason; a = actor; al = actors_location; 3283    parser_one = routine; 3284    if (act == 0) actor = player; else actor = act; 3285    actors_location = ScopeCeiling(actor); 3286    scope_reason = LOOPOVERSCOPE_REASON; 3287    SearchScope(actors_location, actor, 0); 3288    parser_one = x; scope_reason = y; actor = a; actors_location = al; 3289];

SearchScope.

Level 1. The method is: (a) If the context is a scope=... token, then the search is delegated to "stage 2" of the scope routine. This was the old I6 way to override the searching behaviour: while users probably won't be using it any more, the template does, in order to give testing commands universal scope which is exempt from the activity below; and the NI compiler creates scope=... tokens to handle Understand grammar such as "[any room]". So the feature remains very much still in use. (b) The "deciding the scope of" activity is given the chance to intervene. This is the I7 way to override the searching behaviour, and is the one taken by users. (c) And otherwise: (-1) The I6 multiinside token, used as the first noun of its grammar line, has as its scope all of the objects which are inside or on top of the second noun of the grammar line. This provides a neat scope for the ALL in a command like GET ALL FROM CUPBOARD, where the player clearly does not intend ALL to refer to the cupboard itself, for instance. The difficulty is that we don't yet know what the second object is, if we are parsing left to right. But the parser code above has taken care of all of that, and the advance_warning global is set to the object number of the second noun, or to -1 if that is not yet known. Note that we check that the contents are visible before adding them to scope, because otherwise an unscrupulous player could use such a command to detect the contents of an opaque locked box. If this rule applies, we skip (c.2), (c.3) and (c.4). (-2) For all other tokens except creature, searching scope for the room holding the current actor always catches the compass directions unless a definite article has already been typed. (Thus OPEN THE EAST would match an object called "east door", but not the compass direction "east".) (-3) The contents of domain1 which are not contents of domain2 are placed in scope, and so are any component parts of domain1. If domain1 is a container or supporter, it is placed in scope itself. (-4) The contents and component parts of domain2 are placed in scope. If domain2 is a container or supporter, it is placed in scope itself. (-5) In darkness, the actor and his component parts are in scope. If the actor is inside or on top of something, then that thing is also in scope. (This avoids a situation where the player gets into an opaque box, then pulls it closed from the inside, plunging himself into darkness, then types OPEN BOX only to be told that he can't see any such thing.)

3332[ SearchScope domain1 domain2 context i; 3333    if (domain1 == 0) return; 3334    ! (a) 3335    if (scope_token) { 3336        scope_stage = 2; 3337        #Ifdef DEBUG; 3338        if (parser_trace >= 3) print " [Scope routine called at stage 2]^"; 3339        #Endif; 3340        if (indirect(scope_token) ~= 0) rtrue; 3341    } 3342    ! (b) 3343    BeginActivity(DECIDING_SCOPE_ACT, actor); 3344    if (ForActivity(DECIDING_SCOPE_ACT, actor) == false) { 3345        ! (c.1) 3346        if ((scope_reason == PARSING_REASON) && (context == MULTIINSIDE_TOKEN) && 3347            (advance_warning ~= -1)) { 3348            if (IsSeeThrough(advance_warning) == 1) 3349                ScopeWithin(advance_warning, 0, context); 3350        } else { 3351            ! (c.2) 3352            if ((scope_reason == PARSING_REASON) && (context ~= CREATURE_TOKEN) && 3353                (indef_mode == 0) && (domain1 == actors_location)) 3354                    ScopeWithin(compass); 3355            ! (c.3) 3356            if (domain1 has supporter or container) DoScopeAction(domain1); 3357            ScopeWithin(domain1, domain2, context); 3358            ! (c.4) 3359            if (domain2) { 3360                if (domain2 has supporter or container) DoScopeAction(domain2); 3361                ScopeWithin(domain2, 0, context); 3362            } 3363        } 3364        ! (c.5) 3365        if (thedark == domain1 or domain2) { 3366            DoScopeActionAndRecurse(actor, actor, context); 3367            if (parent(actor) has supporter or container) 3368                DoScopeActionAndRecurse(parent(actor), parent(actor), context); 3369        } 3370    } 3371    EndActivity(DECIDING_SCOPE_ACT, actor); 3372];

ScopeWithin.

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

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

3387[ ScopeWithin domain nosearch context obj next_obj; 3388    if (domain == 0) rtrue; 3389 3390    ! Look through the objects in the domain, avoiding "objectloop" in case 3391    ! movements occur. 3392    obj = child(domain); 3393    while (obj) { 3394        next_obj = sibling(obj); 3395        if ((domain == actor) || (TestConcealment(domain, obj) == false)) 3396            DoScopeActionAndRecurse(obj, nosearch, context); 3397        obj = next_obj; 3398    } 3399];

DoScopeActionAndRecurse.

Level 3. In all cases, the domain itself is visited. There are then three possible forms of recursion: (a) To unconcealed objects which are inside, on top of, carried or worn by the domain: this is called "searching" in traditional I6 language and is suppressed if domain is the special value nosearch. (b) To unconcealed component parts of the domain. (c) To any other objects listed in the add_to_scope property array, or supplied by the add_to_scope property routine, if it has one. (I7 does not usually use add_to_scope, but it remains a useful hook in the parser, so it retains its old I6 library interpretation.)

3415[ DoScopeActionAndRecurse domain nosearch context i ad n obj next_obj; 3416    DoScopeAction(domain); 3417 3418     ! (a) 3419    if ((domain ~= nosearch) && 3420        ((domain ofclass K1_room or K8_person) || (IsSeeThrough(domain) == 1))) { 3421        obj = child(domain); 3422        while (obj) { 3423            next_obj = sibling(obj); 3424            if ((domain == actor) || (TestConcealment(domain, obj) == false)) 3425                DoScopeActionAndRecurse(obj, nosearch, context); 3426            obj = next_obj; 3427        } 3428    } 3429 3430    ! (b) 3431    if (domain provides component_child) { 3432        obj = domain.component_child; 3433        while (obj) { 3434            next_obj = obj.component_sibling; 3435            if ((domain == actor) || (TestConcealment(domain, obj) == false)) 3436                DoScopeActionAndRecurse(obj, 0, context); 3437            obj = next_obj; 3438        } 3439    } 3440 3441    ! (c) 3442    ad = domain.&add_to_scope; 3443    if (ad ~= 0) { 3444        ! Test if the property value is not an object. 3445        #Ifdef TARGET_ZCODE; 3446        i = (UnsignedCompare(ad-->0, top_object) > 0); 3447        #Ifnot; ! TARGET_GLULX 3448        i = (((ad-->0)->0) ~= $70); 3449        #Endif; ! TARGET_ 3450 3451        if (i) { 3452            ats_flag = 2+context; 3453            RunRoutines(domain, add_to_scope); 3454            ats_flag = 0; 3455        } 3456        else { 3457            n = domain.#add_to_scope; 3458            for (i=0 : (WORDSIZE*i)<n : i++) 3459                if (ad-->i) 3460                    DoScopeActionAndRecurse(ad-->i, 0, context); 3461        } 3462    } 3463];

DoScopeAction.

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

3471[ DoScopeAction item; 3472 3473    #Ifdef DEBUG; 3474    if (parser_trace >= 6) 3475        print "[DSA on ", (the) item, " with reason = ", scope_reason, 3476            " p1 = ", parser_one, " p2 = ", parser_two, "]^"; 3477    #Endif; ! DEBUG 3478 3479    @push parser_one; @push scope_reason; 3480 3481    switch(scope_reason) { 3482        TESTSCOPE_REASON: if (item == parser_one) parser_two = 1; 3483        LOOPOVERSCOPE_REASON: if (parser_one ofclass Routine) indirect(parser_one, item); 3484        PARSING_REASON, TALKING_REASON: MatchTextAgainstObject(item); 3485    } 3486 3487    @pull scope_reason; @pull parser_one; 3488];

Parsing Object Names.

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

3499[ MatchTextAgainstObject item i; 3500    if (token_filter ~= 0 && ConsultNounFilterToken(item) == 0) return; 3501 3502    if (match_from <= num_words) { ! If there's any text to match, that is 3503        wn = match_from; 3504        i = NounWord(); 3505        if ((i == 1) && (player == item)) MakeMatch(item, 1); ! "me" 3506        if ((i >= 2) && (i < 128) && (LanguagePronouns-->i == item)) MakeMatch(item, 1); 3507    } 3508 3509    ! Construing the current word as the start of a noun, can it refer to the 3510    ! object? 3511 3512    wn = match_from; 3513    if (TryGivenObject(item) > 0) 3514        if (indef_nspec_at > 0 && match_from ~= indef_nspec_at) { 3515            ! This case arises if the player has typed a number in 3516            ! which is hypothetically an indefinite descriptor: 3517            ! e.g. "take two clubs". We have just checked the object 3518            ! against the word "clubs", in the hope of eventually finding 3519            ! two such objects. But we also backtrack and check it 3520            ! against the words "two clubs", in case it turns out to 3521            ! be the 2 of Clubs from a pack of cards, say. If it does 3522            ! match against "two clubs", we tear up our original 3523            ! assumption about the meaning of "two" and lapse back into 3524            ! definite mode. 3525 3526            wn = indef_nspec_at; 3527            if (TryGivenObject(item) > 0) { 3528                match_from = indef_nspec_at; 3529                ResetDescriptors(); 3530            } 3531            wn = match_from; 3532        } 3533];

TryGivenObject.

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

3542[ TryGivenObject obj nomatch threshold k w j; 3543    #Ifdef DEBUG; 3544    if (parser_trace >= 5) print " Trying ", (the) obj, " (", obj, ") at word ", wn, "^"; 3545    #Endif; ! DEBUG 3546 3547    if (nomatch && obj == 0) return 0; 3548 3549! if (nomatch) print "*** TryGivenObject *** on ", (the) obj, " at wn = ", wn, "^"; 3550 3551    dict_flags_of_noun = 0; 3552 3553! If input has run out then always match, with only quality 0 (this saves 3554! time). 3555 3556    if (wn > num_words) { 3557        if (nomatch) return 0; 3558        if (indef_mode ~= 0) 3559            dict_flags_of_noun = $$01110000; ! Reject "plural" bit 3560        MakeMatch(obj,0); 3561        #Ifdef DEBUG; 3562        if (parser_trace >= 5) print " Matched (0)^"; 3563        #Endif; ! DEBUG 3564        return 1; 3565    } 3566 3567! Ask the object to parse itself if necessary, sitting up and taking notice 3568! if it says the plural was used: 3569 3570    if (obj.parse_name~=0) { 3571        parser_action = NULL; j=wn; 3572        k = RunRoutines(obj,parse_name); 3573        if (k > 0) { 3574            wn=j+k; 3575 3576          .MMbyPN; 3577 3578            if (parser_action == ##PluralFound) 3579                dict_flags_of_noun = dict_flags_of_noun | 4; 3580 3581            if (dict_flags_of_noun & 4) { 3582                if (~~allow_plurals) k = 0; 3583                else { 3584                    if (indef_mode == 0) { 3585                        indef_mode = 1; indef_type = 0; indef_wanted = 0; 3586                    } 3587                    indef_type = indef_type | PLURAL_BIT; 3588                    if (indef_wanted == 0) indef_wanted = INDEF_ALL_WANTED; 3589                } 3590            } 3591 3592            #Ifdef DEBUG; 3593            if (parser_trace >= 5) print " Matched (", k, ")^"; 3594            #Endif; ! DEBUG 3595            if (nomatch == false) MakeMatch(obj,k); 3596            return k; 3597        } 3598        if (k == 0) jump NoWordsMatch; 3599    } 3600 3601    ! The default algorithm is simply to count up how many words pass the 3602    ! Refers test: 3603 3604    parser_action = NULL; 3605 3606    w = NounWord(); 3607 3608    if (w == 1 && player == obj) { k=1; jump MMbyPN; } 3609 3610    if (w >= 2 && w < 128 && (LanguagePronouns-->w == obj)) { k = 1; jump MMbyPN; } 3611 3612    if (Refers(obj, wn-1) == 0) { 3613        .NoWordsMatch; 3614        if (indef_mode ~= 0) { k = 0; parser_action = NULL; jump MMbyPN; } 3615        rfalse; 3616    } 3617 3618    threshold = 1; 3619    dict_flags_of_noun = (w->#dict_par1) & $$01110100; 3620    w = NextWord(); 3621    while (Refers(obj, wn-1)) { 3622        threshold++; 3623        if (w) 3624           dict_flags_of_noun = dict_flags_of_noun | ((w->#dict_par1) & $$01110100); 3625        w = NextWord(); 3626    } 3627 3628    k = threshold; 3629    jump MMbyPN; 3630];

Refers.

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

3639[ Refers obj wnum wd k l m; 3640    if (obj == 0) rfalse; 3641 3642    #Ifdef LanguageRefers; 3643    k = LanguageRefers(obj,wnum); if (k >= 0) return k; 3644    #Endif; ! LanguageRefers 3645 3646    k = wn; wn = wnum; wd = NextWordStopped(); wn = k; 3647 3648    if (parser_inflection >= 256) { 3649        k = indirect(parser_inflection, obj, wd); 3650        if (k >= 0) return k; 3651        m = -k; 3652    } 3653    else 3654        m = parser_inflection; 3655    k = obj.&m; l = (obj.#m)/WORDSIZE-1; 3656    for (m=0 : m<=l : m++) 3657        if (wd == k-->m) rtrue; 3658    rfalse; 3659]; 3660 3661[ WordInProperty wd obj prop k l m; 3662    k = obj.&prop; l = (obj.#prop)/WORDSIZE-1; 3663    for (m=0 : m<=l : m++) 3664        if (wd == k-->m) rtrue; 3665    rfalse; 3666];

NounWord.

NounWord (which takes no arguments) returns: (a) 0 if the next word is not in the dictionary or is but does not carry the "noun" bit in its dictionary entry, (b) 1 if it is a word meaning "me", (c) the index in the pronoun table (plus 2) of the value field of a pronoun, if it is a pronoun, (d) the address in the dictionary if it is a recognised noun.

3678[ NounWord i j s; 3679    i = NextWord(); 3680    if (i == 0) rfalse; 3681    if (i == ME1__WD or ME2__WD or ME3__WD) return 1; 3682    s = LanguagePronouns-->0; 3683    for (j=1 : j<=s : j=j+3) 3684        if (i == LanguagePronouns-->j) 3685            return j+2; 3686    if ((i->#dict_par1)&128 == 0) rfalse; 3687    return i; 3688];

TryNumber.

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

(a) -1000 if it is not a number (b) the number, if it has between 1 and 4 digits (c) 10000 if it has 5 or more digits.

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

3705[ TryNumber wordnum i j c num len mul tot d digit; 3706    i = wn; wn = wordnum; j = NextWord(); wn = i; 3707    j = NumberWord(j); ! Test for verbal forms ONE to THIRTY 3708    if (j >= 1) return j; 3709 3710    #Ifdef TARGET_ZCODE; 3711    i = wordnum*4+1; j = parse->i; num = j+buffer; len = parse->(i-1); 3712    #Ifnot; ! TARGET_GLULX 3713    i = wordnum*3; j = parse-->i; num = j+buffer; len = parse-->(i-1); 3714    #Endif; ! TARGET_ 3715 3716    if (len >= 4) mul=1000; 3717    if (len == 3) mul=100; 3718    if (len == 2) mul=10; 3719    if (len == 1) mul=1; 3720 3721    tot = 0; c = 0; len = len-1; 3722 3723    for (c=0 : c<=len : c++) { 3724        digit=num->c; 3725        if (digit == '0') { d = 0; jump digok; } 3726        if (digit == '1') { d = 1; jump digok; } 3727        if (digit == '2') { d = 2; jump digok; } 3728        if (digit == '3') { d = 3; jump digok; } 3729        if (digit == '4') { d = 4; jump digok; } 3730        if (digit == '5') { d = 5; jump digok; } 3731        if (digit == '6') { d = 6; jump digok; } 3732        if (digit == '7') { d = 7; jump digok; } 3733        if (digit == '8') { d = 8; jump digok; } 3734        if (digit == '9') { d = 9; jump digok; } 3735        return -1000; 3736     .digok; 3737        tot = tot+mul*d; mul = mul/10; 3738    } 3739    if (len > 3) tot=10000; 3740    return tot; 3741];

Gender.

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

For GNAs – gender/noun/animation combinations – see the Inform Designer's Manual, 4th edition.

3752[ GetGender person; 3753    if (person hasnt female) rtrue; 3754    rfalse; 3755]; 3756 3757[ GetGNAOfObject obj case gender; 3758    if (obj hasnt animate) case = 6; 3759    if (obj has male) gender = male; 3760    if (obj has female) gender = female; 3761    if (obj has neuter) gender = neuter; 3762    if (gender == 0) { 3763        if (case == 0) gender = LanguageAnimateGender; 3764        else gender = LanguageInanimateGender; 3765    } 3766    if (gender == female) case = case + 1; 3767    if (gender == neuter) case = case + 2; 3768    if (obj has pluralname) case = case + 3; 3769    return case; 3770];

Noticing Plurals.

3775[ DetectPluralWord at n i w swn outcome; 3776    swn = wn; wn = at; 3777    for (i=0:i<n:i++) { 3778        w = NextWordStopped(); 3779        if (w == 0 or THEN1__WD or COMMA_WORD or -1) break; 3780        if ((w->#dict_par1) & $$00000100) { 3781            parser_action = ##PluralFound; 3782            outcome = true; 3783        } 3784    } 3785    wn = swn; 3786    return outcome; 3787];

Pronoun Handling.

3792[ SetPronoun dword value x; 3793    for (x=1 : x<=LanguagePronouns-->0 : x=x+3) 3794        if (LanguagePronouns-->x == dword) { 3795            LanguagePronouns-->(x+2) = value; return; 3796        } 3797    RunTimeError(14); 3798]; 3799 3800[ PronounValue dword x; 3801    for (x=1 : x<=LanguagePronouns-->0 : x=x+3) 3802        if (LanguagePronouns-->x == dword) 3803            return LanguagePronouns-->(x+2); 3804    return 0; 3805]; 3806 3807[ ResetVagueWords obj; PronounNotice(obj); ]; 3808 3809[ PronounNotice obj x bm g; 3810    if (obj == player) return; 3811 3812    g = (GetGNAOfObject(obj)); 3813 3814    bm = PowersOfTwo_TB-->g; 3815    for (x=1 : x<=LanguagePronouns-->0 : x=x+3) 3816        if (bm & (LanguagePronouns-->(x+1)) ~= 0) 3817            LanguagePronouns-->(x+2) = obj; 3818 3819    if (((g % 6) < 3) && (obj has ambigpluralname)) { 3820        g = g + 3; 3821        bm = PowersOfTwo_TB-->g; 3822        for (x=1 : x<=LanguagePronouns-->0 : x=x+3) 3823            if (bm & (LanguagePronouns-->(x+1)) ~= 0) 3824                LanguagePronouns-->(x+2) = obj; 3825    } 3826]; 3827 3828[ PronounNoticeHeldObjects x; 3829#IFNDEF MANUAL_PRONOUNS; 3830    objectloop(x in player) PronounNotice(x); 3831#ENDIF; 3832    x = 0; ! To prevent a "not used" error 3833    rfalse; 3834];

Yes/No Questions.

3839[ YesOrNo i j; 3840    for (::) { 3841        #Ifdef TARGET_ZCODE; 3842        if (location == nothing || parent(player) == nothing) read buffer2 parse2; 3843        else read buffer2 parse2 DrawStatusLine; 3844        j = parse2->1; 3845        #Ifnot; ! TARGET_GLULX; 3846        if (location ~= nothing && parent(player) ~= nothing) DrawStatusLine(); 3847        KeyboardPrimitive(buffer2, parse2); 3848        j = parse2-->0; 3849        #Endif; ! TARGET_ 3850        if (j) { ! at least one word entered 3851            i = parse2-->1; 3852            if (i == YES1__WD or YES2__WD or YES3__WD) rtrue; 3853            if (i == NO1__WD or NO2__WD or NO3__WD) rfalse; 3854        } 3855        YES_OR_NO_QUESTION_INTERNAL_RM('A'); print "> "; 3856    } 3857]; 3858 3859[ YES_OR_NO_QUESTION_INTERNAL_R; ];

Number Words.

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

3866[ NumberWord o i n; 3867    n = LanguageNumbers-->0; 3868    for (i=1 : i<=n : i=i+2) 3869        if (o == LanguageNumbers-->i) return LanguageNumbers-->(i+1); 3870    return 0; 3871];

Choose Objects.

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

3883!Constant COBJ_DEBUG; 3884 3885! the highest value returned by CheckDPMR (see the Standard Rules) 3886Constant HIGHEST_DPMR_SCORE = 4; 3887 3888Array alt_match_list --> (MATCH_LIST_WORDS+1); 3889 3890#ifdef TARGET_GLULX; 3891[ COBJ__Copy words from to i; 3892    for (i=0: i<words: i++) 3893        to-->i = from-->i; 3894]; 3895#ifnot; 3896[ COBJ__Copy words from to bytes; 3897    bytes = words * 2; 3898    @copy_table from to bytes; 3899]; 3900#endif; 3901 3902! swap alt_match_list with match_list/number_matched 3903[ COBJ__SwapMatches i x; 3904    ! swap the counts 3905    x = number_matched; 3906    number_matched = alt_match_list-->0; 3907    alt_match_list-->0 = x; 3908    ! swap the values 3909    if (x < number_matched) x = number_matched; 3910    for (i=x: i>0: i--) { 3911        x = match_list-->(i-1); 3912        match_list-->(i-1) = alt_match_list-->i; 3913        alt_match_list-->i = x; 3914    } 3915]; 3916 3917[ ChooseObjects obj code l i swn spcount; 3918    if (code<2) rfalse; 3919 3920    if (cobj_flag == 1) { 3921        .CodeOne; 3922        if (parameters > 0) { 3923            #ifdef COBJ_DEBUG; 3924            print "[scoring ", (the) obj, " (second)]^"; 3925            #endif; 3926            return ScoreDabCombo(parser_results-->INP1_PRES, obj); 3927        } else { 3928            #ifdef COBJ_DEBUG; 3929            print "[scoring ", (the) obj, " (first) in ", 3930                alt_match_list-->0, " combinations]^"; 3931            #endif; 3932            l = 0; 3933            for (i=1: i<=alt_match_list-->0: i++) { 3934                spcount = ScoreDabCombo(obj, alt_match_list-->i); 3935                if (spcount == HIGHEST_DPMR_SCORE) { 3936                    #ifdef COBJ_DEBUG; 3937                    print "[scored ", spcount, " - best possible]^"; 3938                    #endif; 3939                    return spcount; 3940                } 3941                if (spcount>l) l = spcount; 3942            } 3943            return l; 3944        } 3945    } 3946    if (cobj_flag == 2) { 3947        .CodeTwo; 3948        #ifdef COBJ_DEBUG; 3949        print "[scoring ", (the) obj, " (simple); parameters = ", parameters, 3950            " aw = ", advance_warning, "]^"; 3951        #endif; 3952        @push action_to_be; 3953        if (parameters==0) { 3954            if (advance_warning > 0) 3955                l = ScoreDabCombo(obj, advance_warning); 3956            else 3957                l = ScoreDabCombo(obj, 0); 3958        } else { 3959            l = ScoreDabCombo(parser_results-->INP1_PRES, obj); 3960        } 3961        @pull action_to_be; 3962        return l; 3963    } 3964 3965    #ifdef COBJ_DEBUG; 3966    print "[choosing a cobj strategy: "; 3967    #endif; 3968    swn = wn; 3969    spcount = pcount; 3970    while (line_ttype-->pcount == PREPOSITION_TT) pcount++; 3971    if (line_ttype-->pcount == ELEMENTARY_TT) { 3972        if (line_tdata-->pcount == TOPIC_TOKEN) { 3973            pcount = spcount; 3974            jump CodeTwo; 3975        } 3976        while (wn <= num_words) { 3977            l = NextWordStopped(); wn--; 3978            if (l == THEN1__WD) break; 3979            if ( (l ~= -1 or 0) && (l->#dict_par1) &8 ) { wn++; continue; } ! if preposition 3980            if (l == ALL1__WD or ALL2__WD or ALL3__WD or ALL4__WD or ALL5__WD) { wn++; continue; } 3981            SafeSkipDescriptors(); 3982            ! save the current match state 3983            @push match_length; @push token_filter; @push match_from; 3984            alt_match_list-->0 = number_matched; 3985            COBJ__Copy(number_matched, match_list, alt_match_list+WORDSIZE); 3986            ! now get all the matches for the second noun 3987            match_length = 0; number_matched = 0; match_from = wn; 3988            token_filter = 0; 3989            SearchScope(actor, actors_location, line_tdata-->pcount); 3990            #ifdef COBJ_DEBUG; 3991            print number_matched, " possible second nouns]^"; 3992            #endif; 3993            wn = swn; 3994            cobj_flag = 1; 3995            ! restore match variables 3996            COBJ__SwapMatches(); 3997            @pull match_from; @pull token_filter; @pull match_length; 3998            pcount = spcount; 3999            jump CodeOne; 4000        } 4001    } 4002    pcount = spcount; 4003    wn = swn; 4004     4005    #ifdef COBJ_DEBUG; 4006    print "nothing interesting]^"; 4007    #endif; 4008    cobj_flag = 2; 4009    jump CodeTwo; 4010]; 4011 4012[ ScoreDabCombo a b result; 4013    @push action; @push act_requester; @push noun; @push second; 4014    action = action_to_be; 4015    act_requester = player; 4016    if (action_reversed) { noun = b; second = a; } 4017    else { noun = a; second = b; } 4018    result = CheckDPMR(); 4019    @pull second; @pull noun; @pull act_requester; @pull action; 4020    #ifdef COBJ_DEBUG; 4021    print "[", (the) a, " / ", (the) b, " => ", result, "]^"; 4022    #endif; 4023    return result; 4024];

Default Topic.

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

4030[ DefaultTopic; return GPR_FAIL; ]; 4031