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;
30Global nextbest_etype;
31
32Global parser_inflection;
33
34Array pattern --> 32;
35Global pcount;
36Array pattern2 --> 32;
37Global pcount2;
38
39Array line_ttype-->32;
40Array line_tdata-->32;
41Array line_token-->32;
42
43Global nsns;
44
45Global params_wanted;
46
47Global inferfrom;
48Global inferword;
49Global dont_infer;
50
51Global cobj_flag = 0;
52
53Global oops_from;
54Global saved_oops;
55Array oops_workspace -> 64;
56
57Global held_back_mode;
58Global hb_wn;
59
60
61Global usual_grammar_after;
62
Grammar Token Variables.
More globals, but dealing at the level of individual tokens now.
68Constant PATTERN_NULL = $ffff;
69
70Global found_ttype;
71Global found_tdata;
72Global token_filter;
73
74Global length_of_noun;
75
76Global lookahead;
77
78Global multi_mode;
79Global multi_wanted;
80Global multi_had;
81Global multi_context;
82
83Global indef_mode;
84
85Global indef_type;
86Global indef_wanted;
87Constant INDEF_ALL_WANTED = 32767;
88Global indef_guess_p;
89Global indef_owner;
90Global indef_cases;
91Global indef_possambig;
92
93Global indef_nspec_at;
94
95Global allow_plurals;
96
97Global take_all_rule;
98
99
100
101
102Global dict_flags_of_noun;
103
104Global pronoun__word;
105Global pronoun__obj;
106
107Constant comma_word = 'comma,';
108
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;
118Array match_classes --> MATCH_LIST_WORDS;
119Array match_scores --> MATCH_LIST_WORDS;
120Global number_matched;
121Global number_of_classes;
122Global match_length;
123Global match_from;
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;
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];
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;
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
381 for (i=0 : i<64 : i++) oops_workspace->i = a_buffer->i;
382
383
384
385 #Ifdef TARGET_ZCODE;
386 a_buffer->0 = INPUT_BUFFER_LEN;
387 a_table->0 = 15;
388 #Endif;
389
390
391 PrintPrompt();
392 DrawStatusLine();
393 KeyboardPrimitive(a_buffer, a_table);
394
395
396 #Ifdef TARGET_ZCODE; nw = a_table->1; #Ifnot; nw = a_table-->0; #Endif;
397
398
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
412
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
421
422
423 for (i=0 : i<INPUT_BUFFER_LEN : i++) buffer2->i = a_buffer->i;
424 #Ifdef TARGET_ZCODE;
425 x1 = a_table->9;
426 x2 = a_table->8;
427 #Ifnot;
428 x1 = a_table-->6;
429 x2 = a_table-->5;
430 #Endif;
431
432
433
434 for (i=0 : i<64 : i++) a_buffer->i = oops_workspace->i;
435 VM_Tokenise(a_buffer,a_table);
436
437
438 #Ifdef TARGET_ZCODE;
439 w = a_table->(4*oops_from + 1);
440 w2 = a_table->(4*oops_from);
441 #Ifnot;
442 w = a_table-->(3*oops_from);
443 w2 = a_table-->(3*oops_from - 1);
444 #Endif;
445
446
447 for (i=0 : i<w2 : i++) a_buffer->(i+w) = ' ';
448
449 if (w2 < x2) {
450
451 for (i=INPUT_BUFFER_LEN-1 : i>=w+x2 : i--)
452 a_buffer->i = a_buffer->(i-x2+w2);
453
454
455 #Ifdef TARGET_ZCODE;
456 a_buffer->1 = (a_buffer->1) + (x2-w2);
457 #Ifnot;
458 a_buffer-->0 = (a_buffer-->0) + (x2-w2);
459 #Endif;
460 }
461
462
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
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
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
541
542
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
568
569
570 num_words = WordCount(); players_command = 100 + num_words;
571 wn = 1; inferred_go = false;
572
573 #Ifdef LanguageToInformese;
574 LanguageToInformese();
575
576 VM_Tokenise(buffer,parse);
577 #Endif;
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;
590 j = parse-->(i*3 + 1);
591 #Endif;
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;
604 if (j->0 == $60) print (address) j;
605 else print j;
606 #Endif;
607 }
608 if (i ~= num_words-1) print " / ";
609 }
610 print " ]^";
611 }
612 #Endif;
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
624
625 .BeginCommand;
626
627 wn = verb_wordnum;
628 verb_word = NextWordStopped();
629
630
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
640
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;
654 if (buffer3-->0 == 0) {
655 PARSER_COMMAND_INTERNAL_RM('D'); new_line;
656 jump ReType;
657 }
658 #Endif;
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
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;
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;
706
707
708
709
710 if (verb_word == 0 || ((verb_word->#dict_par1) & 1) == 0) {
711
712
713
714
715
716
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
726
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 }
Parser Letter C.
Is anyone being addressed?
742
743
744
745
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
756
757
758 .Conversation;
759
760 j = wn - 1;
761
762
763
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
778
779
780
781 if (l hasnt animate && l hasnt talkable) {
782 best_etype = ANIMALISTEN_PE; noun = l; jump GiveError;
783 }
784
785
786
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
794
795 PronounNotice(l);
796
797
798
799
800 verb_wordnum = j + 1;
801
802
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;
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
834
835
836 meta = ((verb_word->#dict_par1) & 2)/2;
837
838
839
840 if (meta == 1 && actor ~= player) {
841 best_etype = VERB_PE;
842 meta = 0;
843 jump GiveError;
844 }
845
846
847
848 i = DictionaryWordToVerbNum(verb_word);
849
850
851
852
853
854 #Ifdef TARGET_ZCODE;
855 syntax = (HDR_STATICMEMORY-->0)-->i;
856 #Ifnot;
857 syntax = (#grammar_table)-->(i+1);
858 #Endif;
859
860
861
862
863 num_lines = (syntax->0) - 1;
864
865
866
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;
874
875 best_etype = STUCK_PE; nextbest_etype = STUCK_PE;
876 multiflag = false;
877
878
879
880
881
882
883
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
894
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;
905
906
907
908
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
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
945
946
947 #Ifdef DEBUG;
948 if (parser_trace >= 2) print " [Trying look-ahead]^";
949 #Endif;
950
951
952
953 pcount++;
954 if (line_ttype-->pcount == PREPOSITION_TT) {
955
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
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
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
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
1001 wn--;
1002
1003 if ((line_ttype-->pcount == ELEMENTARY_TT) &&
1004 (line_tdata-->pcount == NOUN_TOKEN)) {
1005 l = Descriptors();
1006 if (l~=0) etype=l;
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;
1022 if (l == REPARSE_CODE) jump ReParse;
1023 if (l >= 2) advance_warning = l;
1024 }
1025 }
1026 break;
1027 }
1028 }
1029 }
1030
1031
1032
1033
1034
1035 take_all_rule = 0;
1036 if (m && params_wanted == 1 && action_to_be == ##Take)
1037 take_all_rule = 1;
1038
1039
1040
1041
1042
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;
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;
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;
1133
1134 if (l == REPARSE_CODE) jump ReParse;
1135 if (l == false) break;
1136 }
1137 else {
1138
1139
1140
1141
1142
1143
1144
1145
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
1160
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
1185
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;
1195
1196
1197
1198 oops_from = 0;
1199
1200
1201
1202 if (inferfrom ~= 0) {
1203 PrintInferredCommand(inferfrom);
1204 ClearParagraphing(20);
1205 }
1206
1207
1208
1209 parser_results-->ACTION_PRES = action_to_be;
1210 parser_results-->NO_INPS_PRES = parameters;
1211
1212
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
1225
1226
1227 if (parameters > 0 && parser_results-->INP1_PRES >= 2)
1228 PronounNotice(parser_results-->INP1_PRES);
1229
1230
1231
1232
1233 if (held_back_mode) {
1234 wn=hb_wn;
1235 jump LookForMore;
1236 }
1237 rtrue;
1238
1239 }
1240 }
1241
1242 .LineFailed;
1243
1244
1245
1246 if (etype > best_etype) best_etype = etype;
1247 if (etype ~= ASKSCOPE_PE && etype > nextbest_etype) nextbest_etype = etype;
1248
1249
1250
1251
1252 if (take_all_rule == 2 && etype==NOTHING_PE) break;
1253
1254 }
1255
1256
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
1306
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();
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;
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
1384
1385 jump ReType;
1386
1387
1388
Parser Letter K.
Last thing: check for THEN and further instructions(s), return.
1394
1395
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;
1417];
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
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
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;
1521 if (l == -1000) {
1522 #Ifdef DEBUG;
1523 if (parser_trace >= 3) print " [Read special word at word number ", wn, "]^";
1524 #Endif;
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;
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;
1564 return GPR_PREPOSITION;
1565 }
1566
1567 PREPOSITION_TT:
1568
1569
1570 if ((token->0) & $10) return GPR_PREPOSITION;
1571
1572
1573
1574
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
1583
1584 if (inferfrom == 0) return -1;
1585
1586
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
1597
1598
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;
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;
1617 l = indirect(scope_token);
1618 #Ifdef DEBUG;
1619 if (parser_trace >= 3) print " [Scope routine returned multiple-flag of ", l, "]^";
1620 #Endif;
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 }
1632
1633 token = given_tdata;
Parse Token Letter B.
Begin parsing an object list.
1639
1640
1641
1642
1643
1644
1645
1646
1647
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
1660
1661 .ObjectList;
1662
1663 #Ifdef DEBUG;
1664 if (parser_trace >= 3) print " [Object list from word ", wn, "]^";
1665 #Endif;
1666
1667
1668
1669
1670
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
1680
1681
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
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
1711
1712
1713 oops_from = wn;
1714
1715
1716
1717
1718
1719
1720
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;
1727 l = NounDomain(actors_location, actor, token);
1728 if (l == REPARSE_CODE) return l;
1729 if (indef_wanted == INDEF_ALL_WANTED && l == 0 && number_matched == 0)
1730 l = 1;
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 }
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;
1760
1761 if (l == 1) {
1762 if (~~many_flag) many_flag = true;
1763 else {
1764 k = multiple_object-->0;
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;
1774 }
1775 }
1776 else {
1777
1778
1779 if (match_length == 0 && indef_possambig) {
1780
1781
1782
1783
1784
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 }
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;
1802 }
1803 }
1804 }
1805
1806 else {
1807
1808
1809
1810
1811 l = NounDomain(actor,actors_location,token);
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;
1820 }
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
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;
1841 }
1842 single_object = l;
1843 }
1844
1845
1846
1847
1848
1849
1850 wn = match_from + match_length;
Parse Token Letter E.
Parse connectives (AND, BUT, etc.) and go back to (C).
1856
1857
1858
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;
1869
1870 if (~~token_allows_multiple) {
1871 if (multiflag) jump PassToken;
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;
1885 }
1886 dont_infer = true; inferfrom=0;
1887 jump ObjectList;
1888 }
1889
1890 wn--;
Parse Token Letter F.
Return the conclusion of parsing an object list.
1896
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
1918
1919
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];
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;
1955Constant MY_BIT = 2;
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--;
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;
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;
2136
2137 wn = match_from+match_length;
2138
2139
2140
2141
2142 if (number_matched == 0) { wn++; rfalse; }
2143
2144
2145
2146
2147
2148 if (match_from <= num_words) {
2149 if (number_matched == 1) {
2150 i=match_list-->0;
2151 return i;
2152 }
2153
2154
2155
2156
2157
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
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;
2196
2197 }
2198
2199
2200
2201
2202
2203
2204
2205
2206
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
2218
2219
2220
2221
2222
2223 if (match_from > num_words) jump Incomplete;
2224
2225
2226
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;
2251 PARSER_CLARIF_INTERNAL_RM('H');
2252 }
2253 }
2254 print "?^";
2255
2256 .SkipWhichQuestion; EndActivity(ASKING_WHICH_DO_YOU_MEAN_ACT);
2257
2258
2259
2260 .WhichOne;
2261 #Ifdef TARGET_ZCODE;
2262 for (i=2 : i<INPUT_BUFFER_LEN : i++) buffer2->i = ' ';
2263 #Endif;
2264 answer_words=Keyboard(buffer2, parse2);
2265
2266
2267 first_word = (parse2-->1);
2268
2269
2270
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
2287
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
2296
2297
2298
2299
2300
2301
2302 #Ifdef LanguageIsVerb;
2303 if (first_word == 0) {
2304 j = wn; first_word = LanguageIsVerb(buffer2, parse2, 1); wn = j;
2305 }
2306 #Endif;
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
2316
2317
2318
2319
2320
2321
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;
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;
2339
2340
2341
2342
2343 .RECONSTRUCT_INPUT;
2344
2345 num_words = WordCount(); players_command = 100 + num_words;
2346 wn = 1;
2347 #Ifdef LanguageToInformese;
2348 LanguageToInformese();
2349
2350 VM_Tokenise(buffer,parse);
2351 #Endif;
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
2359
2360
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;
2371 answer_words = Keyboard(buffer2, parse2);
2372
2373
2374
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;
2388
2389
2390
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
2401
2402
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;
2410 i = WORDSIZE + buffer-->0;
2411 (buffer-->0)++; buffer->(i++) = ' ';
2412 #Endif;
2413
2414 #Ifdef DEBUG;
2415 if (parser_trace >= 5)
2416 print "[Gluing in inference with pattern code ", pattern-->j, "]^";
2417 #Endif;
2418
2419
2420
2421 parse2-->1 = 0;
2422
2423
2424
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;
2435 break;
2436 }
2437 }
2438 else {
2439
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;
2445 }
2446
2447
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;
2459 k = Glulx_PrintAnyToArray(buffer+i, INPUT_BUFFER_LEN-i, parse2-->1);
2460 i = i + k; buffer-->0 = i - WORDSIZE;
2461 #Endif;
2462 }
2463 }
2464 }
2465
2466
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;
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;
2484
2485
2486
2487
2488 #Ifdef TARGET_ZCODE;
2489 for (: i<INPUT_BUFFER_LEN : i++) buffer->i = ' ';
2490 #Endif;
2491
2492 jump RECONSTRUCT_INPUT;
2493
2494];
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;
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
2596
2597
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
2608 i = SingleBestGuess();
2609 if (i >= 0) {
2610
2611 #Ifdef DEBUG;
2612 if (parser_trace >= 4) print " Single best-scoring object returned.]^";
2613 #Endif;
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;
2653 }
2654 else {
2655 i = i;
2656 #Ifdef DEBUG;
2657 if (parser_trace >= 4) print " Rejecting it^";
2658 #Endif;
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;
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;
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;
2721 return 0;
2722 }
2723 #Ifdef DEBUG;
2724 if (parser_trace >= 4) print " Best choices are all from the same group.^";
2725 #Endif;
2726 }
2727 }
2728
2729
2730
2731
2732
2733
2734 if (n == 1) dont_infer = true;
2735 return BestGuess();
2736
2737];
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;
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;
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;
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;
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;
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;
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
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;
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;
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;
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
2895
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;
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
2926
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;
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;
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;
3004 if (o1 == 0 || o2 == 0) rfalse;
3005 if (o1 ofclass K3_direction || o2 ofclass K3_direction) rfalse;
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
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
3028
3029
3030
3031 p1 = o1.&1; n1 = (o1.#1)/WORDSIZE;
3032 p2 = o2.&1; n2 = (o2.#1)/WORDSIZE;
3033
3034
3035
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
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;
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;
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
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
3343 BeginActivity(DECIDING_SCOPE_ACT, actor);
3344 if (ForActivity(DECIDING_SCOPE_ACT, actor) == false) {
3345
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
3352 if ((scope_reason == PARSING_REASON) && (context ~= CREATURE_TOKEN) &&
3353 (indef_mode == 0) && (domain1 == actors_location))
3354 ScopeWithin(compass);
3355
3356 if (domain1 has supporter or container) DoScopeAction(domain1);
3357 ScopeWithin(domain1, domain2, context);
3358
3359 if (domain2) {
3360 if (domain2 has supporter or container) DoScopeAction(domain2);
3361 ScopeWithin(domain2, 0, context);
3362 }
3363 }
3364
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
3391
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
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
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
3442 ad = domain.&add_to_scope;
3443 if (ad ~= 0) {
3444
3445 #Ifdef TARGET_ZCODE;
3446 i = (UnsignedCompare(ad-->0, top_object) > 0);
3447 #Ifnot;
3448 i = (((ad-->0)->0) ~= $70);
3449 #Endif;
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;
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) {
3503 wn = match_from;
3504 i = NounWord();
3505 if ((i == 1) && (player == item)) MakeMatch(item, 1);
3506 if ((i >= 2) && (i < 128) && (LanguagePronouns-->i == item)) MakeMatch(item, 1);
3507 }
3508
3509
3510
3511
3512 wn = match_from;
3513 if (TryGivenObject(item) > 0)
3514 if (indef_nspec_at > 0 && match_from ~= indef_nspec_at) {
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
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;
3546
3547 if (nomatch && obj == 0) return 0;
3548
3549
3550
3551 dict_flags_of_noun = 0;
3552
3553
3554
3555
3556 if (wn > num_words) {
3557 if (nomatch) return 0;
3558 if (indef_mode ~= 0)
3559 dict_flags_of_noun = $$01110000;
3560 MakeMatch(obj,0);
3561 #Ifdef DEBUG;
3562 if (parser_trace >= 5) print " Matched (0)^";
3563 #Endif;
3564 return 1;
3565 }
3566
3567
3568
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;
3595 if (nomatch == false) MakeMatch(obj,k);
3596 return k;
3597 }
3598 if (k == 0) jump NoWordsMatch;
3599 }
3600
3601
3602
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;
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.∝ 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);
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;
3713 i = wordnum*3; j = parse-->i; num = j+buffer; len = parse-->(i-1);
3714 #Endif;
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];
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];
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;
3833 rfalse;
3834];
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;
3846 if (location ~= nothing && parent(player) ~= nothing) DrawStatusLine();
3847 KeyboardPrimitive(buffer2, parse2);
3848 j = parse2-->0;
3849 #Endif;
3850 if (j) {
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
3884
3885
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
3903[ COBJ__SwapMatches i x;
3904
3905 x = number_matched;
3906 number_matched = alt_match_list-->0;
3907 alt_match_list-->0 = x;
3908
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; }
3980 if (l == ALL1__WD or ALL2__WD or ALL3__WD or ALL4__WD or ALL5__WD) { wn++; continue; }
3981 SafeSkipDescriptors();
3982
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
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
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