1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "glk/agt/agility.h"
24 #include "glk/agt/interp.h"
25
26 namespace Glk {
27 namespace AGT {
28
29 /*
30
31 This is probably the ugliest and least readable of all of the
32 source files. The parser isn't really that complex in principle,
33 but it has to deal with a lot of of special cases and also be
34 downward-compatible with the original parsers (which sometimes did
35 strange things) which adds a lot of additional code. The noun parsing
36 code is particularly convoluted.
37
38 Also, there are a fair number of global variables in this module
39 that are really local variables at heart but ended up global
40 because they were needed in too many different places and I didn't
41 want to complicate things even more by having to explicitly pass
42 them all up and down through the parsing routines. (One of those
43 times it would be convenient to have the ability to nest procedures
44 and functions like Pascal.)
45
46 */
47
48 /* These store the previous turn's values for use in disambiguation and
49 implementing OOPS */
50 /* parse_ip saves the ip after parsing, ip_back saves it before. */
51 word input_back[MAXINPUT];
52 words in_text_back[MAXINPUT];
53 int ip_back, parse_ip;
54
55 /* The following are global only for rfree() purposes and
56 in order to maintain state for disambiguation */
57 static int vnum; /* Verb number from synonym scan */
58
59 /* Pointers to negative-terminated arrays of possible nouns */
60 static parse_rec *lactor = NULL, *lobj = NULL, *lnoun = NULL;
61
62 static int ambig_flag = 0;
63 /* Was last input ambiguous? (so player could be entering
64 disambiguation info?) 1=ambig actor, 2=ambig noun,
65 3=ambig obj */
66
67
68 /* Empty ALL error messages, for those verbs that have their own */
69 int all_err_msg[] = {73, 83, 113, 103, /* open, close, lock, unlock: 15 - 18 */
70 239, 239, 239, 239, /* 19 - 22 */
71 123, 125
72 }; /* eat, drink: 23 - 24 */
73
74
75
76 /*-------------------------------------------------------------------*/
77 /* DEBUGGING OUTPUT FUNCTIONS & MISC UTILITIES */
78 /*-------------------------------------------------------------------*/
79
80 /* This routine frees the space used by the parsing data structures */
freeall(void)81 static void freeall(void) {
82 rfree(lnoun);
83 rfree(lobj);
84 lnoun = lobj = NULL;
85 }
86
87
88 /* Print out parse error message and abort the parse */
parseerr(int msgid,const char * s,int n)89 static int parseerr(int msgid, const char *s, int n) {
90 if (n >= 0)
91 gen_sysmsg(msgid, s, MSG_PARSE, in_text[n]);
92 else
93 gen_sysmsg(msgid, s, MSG_PARSE, "");
94 freeall();
95 ep = n;
96 ip = -1;
97 return -1;
98 }
99
100
101 /* Print out noun list; used for debugging the parser */
print_nlist(parse_rec * n)102 static void print_nlist(parse_rec *n) {
103 char *s;
104 int c;
105 char buff[100];
106
107 if (n->info == D_END)
108 writestr("----");
109 if (n->info == D_ALL) {
110 writestr("ALL ");
111 n++;
112 }
113 for (c = 0; n->info != D_END && c < 20; n++, c++)
114 if (n->info == D_AND) writestr(" AND ");
115 else if (n->info == D_NUM) { /* Number entered */
116 sprintf(buff, "#%ld(%d); ", n->num, n->obj);
117 writestr(buff);
118 } else if (n->obj < 0) {
119 writestr(dict[-(n->obj)]);
120 sprintf(buff, "(%d); ", n->obj);
121 writestr(buff);
122 } else {
123 s = objname(n->obj);
124 writestr(s);
125 rfree(s);
126 sprintf(buff, "(%d) ['%s %s']; ", n->obj, dict[n->adj], dict[n->noun]);
127 writestr(buff);
128 }
129 if (n->info != D_END) writestr("///");
130 writeln("");
131 }
132
133 /* Output the parser's analysis of the current input line; used
134 for debugging */
parse_out(parse_rec * lactor_,int vb_,parse_rec * lnoun_,int prep_,parse_rec * lobj_)135 static int parse_out(parse_rec *lactor_, int vb_, parse_rec *lnoun_, int prep_,
136 parse_rec *lobj_) {
137 writeln("ANALYSIS:");
138 writestr("Actor: ");
139 print_nlist(lactor_);
140 writestr("Verb:");
141 writeln(dict[ syntbl[auxsyn[vb_]] ]);
142 writestr("DObj: ");
143 print_nlist(lnoun_);
144 writestr("Prep: ");
145 if (prep_ != 0) writeln(dict[prep_]);
146 else writeln("---");
147 writestr("IObj: ");
148 print_nlist(lobj_);
149 return 0;
150 }
151
152
save_input(void)153 static void save_input(void) {
154 int i;
155
156 for (i = 0; input[i] != -1 && i < MAXINPUT; i++) {
157 input_back[i] = input[i];
158 strncpy(in_text_back[i], in_text[i], 24);
159 }
160 input_back[i] = -1;
161 ip_back = ip;
162 }
163
restore_input(void)164 static void restore_input(void) {
165 int i;
166
167 for (i = 0; input_back[i] != -1 && i < MAXINPUT; i++) {
168 input[i] = input_back[i];
169 strncpy(in_text[i], in_text_back[i], 24);
170 }
171 input[i] = -1;
172 ip = ip_back;
173 }
174
175
176
177 /*-------------------------------------------------------------------*/
178 /* Misc. Parsing Routines (includes parsing of verbs and preps) */
179 /*-------------------------------------------------------------------*/
180
181 #define w_and(w) (w==ext_code[wand] || w==ext_code[wc])
182 #define w_but(w) (w==ext_code[wbut] || w==ext_code[wexcept])
183 #define w_isterm(w) (w==ext_code[wp] || w==ext_code[wthen] || \
184 w==ext_code[wsc] || w_and(w) || w==-1)
185
186
187
check_comb(int combptr)188 static word check_comb(int combptr) {
189 int k;
190 word w;
191
192 if (combptr == 0) return 0;
193 w = syntbl[combptr];
194 for (combptr += 1, k = ip; syntbl[combptr] != 0; combptr++, k++)
195 if (syntbl[combptr] != input[k]) break;
196 if (syntbl[combptr] == 0) {
197 ip = k - 1;
198 return w;
199 }
200 return 0;
201 }
202
203
comb_verb(void)204 static int comb_verb(void)
205 /* This eliminates two-word verbs */
206 {
207 int i;
208 word w;
209
210 for (i = 0; i < num_comb; i++) {
211 w = check_comb(comblist[i]);
212 if (w != 0) return w;
213 }
214
215 if (input[ip] == ext_code[wgo] && verb_authorsyn(ext_code[wgo]) == 0) {
216 /* GO <dir> --> <dir> */
217 w = input[ip + 1];
218 if (w != 0) i = verb_builtin(w);
219 else i = 0;
220 if (i != 0) {
221 ip++;
222 return w;
223 }
224 }
225
226 for (i = 0; i < num_auxcomb; i++) {
227 w = check_comb(auxcomb[i]);
228 if (w != 0) return w;
229 }
230
231 return input[ip];
232 }
233
234
235
236
237
238 /* This return true if the word in question is in the list
239 of original AGT verbs, but is not the canonical verb.
240 This is needed because verbs on this list
241 are not overridden by dummy_verbs in pre-Magx games. */
orig_agt_verb(word w)242 static rbool orig_agt_verb(word w) {
243 int i;
244 if (aver <= AGT10 && w == ext_code[wg]) return 0; /* AGT 1.0 didn't have AGAIN */
245 for (i = 0; old_agt_verb[i] != -1 && old_agt_verb[i] != w; i++);
246 return (old_agt_verb[i] == w);
247 }
248
249 /* A few comments on id_verb:
250 The sequence is as follows:
251 i) Convert built-in synonyms to the base form (TAKE-->GET, for example)
252 _unless_ there is an author-defined synonym. (The original AGT
253 didn't have this 'unless'.)
254 ii) Check room-synonyms
255 iii) ID the verb based first on game-specific synonyms and then
256 falling back to the built-in ones.
257 (AGT gave the built-in ones higher priority than the game-specific
258 ones, but this causes problems and is generally a bad idea.)
259 */
260
id_verb(void)261 static int id_verb(void)
262 /* Identify verb at ip=i ; return verb id if found, 0 otherwise */
263 {
264 word w;
265 int j, canon_word, tmp;
266
267 w = comb_verb(); /* Combine 2-word verbs */
268 if (w == 0) return 0;
269
270 /* Pre-Canonization of w: see if w has any built-in synonyms */
271 canon_word = verb_builtin(w);
272 if (canon_word != 0) {
273 if (aver < AGX00 && orig_agt_verb(w))
274 /* In orig AGT, author-defined verbs don't override builtin syns */
275 tmp = 0;
276 else
277 tmp = verb_authorsyn(w); /* Author-defined verbs override built-in ones */
278 if (tmp == 0 || tmp == canon_word)
279 w = syntbl[auxsyn[canon_word]];
280 }
281
282 /* Now check room-specific synonyms (being the most localized,
283 they have the highest priority) */
284 for (j = room[loc].replacing_word; syntbl[j] != 0; j++)
285 if (w == syntbl[j])
286 w = room[loc].replace_word;
287
288 /* Next check to see if we already have the canonical form of the verb. */
289 /* and go through the built-in list of synonyms */
290 canon_word = verb_code(w);
291 if (!PURE_DUMMY && canon_word == 57) canon_word = 0; /* AFTER */
292 return canon_word;
293 }
294
295
296 #define compr_prep(w1,w2,r) {if (w1==input[ip] && w2==input[ip+1]) \
297 {ip+=2;return r;}}
298 #define cprep(c1,c2,r) compr_prep(ext_code[c1],ext_code[c2],ext_code[r])
299
300 /* Eventually should add support for the handful of two word preps */
301 /* (eg IN TO, OUT OF,...); otherwise, this is pretty trivial. */
parse_prep(void)302 static int parse_prep(void) {
303 int i;
304 int j, k;
305
306 for (j = 0; j < num_prep; j++) { /* Scan user-defined prepositions */
307 /* This table is formatted like the multi-verb table:
308 end-prep prep-word1 prepword2 etc. */
309 for (k = 0; syntbl[userprep[j] + k + 1] != 0; k++)
310 if (syntbl[userprep[j] + k + 1] != input[ip + k]) break;
311 if (syntbl[userprep[j] + k + 1] == 0) {
312 ip += k;
313 return syntbl[userprep[j]];
314 }
315 }
316 cprep(win, wto, winto);
317 cprep(wout, wof, wfrom);
318 for (i = win; i <= wabout; ++i)
319 if (ext_code[i] == input[ip]) return input[ip++];
320 return 0;
321 }
322
323
324
noun_syn(word w,int obj)325 static int noun_syn(word w, int obj)
326 /* Is the word w a synonym for the object obj? */
327 /* obj is encoded as ususal. */
328 /* 0=no match, 1=adjective match, 2=synonym match, 3=noun match */
329 /* 2 will only occur if PURE_SYN is false */
330 {
331 int i;
332
333 if (w <= 0) return 0;
334
335 if (obj >= first_noun && obj <= maxnoun) {
336 obj = obj - first_noun;
337 if (w == noun[obj].name) return 3;
338 if (noun[obj].has_syns)
339 for (i = noun[obj].syns; syntbl[i] != 0; i++)
340 if (w == syntbl[i]) return (PURE_SYN ? 3 : 2);
341 if (w == noun[obj].adj) return 1;
342 return 0;
343 }
344 if (obj >= first_creat && obj <= maxcreat) {
345 obj = obj - first_creat;
346 if (w == creature[obj].name) return 3;
347 if (creature[obj].has_syns)
348 for (i = creature[obj].syns; syntbl[i] != 0; i++)
349 if (w == syntbl[i]) return (PURE_SYN ? 3 : 2);
350 if (w == creature[obj].adj) return 1;
351 return 0;
352 }
353 return 0; /* If the object doesn't exist, can't have synonyms */
354 }
355
356
357
358 /*-------------------------------------------------------------------*/
359 /* Noun-list manipulation functions. */
360 /*-------------------------------------------------------------------*/
361
new_list(void)362 static parse_rec *new_list(void) {
363 parse_rec *list;
364
365 list = (parse_rec *)rmalloc(sizeof(parse_rec));
366 list[0].obj = 0;
367 list[0].num = 0;
368 list[0].adj = list[0].noun = 0;
369 list[0].info = D_END;
370 return list;
371 }
372
add_w_rec(parse_rec * pold,int obj0,long num0,int info0,word adj0,word noun0)373 static parse_rec *add_w_rec(parse_rec *pold, int obj0, long num0, int info0,
374 word adj0, word noun0) {
375 parse_rec *pnew;
376 int n;
377
378 for (n = 0; pold[n].info != D_END; n++);
379 pnew = (parse_rec *)rrealloc(pold, (n + 2) * sizeof(parse_rec));
380 pnew[n].obj = obj0;
381 pnew[n].num = num0;
382 pnew[n].info = info0;
383 pnew[n].adj = adj0;
384 pnew[n].noun = noun0;
385 pnew[n + 1].obj = 0;
386 pnew[n + 1].info = D_END;
387 return pnew;
388 }
389
add_rec(parse_rec * old,int obj0,long num0,int info0)390 static parse_rec *add_rec(parse_rec *old, int obj0, long num0, int info0) {
391 word w;
392
393 if (obj0 < 0) w = -obj0; /* The NOUN field of literal words will just be
394 that word */
395 else w = 0;
396 return add_w_rec(old, obj0, num0, info0, 0, w);
397 }
398
399
kill_rec(parse_rec * old,int index)400 static parse_rec *kill_rec(parse_rec *old, int index)
401 /* Remove record old[index] */
402 {
403 parse_rec *pnew;
404 int i;
405
406 for (i = index; old[i].info != D_END; i++) {
407 old[i].obj = old[i + 1].obj;
408 old[i].num = old[i + 1].num;
409 old[i].noun = old[i + 1].noun;
410 old[i].adj = old[i + 1].adj;
411 old[i].info = old[i + 1].info;
412 old[i].score = old[i + 1].score;
413 }
414 pnew = (parse_rec *)rrealloc(old, i * sizeof(parse_rec)); /* Shrink it by one */
415 return pnew;
416 }
417
concat_list(parse_rec * dest,parse_rec * src)418 static parse_rec *concat_list(parse_rec *dest, parse_rec *src) {
419 int i, j;
420
421 for (i = 0; dest[i].info != D_END; i++); /* Put i at end of first list */
422 for (j = 0; src[j].info != D_END; j++); /* j marks end of the second list */
423 dest = (parse_rec *)rrealloc(dest, sizeof(parse_rec) * (i + j + 1));
424 memcpy(dest + i, src, (j + 1)*sizeof(parse_rec));
425 return dest;
426 }
427
purge_list(parse_rec * list)428 static parse_rec *purge_list(parse_rec *list)
429 /* It should be possible to make this more efficiant */
430 {
431 int i;
432
433 for (i = 0; list[i].info != D_END;)
434 if ((list[i].info & D_MARK) != 0)
435 list = kill_rec(list, i);
436 else i++; /* (Note: we only increment i if we _don't_ kill) */
437 return list;
438 }
439
clean_list(parse_rec * list)440 static void clean_list(parse_rec *list) {
441 for (; list->info != D_END; list++)
442 list->info &= ~D_MARK; /* Clear mark */
443 }
444
445
copy_list(parse_rec * list)446 static parse_rec *copy_list(parse_rec *list) {
447 parse_rec *cpy;
448 int i;
449
450 cpy = new_list();
451 for (i = 0; list[i].info != D_END; i++);
452 cpy = (parse_rec *)rmalloc(sizeof(parse_rec) * (i + 1));
453 memcpy(cpy, list, (i + 1)*sizeof(parse_rec));
454 return cpy;
455 }
456
457
458
459 /* Among other things, this is used to add disambiguation information */
scan_rec(int item,int num,parse_rec * list)460 static int scan_rec(int item, int num, parse_rec *list) {
461 int i;
462
463 for (i = 0; list[i].info != D_END && list[i].info != D_AND; i++)
464 if (list[i].obj == item && list[i].num == num)
465 return i;
466 return -1;
467 }
468
scan_andrec(int item,parse_rec * list)469 static rbool scan_andrec(int item, parse_rec *list) {
470 for (; list->info != D_END; list++)
471 if (list->obj == item && list->info != D_AND
472 && list->info != D_ALL) return 1;
473 return 0;
474 }
475
multinoun(parse_rec * list)476 static rbool multinoun(parse_rec *list)
477 /* Determines if LIST refers to a multiple object */
478 {
479 if (list->info == D_ALL) return 1;
480 for (; list->info != D_END; list++)
481 if (list->info == D_AND) return 1;
482 return 0;
483 }
484
485 /* This updates the adj and noun fields of a record and also
486 updates the disambiguation priority */
487 /* We are already either D_ADJ or D_SYN; this just handles
488 possible promotion */
add_words_to_rec(parse_rec * nrec,word w,int tmp)489 static void add_words_to_rec(parse_rec *nrec, word w, int tmp) {
490 word w2;
491
492 if (tmp == 2 || tmp == 3) {
493 w2 = nrec->noun;
494 nrec->noun = w;
495 w = w2;
496 }
497 if (nrec->adj == 0)
498 nrec->adj = w;
499 if (tmp == 2) nrec->info = D_SYN;
500 else if (tmp == 3) nrec->info = D_NOUN;
501 }
502
503 /* This updates nrec with the information in newobj; this routine
504 is called while adding disambiguation information; newobj is from
505 what the player just typed in to clarify the ambiguity */
update_rec_words(parse_rec * nrec,parse_rec * newobj)506 void update_rec_words(parse_rec *nrec, parse_rec *newobj) {
507 int tmp;
508
509 if (nrec->adj == 0) nrec->adj = newobj->adj;
510 if (newobj->info == D_ADJ) tmp = 1;
511 else if (newobj->info == D_SYN) tmp = 2;
512 else if (newobj->info == D_NOUN) tmp = 3;
513 else return;
514 add_words_to_rec(nrec, newobj->noun, tmp);
515 }
516
517
ident_objrec(parse_rec * p1,parse_rec * p2)518 static rbool ident_objrec(parse_rec *p1, parse_rec *p2) {
519 word noun1, adj1, noun2, adj2;
520
521 if (p1->obj == p2->obj) return 1;
522 if (p1->obj <= 0 || p2->obj <= 0) return 0;
523 if (tnoun(p1->obj)) {
524 noun1 = noun[p1->obj - first_noun].name;
525 adj1 = noun[p1->obj - first_noun].adj;
526 } else if (tcreat(p1->obj)) {
527 noun1 = creature[p1->obj - first_creat].name;
528 adj1 = creature[p1->obj - first_creat].adj;
529 } else return 0;
530 if (tnoun(p2->obj)) {
531 noun2 = noun[p2->obj - first_noun].name;
532 adj2 = noun[p2->obj - first_noun].adj;
533 } else if (tcreat(p2->obj)) {
534 noun2 = creature[p2->obj - first_creat].name;
535 adj2 = creature[p2->obj - first_creat].adj;
536 } else return 0;
537 return (noun1 == noun2 && adj1 == adj2);
538 }
539
540 /*-------------------------------------------------------------------*/
541 /* Disambiguation and ALL expansion routines */
542 /*-------------------------------------------------------------------*/
543
544 /* Eliminate non-creatures and non-present creatures from list. */
545 /* Very similar to disambig below, but simpler. */
fix_actor(parse_rec * alist)546 static parse_rec *fix_actor(parse_rec *alist) {
547 int i, cnt;
548
549 assert(alist != NULL);
550 if (alist[0].info == D_ALL) { /* ALL?! */
551 rfree(alist);
552 return new_list();
553 }
554
555 /* Start by eliminating non-creatures */
556 cnt = 0;
557 for (i = 0; alist[i].info != D_END; i++)
558 if ((alist[i].obj < first_creat || alist[i].obj > maxcreat)
559 && alist[i].obj != -ext_code[weverybody]) {
560 if (alist[i].info != D_AND)
561 alist[i].info |= D_MARK;
562 } else cnt++;
563 alist = purge_list(alist);
564 if (cnt <= 1) return alist;
565
566 /* Now eliminate those not present */
567 cnt = 0;
568 for (i = 0; alist[i].info != D_END; i++)
569 if (!genvisible(&alist[i])) {
570 if (alist[i].info != D_AND)
571 alist[i].info |= D_MARK;
572 } else cnt++;
573
574 if (cnt == 0) alist[0].info &= ~D_MARK;
575
576 return purge_list(alist);
577 }
578
579
580 /* Convert disambig list to all list, moving things up. */
convert_to_all(parse_rec * list,int * ofsref)581 static parse_rec *convert_to_all(parse_rec *list, int *ofsref) {
582 int i;
583 int cnt;
584
585 for (i = *ofsref; list[i].info != D_AND && list[i].info != D_END; i++);
586 cnt = i - *ofsref; /* Number of objects. We will add cnt-1 ANDs */
587
588 while (list[i].info != D_END) i++;
589 list = (parse_rec *)rrealloc(list, (i + cnt) * sizeof(parse_rec));
590 memmove(list + *ofsref + 2 * cnt - 1, list + *ofsref + cnt,
591 (i + 1 - cnt - *ofsref)*sizeof(parse_rec));
592 for (i = cnt - 1; i >= 0; i--) {
593 int k;
594 list[*ofsref + 2 * i] = list[*ofsref + i];
595 if (i == 0) break;
596 k = *ofsref + 2 * i - 1;
597 list[k].obj = 0;
598 list[k].num = 0;
599 list[k].adj = list[k].noun = 0;
600 list[k].info = D_AND;
601 }
602 /* *ofsref+=2*cnt-1; */
603 return list;
604 }
605
606
607
add_disambig_info(parse_rec * ilist,int ofs,parse_rec * truenoun)608 static parse_rec *add_disambig_info(parse_rec *ilist, int ofs,
609 parse_rec *truenoun)
610 /* Basically, try to find interesection of tmp and truenoun,
611 or failing that, between ilist and truenoun */
612 /* truenoun is what the player just typed in to resolve
613 the ambiguity */
614 {
615 int i, n;
616
617 for (i = ofs; ilist[i].info != D_AND && ilist[i].info != D_END; i++) {
618 if (truenoun[0].info == D_EITHER) {
619 /* Mark all but the first for deletion */
620 if (i > ofs) ilist[i].info |= D_MARK;
621 } else {
622 n = scan_rec(ilist[i].obj, ilist[i].num, truenoun);
623 if (n == -1)
624 ilist[i].info |= D_MARK;
625 else /* Add any new information to the words */
626 update_rec_words(&ilist[i], &truenoun[n]);
627 }
628 }
629 ilist = purge_list(ilist);
630 truenoun[0].obj = 0;
631 truenoun[0].num = 0;
632 truenoun[0].info = D_END; /* Truenoun no longer useful */
633 return ilist;
634 }
635
636 static int max_disambig_score;
637
score_disambig(parse_rec * rec,int ambig_type)638 static int score_disambig(parse_rec *rec, int ambig_type)
639 /* This is just a wrapper for check_obj (defined in runverb.c) */
640 /* ambig_type=1 for actor, 2 for noun, 3 for object */
641 /* We can assume that the earlier bits have already been disambiguated */
642 /* Parse of current command in lactor, vnum, lnoun, prep, and lobj */
643 {
644 if (ambig_type == 1) /* ACTOR */
645 return DISAMBIG_SUCC;
646 else if (ambig_type == 2) /* NOUN */
647 return check_obj(lactor, vnum, rec, prep, NULL);
648 else if (ambig_type == 3) /* IOBJ */
649 return check_obj(lactor, vnum, lnoun, prep, rec);
650 else fatal("Invalid ambig_type!");
651 return 0;
652 }
653
654
655 /* This routine does all expansion: it returns a list of ALL objects in the
656 current context (lactor, vnum, <ALL>, prep, lobj) */
657 /* lnoun is assumed to be a list of exceptions, which needs to be freed
658 at the end. (i.e. we want to expand to ALL EXCEPT <lnoun>) */
expand_all(parse_rec * lnoun_)659 static parse_rec *expand_all(parse_rec *lnoun_) {
660 parse_rec *list;
661 int i, j;
662 rbool prev_obj; /* Is there a previous object on the list? */
663 rbool kill_obj; /* Don't put current object on ALL list after all */
664 parse_rec temp_obj; /* Used to pass object info to disambiguation routine */
665
666 if (debug_parse) {
667 writestr("ALL BUT:");
668 print_nlist(lnoun_);
669 }
670 tmpobj(&temp_obj);
671 nounloop(i)
672 noun[i].scratch = 0;
673 creatloop(i)
674 creature[i].scratch = 0;
675 objloop(i)
676 if (((verbflag[vnum]&VERB_GLOBAL) != 0 || visible(i))
677 && (lnoun_ == NULL || !scan_andrec(i, lnoun_))) {
678 temp_obj.obj = i;
679 if (score_disambig(&temp_obj, 2) >= 500) {
680 if (tnoun(i)) noun[i - first_noun].scratch = 1;
681 else if (tcreat(i)) creature[i - first_creat].scratch = 1;
682 else writeln("INTERNAL ERROR: Invalid object type in expand_all().");
683 }
684 }
685
686 /* The following ensures that if an object and it's container
687 are both selected, only the container will actually make it
688 onto the list.*/
689 list = new_list();
690 prev_obj = 0;
691 objloop(i)
692 if (it_scratch(i)) {
693 kill_obj = 0;
694 for (j = it_loc(i); tnoun(j) || tcreat(j); j = it_loc(j))
695 if (it_scratch(j)) {
696 kill_obj = 1;
697 break;
698 }
699 if (kill_obj) continue;
700 /* Now actually add object to list. */
701 if (prev_obj) list = add_rec(list, 0, 0, D_AND);
702 list = add_rec(list, i, 0, D_SYN);
703 prev_obj = 1;
704 }
705
706 if (debug_parse) {
707 writestr("ALL==>");
708 print_nlist(list);
709 }
710 rfree(lnoun_);
711 return list;
712 }
713
714
715
716 /* disambig check checks for the various things that can eliminate a noun
717 from the list. The higher dlev is, the more things that are bad */
718 /* Returns 1 if we should keep it, 0 if we should kill it */
719 /* The elimination order is based on AGT:
720 0-Eliminate adjective matches if PURE_NOUN is set and
721 out-of-scope adjectives that are not at the head of the list.
722 1-Eliminate adj matches if the adjective is out-of-scope.
723 2-eliminate SCENE and DOOR (D_INTERN)
724 3-eliminate out-of-scope nouns that are not at the head of the list
725 4-eliminate out-of-scope nouns
726 5-eliminate numbers that don't have associated dictionary words
727 6-eliminate adj only matches (i.e. noun-free) [only if PURE_ADJ]
728 7-eliminate pronouns (D_PRO)
729 -eliminate ALL (D_ALL)
730 -eliminate numbers
731 (Never eliminated: FLAG,GLOBAL,PIX,SYN,NOUN)
732 */
733
734 #define MAX_DSCHEME 3
735 #define MAX_DLEV 2
736 /* ambig_type=1 for actor, 2 for noun, 3 for object */
737 /* We can assume that the earlier bits have already been disambiguated */
738 /* Parse of current command in lactor, vnum, lnoun, prep, and lobj */
739 /* pick_one is used to select the first noun in the case that all of
740 them get eliminated during visibility testing (if none of the nouns
741 are visible, we don't want to ask disambiguation questions) */
742
disambig_check(parse_rec * rec,int dsch,int dlev,int ambig_type,rbool pick_one)743 static rbool disambig_check(parse_rec *rec, int dsch, int dlev,
744 int ambig_type, rbool pick_one) {
745 switch (dsch) {
746 case 0:
747 switch (dlev) { /* Syntactic checks: pre-scope */
748 case 0:
749 return (!PURE_ADJ || rec->info != D_ADJ);
750 case 1:
751 return (rec->info != D_INTERN); /* Elim SCENE and DOOR */
752 case 2:
753 return (rec->info != D_NUM || rec->obj != 0); /* Eliminate numbers w/o
754 corrosponding word matches */
755 default:
756 return 0;
757 }
758 case 1:
759 switch (dlev) { /* Scope checks */
760 case 0:
761 /* Just compute the scores, but don't eliminate anything yet */
762 /* if PURE_DISAMBIG, no intel dismbig */
763 if (PURE_DISAMBIG || rec->info == D_NUM)
764 rec->score = DISAMBIG_SUCC;
765 else rec->score = score_disambig(rec, ambig_type);
766 if (rec->score >= max_disambig_score)
767 max_disambig_score = rec->score;
768 return 1;
769 case 1:
770 return (rec->score == max_disambig_score);
771 case 2:
772 return (rec->info == D_NUM
773 || ((verbflag[vnum] & VERB_GLOBAL) != 0 && rec->score >= 500)
774 || (tnoun(rec->obj) && noun[rec->obj - first_noun].scope)
775 || (tcreat(rec->obj) && creature[rec->obj - first_creat].scope));
776 default:
777 return 1;
778 }
779 case 2:
780 switch (dlev) { /* Syntax, take 2 */
781 case 0: /* Reserved for alternative adjective checking */
782 return 1;
783 /* Kill internal matches */
784 case 1:
785 return (rec->info != D_PRO && rec->info != D_ALL &&
786 rec->info != D_INTERN && rec->info != D_NUM);
787 default:
788 return 0;
789 }
790 case 3:
791 return pick_one;
792 default:
793 return 0;
794 }
795 }
796
797
798 /* disambig_a_noun does disambiguation for a single AND-terminated block */
799 /* *list* contains the list of possible objects, */
800 /* *ofs* is our starting offset within the list (since with ANDs we */
801 /* may not be starting at list[0]) */
802
disambig_a_noun(parse_rec * list,int ofs,int ambig_type)803 static parse_rec *disambig_a_noun(parse_rec *list, int ofs, int ambig_type)
804 /* ambig_type=1 for actor, 2 for noun, 3 for object */
805 /* We can assume that the earlier bits have already been disambiguated */
806 /* Parse of current command in lactor, vnum, lnoun, prep, and lobj */
807 {
808 int i, cnt; /* cnt keeps track of how many nouns we've let through */
809 int dsch; /* Dismabiguation scheme; we run through these in turn,
810 pushing dlev as high as possible for each scheme */
811 int dlev; /* Disambiguation level: how picky do we want to be?
812 We keep raising this until we get a unique choice
813 or until we reach MAX_DLEV and have to give up */
814 rbool one_in_scope; /* True if at least one noun is visible; if no nouns
815 are visible then we never ask for disambiguation
816 from the player (to avoid giving things away) but
817 just take the first one. */
818
819 cnt = 2; /* Arbitrary number > 1 */
820 one_in_scope = 0;
821 max_disambig_score = -1000; /* Nothing built in returns anything lower than 0,
822 but some game author might come up with a
823 clever application of negative scores */
824 for (dsch = 0; dsch <= MAX_DSCHEME; dsch++)
825 for (dlev = 0; dlev <= MAX_DLEV; dlev++) {
826 if (DEBUG_DISAMBIG)
827 rprintf("\nDISAMBIG%c%d:%d: ", (dsch == 1 ? '*' : ' '), dsch, dlev);
828 cnt = 0;
829 for (i = ofs; list[i].info != D_END && list[i].info != D_AND; i++)
830 if (disambig_check(&list[i], dsch, dlev, ambig_type,
831 one_in_scope || (i == ofs))
832 ) {
833 cnt++;
834 if (DEBUG_DISAMBIG)
835 rprintf("+%d ", list[i].obj);
836 } else {
837 if (DEBUG_DISAMBIG)
838 rprintf("-%d ", list[i].obj);
839 list[i].info |= D_MARK; /* Mark it for deletion */
840 }
841 if (cnt != 0) {
842 list = purge_list(list); /* Delete marked items */
843 if (cnt == 1) return list;
844 if (dsch == 1 && dlev == MAX_DLEV)
845 one_in_scope = 1;
846 } else {
847 clean_list(list); /* Remove marks; we're not purging */
848 break;
849 }
850 }
851 /* Check to make sure we don't have a list of multiple identical items */
852 for (i = ofs; list[i].info != D_END && list[i].info != D_AND; i++) {
853 if (!ident_objrec(&list[i], &list[ofs])) break;
854 list[i].info |= D_MARK;
855 }
856 if (list[i].info == D_END || list[i].info == D_AND) {
857 /* If all of the items are identical, just pick the first */
858 if (one_in_scope) writeln("(Picking one at random)");
859 list[0].info &= ~D_MARK;
860 list = purge_list(list);
861 } else clean_list(list);
862 if (DEBUG_DISAMBIG) rprintf("\n");
863 return list;
864 }
865
866
867
868 /* Note that this routine must be _restartable_, when new input comes in. */
869 /* Truenoun is 0 or else the "correct" noun for where disambig first
870 got stuck. */
871 /* Returns the offset at which it got stuck, or -1 if everything
872 went ok. Return -2 if we eliminate everything */
873 /* *tn_ofs* contains the offset where truenoun is supposed to be used */
874
875 #define list (*ilist) /* in disambig_phrase only */
876
877
disambig_phrase(parse_rec ** ilist,parse_rec * truenoun,int tn_ofs,int ambig_type)878 static int disambig_phrase(parse_rec **ilist, parse_rec *truenoun, int tn_ofs,
879 int ambig_type)
880 /* Note that ilist is double dereferenced: this is so we can realloc it */
881 /* ambig_type=1 for actor, 2 for noun, 3 for object */
882 /* We can assume that the earlier bits have already been disambiguated */
883 {
884 int ofs, i;
885 char *s;
886
887 ofs = 0;
888 if (list[0].info == D_END) return -1; /* No nouns, so no ambiguity */
889 if (list[0].info == D_ALL) ofs = 1; /* might have ALL EXCEPT construction */
890 #ifdef OMEGA
891 return -1; /* No ambiguity over ALL, either */
892 /* (at least if it appears as the first element of the list) */
893 #endif
894
895 while (list[ofs].info != D_END) { /* Go through each AND block */
896 if (ofs == tn_ofs) {
897 if (truenoun[0].info == D_ALL) /* Convert to ALL list */
898 list = convert_to_all(list, &ofs);
899 else {
900 list = add_disambig_info(list, ofs, truenoun);
901 if (list[ofs].info == D_END) { /* We have eliminated all matches */
902 gen_sysmsg(240, "In that case, I don't know what you mean.",
903 MSG_PARSE, "");
904 return -2;
905 }
906 }
907 }
908 list = disambig_a_noun(list, ofs, ambig_type);
909 assert(list[ofs].info != D_END && list[ofs].info != D_AND);
910 if (list[ofs + 1].info != D_END && list[ofs + 1].info != D_AND)
911 /* Disambiguation failed */
912 {
913 writestr("Do you mean");
914 for (i = ofs; list[i].info != D_END && list[i].info != D_AND; i++) {
915 if (list[i + 1].info == D_END || list[i + 1].info == D_AND)
916 if (i > ofs + 1) writestr(", or");
917 else writestr(" or");
918 else if (i > ofs) writestr(",");
919 writestr(" the ");
920 if (list[i].info != D_NUM || list[i].obj != 0)
921 s = (char *)objname(list[i].obj);
922 else {
923 s = (char *)rmalloc(30 * sizeof(char));
924 sprintf(s, "%ld", list[i].num);
925 }
926 writestr(s);
927 rfree(s);
928 }
929 writeln("?");
930 return ofs;
931 }
932 /* Skip forward to next AND */
933 while ((*ilist)[ofs].info != D_END && (*ilist)[ofs].info != D_AND)
934 ofs++;
935 if ((*ilist)[ofs].info == D_AND) ofs++;
936 }
937 return -1;
938 }
939
940 #undef list
941
942 static int disambig_ofs; /* Offset where disambig failed */
943
944 /* ambig_flag stores what we were disambiguating the last time this
945 routine was called: it tells us where we failed so that if the
946 player enters new disambiguation information, we can figure out where
947 it should go */
948
disambig(int ambig_set,parse_rec * list,parse_rec * truenoun)949 static parse_rec *disambig(int ambig_set, parse_rec *list, parse_rec *truenoun)
950 /* ambig_set = 1 for actor, 2 for noun, 3 for object */
951 {
952 if (ambig_flag == ambig_set || ambig_flag == 0) { /* restart where we left off...*/
953 if (truenoun == NULL || truenoun[0].info == D_END) disambig_ofs = -1;
954 disambig_ofs = disambig_phrase(&list, truenoun, disambig_ofs, ambig_set);
955 if (disambig_ofs == -1) ambig_flag = 0; /* Success */
956 else if (disambig_ofs == -2) ambig_flag = -1; /* Error: elim all choices */
957 else ambig_flag = ambig_set;
958 }
959 return list;
960 }
961
962
963
964
965 /*-------------------------------------------------------------------*/
966 /* Noun parsing routines */
967 /*-------------------------------------------------------------------*/
968
969
970 /* PARSE_A_NOUN(), parses a single noun, leaves ip pointing after it. */
971 /* Just be greedy: grab as many of the input words as possible */
972 /* Leave ip pointing _after_ last word we get. */
973 /* Return list of all possible objects */
974 /* Go to some difficullty to make sure "all the kings men" will
975 not be accidentally parsed as "all" + "the kings men" */
976 /* (Yes, yes, I know -- you can't have an AGT object with a name as
977 complex as 'all the king's men'-- but you could try to simulate it using
978 synonyms) */
979 /* May also want to use more intellegence along the adj--noun distinction */
980 /* all_ok indicates whether ALL is acceptable */
981
parse_a_noun(void)982 static parse_rec *parse_a_noun(void)
983 /* Returns a list of parse_rec's containing the various possible
984 nouns. */
985 {
986 parse_rec *nlist;
987 char *errptr;
988 int i, tmp, numval, num, oip;
989
990 nlist = new_list();
991 oip = ip; /* Save starting input pointer value */
992
993 if (input[ip] == -1) /* End of input */
994 return nlist;
995 if (input[ip] == 0) { /* i.e. tokeniser threw up its hands */
996 numval = strtol(in_text[ip], &errptr, 10); /* Is it a number? */
997 if (errptr == in_text[ip]) /* Nope. */
998 return nlist;
999 if (*errptr != 0) return nlist; /* May want to be less picky */
1000 nlist = add_rec(nlist, 0, numval, D_NUM);
1001 ip++;
1002 return nlist;
1003 }
1004
1005 /* Basic strategy: try to match nouns. If all matches are of length<=1,
1006 then go looking for flag nouns, global nouns, ALL, DOOR, etc. */
1007 num = 0;
1008 objloop(i)
1009 if ((tmp = noun_syn(input[ip], i)) != 0) {
1010 numval = strtol(in_text[ip], &errptr, 10); /* Is it a number, too? */
1011 if (*errptr != 0) numval = 0; /* Only accept perfectly formed numbers */
1012 nlist = add_w_rec(nlist, i, numval,
1013 (tmp == 1) ? D_ADJ : (tmp == 2 ? D_SYN : D_NOUN),
1014 (tmp == 1) ? input[ip] : 0, /* Adjective */
1015 (tmp == 1) ? 0 : input[ip]); /* Noun */
1016 num++;
1017 }
1018
1019 /* Now we need to try to match more words and reduce our list. */
1020 /* Basically, we keep advancing ip until we get no matches at all. */
1021 /* Note that nouns may only come at the end, so if we find one we know */
1022 /* we are done. Synonyms and adjectives can come anywhere */
1023 /* (If PURE_SYN is set, then we won't see any synonyms-- they */
1024 /* get converted into nouns by noun_syn() */
1025 /* *num* is used to keep track of how many matches we have so we know */
1026 /* when to stop. */
1027
1028 /* compare against the next word in the queue */
1029 while (num > 0 && input[ip] != -1 && ip < MAXINPUT) {
1030 ip++;
1031 if (input[ip] == -1) break;
1032 num = 0;
1033 for (i = 0; nlist[i].info != D_END; i++) /* Which nouns match the new word? */
1034 if (nlist[i].info == D_NOUN) /* Nothing can come after a noun */
1035 nlist[i].info |= D_MARK;
1036 else if ((tmp = noun_syn(input[ip], nlist[i].obj)) == 0)
1037 nlist[i].info |= D_MARK; /* Mark this noun to be eliminated */
1038 else { /* Noun _does_ match */
1039 num++; /* Count them up */
1040 add_words_to_rec(&nlist[i], input[ip], tmp);
1041 }
1042 /* If we had any matches, kill the nouns that didn't match */
1043 if (num != 0) {
1044 nlist = purge_list(nlist);
1045 for (i = 0; nlist[i].info != D_END; i++)
1046 nlist[i].num = 0; /* Multi-word nouns can't be numbers */
1047 } else /* num==0; we need to clear all of the marks */
1048 clean_list(nlist);
1049 }
1050
1051 /* So at this point num is zero-- that is we have reached the limit
1052 of our matches. */
1053 /* If ip==oip is 0 (meaning no matches so far) or ip==oip+1
1054 (meaning we have a one-word match) then we need to check for
1055 flag nouns, global nouns, ALL, DOOR, pronouns, etc.
1056 and add them to the list if neccessary */
1057
1058 if (ip == oip || ip == oip + 1) {
1059
1060 /* First match the built in things... */
1061 if ((input[oip] == ext_code[wdoor] && aver <= AGX00)
1062 || input[oip] == ext_code[wscene])
1063 nlist = add_rec(nlist, -input[oip], 0, D_INTERN);
1064 else if (input[oip] == ext_code[wall] ||
1065 input[oip] == ext_code[weverything])
1066 nlist = add_rec(nlist, ALL_MARK, 0, D_ALL);
1067 else if (input[oip] == ext_code[whe] || input[oip] == ext_code[whim])
1068 nlist = add_rec(nlist, last_he, 0, D_PRO);
1069 else if (input[oip] == ext_code[wshe] || input[oip] == ext_code[wher])
1070 nlist = add_rec(nlist, last_she, 0, D_PRO);
1071 else if (input[oip] == ext_code[wit])
1072 nlist = add_rec(nlist, last_it, 0, D_PRO);
1073 else if (input[oip] == ext_code[wthey] || input[oip] == ext_code[wthem])
1074 nlist = add_rec(nlist, last_they, 0, D_PRO);
1075 else for (i = 0; i < 10; i++) /* Match direction names */
1076 if (input[oip] == syntbl[auxsyn[i]])
1077 nlist = add_rec(nlist, -syntbl[auxsyn[i]], 0, D_INTERN);
1078 else if (input[oip] == ext_code[weveryone] ||
1079 input[oip] == ext_code[weverybody])
1080 nlist = add_rec(nlist, -ext_code[weverybody], 0, D_INTERN);
1081
1082 /* Next look for number word matches */
1083 numval = strtol(in_text[oip], &errptr, 10); /* Is it a number? */
1084 if (*errptr == 0) /* Yes */
1085 nlist = add_rec(nlist, -input[oip], numval, D_NUM);
1086
1087 /* Next handle the flag nouns and global nouns */
1088 if (globalnoun != NULL)
1089 for (i = 0; i < numglobal; i++)
1090 if (input[oip] == globalnoun[i])
1091 nlist = add_rec(nlist, -input[oip], 0, D_GLOBAL);
1092 for (i = 0; i < MAX_FLAG_NOUN; i++)
1093 if (flag_noun[i] != 0 && input[oip] == flag_noun[i]
1094 #if 0
1095 && (room[loc].flag_noun_bits & (1L << i)) != 0
1096 #endif
1097 )
1098 nlist = add_rec(nlist, -input[oip], 0, D_FLAG);
1099
1100 /* Finally the PIX names */
1101 for (i = 0; i < MAX_PIX; i++)
1102 if (pix_name[i] != 0 && input[oip] == pix_name[i] &&
1103 (room[loc].PIX_bits & (1L << i)) != 0)
1104 nlist = add_rec(nlist, -input[oip], 0, D_PIX);
1105
1106 if (nlist[0].info != D_END) ip = oip + 1;
1107 }
1108 return nlist;
1109 }
1110
parse_noun(int and_ok,int is_actor)1111 static parse_rec *parse_noun(int and_ok, int is_actor)
1112 /* handles multiple nouns */
1113 /* and_ok indicates wheter multiple objects are acceptable */
1114 /* Either_ok indicates whether EITHER is okay (only true when
1115 resolving disambiguation) */
1116 {
1117 parse_rec *next, *lnoun_;
1118 int saveinfo;
1119 rbool all_except;
1120
1121 all_except = 0;
1122 next = lnoun_ = parse_a_noun();
1123 saveinfo = next[0].info;
1124
1125 #if 0 /* Let the main parser sort this out */
1126 if (!and_ok) return lnoun_; /* If no ANDs allowed, stop here. */
1127 #endif
1128 /* We need to explicitly handle the actor case here because
1129 the comma after an actor can be confused with the comma
1130 used to separate multiple objects */
1131 if (is_actor) return lnoun_; /* If no ANDs allowed, stop here. */
1132
1133 if (lnoun_[0].info == D_ALL && w_but(input[ip])) /* ALL EXCEPT ... */
1134 all_except = 1; /* This will cause us to skip over EXCEPT and
1135 start creating an AND list */
1136
1137 /* Now, itereate over <noun> AND <noun> AND ... */
1138 while ((all_except || w_and(input[ip])) &&
1139 saveinfo != D_END) { /* && saveinfo!=D_ALL */
1140 ip++; /* Skip over AND or EXCEPT */
1141 next = parse_a_noun();
1142 saveinfo = next[0].info;
1143 if (next[0].info != D_END) { /* We found a word */
1144 if (!all_except) lnoun_ = add_rec(lnoun_, AND_MARK, 0, D_AND);
1145 lnoun_ = concat_list(lnoun_, next);
1146 } else ip--; /* We hit trouble: back up to the AND */
1147 all_except = 0; /* Only skip EXCEPT once */
1148 rfree(next);
1149 }
1150 return lnoun_;
1151 }
1152
1153
parse_disambig_answer(void)1154 parse_rec *parse_disambig_answer(void) {
1155 parse_rec *temp;
1156
1157 if (input[ip + 1] == -1) {
1158 if (input[ip] == ext_code[wall] || input[ip] == ext_code[weverything]
1159 || input[ip] == ext_code[wboth]) {
1160 temp = new_list();
1161 ip++;
1162 return add_rec(temp, ALL_MARK, 0, D_ALL);
1163 }
1164 if (input[ip] == ext_code[weither] || input[ip] == ext_code[w_any]) {
1165 temp = new_list();
1166 ip++;
1167 return add_rec(temp, 0, 0, D_EITHER);
1168 }
1169 }
1170 return parse_noun(0, 0);
1171 }
1172
1173
1174
1175 /*-------------------------------------------------------------------*/
1176 /* Main parsing routines */
1177 /*-------------------------------------------------------------------*/
1178
1179
parse_cmd(void)1180 static int parse_cmd(void)
1181 /* Parse entered text and execute it, one statement at a time */
1182 /* Needs to leave ip pointing at beginning of next statement */
1183 {
1184 rbool new_actor; /* This is used for some error checking; it is set
1185 if we just found an actor on this command
1186 (as opposed to inheriting one from a previous
1187 command in a multiple statement) */
1188 parse_rec *tmp;
1189 int tp;
1190
1191 /* First go looking for an actor. */
1192 ap = ip;
1193 new_actor = 0;
1194 if (lactor == NULL) {
1195 new_actor = 1;
1196 lactor = parse_noun(0, 1);
1197 /* Check that actor is a creature. */
1198 if (lactor[0].info != D_END) {
1199 lactor = fix_actor(lactor); /* eliminate non-creatures */
1200 if (lactor[0].info == D_END) { /* Not a creature. */
1201 /* print intelligent error message */
1202 if (input[ip] == ext_code[wc]) /* ie there is a comma */
1203 return parseerr(229, "Who is this '$word$' you are addressing?", ap);
1204 else ip = ap; /* Otherwise, assume we shouldn't have parsed it as
1205 an actor-- it may be a verb. */
1206 }
1207 }
1208 if (lactor[0].info != D_END && input[ip] == ext_code[wc])
1209 ip++; /* this skips over a comma after an actor. */
1210 }
1211 /* Now onwards... */
1212 vp = ip;
1213 vnum = id_verb(); /* May increment ip (ip will point at last word in verb) */
1214 if (vnum == 0 && new_actor && lactor[0].info != D_END) {
1215 /* maybe actor is messing us up. */
1216 ip = ap; /* restart from beginning */
1217 vnum = id_verb();
1218 if (vnum == 0) /* if it's still bad, probably we really have an actor */
1219 ip = vp;
1220 else { /* no actor; really a verb */
1221 lactor[0].obj = 0;
1222 lactor[0].info = D_END;
1223 vp = ap;
1224 }
1225 }
1226
1227 TELLHack: /* This is used to restart the noun/prep/object scan
1228 if we find a TELL <actor> TO <verb> ... command */
1229
1230 if (vnum == 0)
1231 return parseerr(230, "I don't understand '$word$' as a verb.", ip);
1232
1233 /* Now we need to find noun, obj, and prep (if they all exist) */
1234 /* standard grammer: verb noun prep obj */
1235 prep = 0;
1236 np = ++ip; /* ip now points just _after_ verb */
1237 lnoun = parse_noun((verbflag[vnum] & VERB_MULTI) != 0, 0);
1238 /* leaves ip pointing just after it.;
1239 lnoun[0].info==D_END means no noun. */
1240 if (prep == 0) { /* prep==0 unless we've met the special TURN ON|OFF case */
1241 pp = ip;
1242 prep = parse_prep(); /* Should be trivial */
1243 op = ip;
1244 lobj = parse_noun(prep == 0, 0); /* and_ok if no prep */
1245 }
1246
1247 /* Check for TELL <actor> TO <verb> ... construction */
1248 /* If found, convert to <actor>, <verb> ... construction */
1249 if (lactor[0].info == D_END && lnoun[0].info != D_END &&
1250 vnum == 31 && prep == ext_code[wto] && !multinoun(lnoun)) {
1251 ip = op; /* Back up */
1252 rfree(lactor);
1253 rfree(lobj);
1254 lactor = lnoun;
1255 lnoun = NULL;
1256 vp = ip; /* Replace TELL with new verb */
1257 vnum = id_verb(); /* May increment ip (ip points att last word in verb) */
1258 goto TELLHack; /* Go back up and reparse the sentence from
1259 the new start point. */
1260 }
1261
1262 /* Convert TURN <noun> ON to TURN ON <noun> */
1263 if (vnum == 35 && (prep == ext_code[won] || prep == ext_code[woff])
1264 && lobj[0].info == D_END) {
1265 tmp = lobj;
1266 lobj = lnoun;
1267 lnoun = tmp;
1268 tp = op;
1269 np = op;
1270 op = tp;
1271 }
1272
1273
1274 /* For pre-Magx versions of AGT,
1275 convert <verb> <prep> <noun> ==> <verb> <noun> <prep> <noun> */
1276 if (aver < AGX00 && lnoun[0].info == D_END && lobj[0].info != D_END) {
1277 rfree(lnoun);
1278 lnoun = copy_list(lobj);
1279 np = op;
1280 }
1281
1282 /* Next we check to convert SHOOT <noun> AT <object> to
1283 SHOOT <object> WITH <noun> */
1284 if (vnum == 49 && prep == ext_code[wat] && !multinoun(lnoun)) {
1285 tmp = lobj;
1286 lobj = lnoun;
1287 lnoun = tmp;
1288 tp = np;
1289 np = op;
1290 op = tp;
1291 prep = ext_code[wwith];
1292 }
1293
1294 /* Now to convert SHOW <*nothing*> to SHOW EXITS */
1295 if (vnum == 40 && prep == 0 && lnoun[0].info == D_END && lobj[0].info == D_END)
1296 vnum = 42; /* LISTEXITS */
1297
1298 /* Convert LOOK <something> into EXAMINE <something> */
1299 if (smart_look && vnum == 19 && lnoun[0].info != D_END) vnum = 20;
1300
1301 /* need better error msgs */
1302 if ((verbflag[vnum]&VERB_MULTI) == 0 && multinoun(lnoun)) {
1303 /* Multiple objects when they are not allowed */
1304 int msgnum;
1305 if (vnum == 31) msgnum = 155; /* TALK */
1306 else if (vnum == 34) msgnum = 160; /* ASK */
1307 else msgnum = 231;
1308 return parseerr(msgnum,
1309 "The verb '$word$' doesn't take multiple objects.", vp);
1310 } else if (multinoun(lobj))
1311 return parseerr(232, "You can't use multiple indirect objects.", op);
1312 else if (lnoun[0].info == D_END && !w_isterm(input[np]) && np != pp)
1313 /* i.e. invalid noun */
1314 return parseerr(233, "I don't understand the word '$word$' as a noun.", np);
1315 else if (lnoun[0].obj == 0 && lnoun[0].info == D_PRO)
1316 return parseerr(234, "I don't know to what '$word$' refers.", np);
1317 else if (lobj[0].info == D_END && !w_isterm(input[op]))
1318 /* i.e. invalid object */
1319 return parseerr(235, "I don't understand the word '$word$' as a noun.", op);
1320 else if (lobj[0].obj == 0 && lobj[0].info == D_PRO)
1321 return parseerr(236, "I don't know to what '$word$' refers.", op);
1322 else if (!w_isterm(input[ip]))
1323 return parseerr(238, "Extra input found: \"$word$...\"", ip);
1324
1325 return 0;
1326 }
1327
1328
v_undo(void)1329 static void v_undo(void) {
1330 if (undo_state == NULL) {
1331 writeln("There is insufficiant memory to support UNDO");
1332 ip = -1;
1333 return;
1334 }
1335 if (can_undo == 0) {
1336 if (newlife_flag)
1337 writeln("You can't UNDO on the first turn.");
1338 else writeln("You can only UNDO one turn.");
1339 ip = -1;
1340 return;
1341 }
1342 writeln("");
1343 writeln("UNDOing a turn...");
1344 can_undo = 0;
1345 putstate(undo_state);
1346 ip = 1;
1347 set_statline();
1348 return;
1349 }
1350
parse(void)1351 rbool parse(void)
1352 /* Wrapper around parse_cmd to handle disambiguation, etc */
1353 /* It returns 1 if everything is okay; 0 if there is ambiguity */
1354 {
1355 parse_rec *currnoun;
1356 int fixword;
1357 int start_ip;
1358
1359 currnoun = NULL;
1360 start_ip = ip;
1361 /* First, we need to see if someone has issued an OOPS command.
1362 OOPS commands are always assumed to stand alone. (i.e. no
1363 THEN nonsense). OOPS commands are always of the form
1364 OOPS <word> */
1365 if (ip == 0 && input[0] == ext_code[woops] && input[1] > 0 &&
1366 input[2] == -1 && ep > -1) {
1367 fixword = input[ip + 1];
1368 restore_input();
1369 input[ep] = fixword;
1370 ambig_flag = 0;
1371 }
1372 ep = -1;
1373
1374
1375 /* Next, we need to determine if someone is trying to do
1376 disambiguation. This is only the case if
1377 i)ambig_flag is set
1378 ii)ip=0 (no multiple command nonsense)
1379 iii)there is only one noun on the line. */
1380 if (ip != 0) ambig_flag = 0;
1381 if (ambig_flag) {
1382 currnoun = parse_disambig_answer();
1383 if (input[ip] == -1 && currnoun[0].info != D_END) {
1384 restore_input(); /* Yep, we're disambiguaing. */
1385 ip = parse_ip;
1386 } else { /* nope; it's a new command */
1387 ip = 0;
1388 ambig_flag = 0;
1389 rfree(currnoun);
1390 freeall();
1391 currnoun = NULL;
1392 }
1393 }
1394
1395 /* Next we go looking for UNDO; again, this must be the first
1396 thing on an empty line. */
1397 if (ip == 0 && input[0] == ext_code[wundo] && input[1] == -1) {
1398 v_undo();
1399 return 1;
1400 }
1401
1402 save_input();
1403
1404 /* If starting a new line, clear out old the old actor */
1405 if (ip == 0) {
1406 actor_in_scope = 0; /* Clear this */
1407 rfree(lactor); /* This resets lactor to NULL */
1408 }
1409
1410 if (!ambig_flag)
1411 if (parse_cmd() == -1)
1412 return 1; /* error condition */
1413
1414 parse_ip = ip;
1415
1416 if (debug_parse)
1417 parse_out(lactor, vnum, lnoun, prep, lobj);
1418
1419 /*Disambiguation routines; do it here instead of earlier to get
1420 error messages in the right place (it's silly and annoying to ask the
1421 player for disambiguation and then die on a parse error after they've
1422 provided it) */
1423 compute_scope(); /* The disambig routines use this information */
1424 lactor = disambig(1, lactor, currnoun);
1425 lnoun = disambig(2, lnoun, currnoun);
1426 lobj = disambig(3, lobj, currnoun);
1427 if (ambig_flag > 0) return 0; /* We need to get disambig info */
1428 if (ambig_flag == -1) {
1429 ambig_flag = 0;
1430 return 1;
1431 }
1432 /* We got rid of too much */
1433 rfree(currnoun);
1434
1435 /* Next, expand ALL if neccessary */
1436 if (!PURE_ALL && lnoun[0].info == D_ALL) {
1437 lnoun = expand_all(lnoun);
1438 if (lnoun[0].info == D_END) { /* ALL expands to nothing */
1439 int msgnum;
1440 if (vnum >= 15 && vnum <= 24)
1441 msgnum = all_err_msg[vnum - 15];
1442 else
1443 msgnum = 239;
1444 parseerr(msgnum, "I don't know what you are referring to.", np);
1445 return 1;
1446 }
1447 }
1448
1449 /* Now that we know that we have an executable command,
1450 we save the undo state before executing if this is the first command
1451 in a sequence. (That is, UNDO undoes whole lines of commands,
1452 not just individual commands) */
1453 if (start_ip == 0 && undo_state != NULL) {
1454 undo_state = getstate(undo_state);
1455 can_undo = 1;
1456 }
1457
1458 /* Now to actually execute the command that has been parsed. */
1459 /* Remember: disambiguation has been done by this time. */
1460
1461 exec(&lactor[0], vnum, lnoun, prep, &lobj[0]);
1462 rfree(lobj);
1463
1464 /* exec is responsible for freeing or whatever lnoun (this is for AGAIN) */
1465
1466 /* Now we clear lnoun and lobj; lactor is handled elsewhere since
1467 we might have FRED, GET ROCK THEN GO NORTH */
1468 lnoun = lobj = NULL;
1469
1470 /* Finally check for THENs */
1471
1472 if (ip != -1 && w_and(input[ip]) && input[ip + 1] == ext_code[wthen])
1473 ip++; /* AND THEN construction */
1474 if (ip != -1 && input[ip] != -1) ip++;
1475 return 1;
1476 }
1477
1478
1479
1480
menu_cmd(void)1481 void menu_cmd(void) {
1482 int i, j;
1483 int choice;
1484 char *curr_cmd, *tmp1, *tmp2; /* String of current command */
1485 int objcnt; /* Number of objects taken by the current verb */
1486 int verbword; /* Verb word */
1487 parse_rec actrec;
1488
1489 parse_rec mobj;
1490 int vnum_; /* Verb number */
1491 word prep_;
1492
1493 menuentry *nounmenu;
1494 int *nounval; /* Object id's for the menu entries */
1495 int nm_size, nm_width; /* Size and width of noun menu */
1496
1497
1498 nounval = NULL;
1499 nounmenu = NULL;
1500 /* Get verb+prep */
1501 choice = agt_menu("", vm_size, vm_width, verbmenu);
1502 if (choice == -1 || doing_restore) return;
1503
1504 verbword = verbinfo[choice].verb;
1505 prep_ = verbinfo[choice].prep;
1506 objcnt = verbinfo[choice].objnum;
1507
1508 /* Now identify the verb */
1509 ip = 0;
1510 input[0] = verbword;
1511 input[1] = input[2] = -1;
1512 if (objcnt <= 1 && prep_ != 0) input[1] = prep_;
1513 vnum_ = id_verb();
1514
1515 lnoun = (parse_rec *)rmalloc(sizeof(parse_rec) * 2);
1516 lnoun[0].obj = 0;
1517 lnoun[0].num = 0;
1518 lnoun[0].info = D_END;
1519
1520 nm_size = nm_width = 0;
1521
1522 if (objcnt >= 1) {
1523 /* Construct noun list */
1524 nounval = get_nouns();
1525 for (nm_size = 0; nounval[nm_size] != 0; nm_size++);
1526 nounmenu = (menuentry *)rmalloc(nm_size * sizeof(menuentry));
1527 nm_width = 0;
1528 for (i = 0; i < nm_size; i++) {
1529 tmp1 = objname(nounval[i]);
1530 strncpy(nounmenu[i], tmp1, MENU_WIDTH);
1531 j = strlen(tmp1);
1532 if (j > nm_width) nm_width = j;
1533 }
1534 if (nm_width > MENU_WIDTH) nm_width = MENU_WIDTH;
1535
1536 if (objcnt >= 2 || prep_ == 0)
1537 curr_cmd = rstrdup(dict[verbword]);
1538 else
1539 curr_cmd = concdup(dict[verbword], dict[prep_]);
1540
1541 choice = agt_menu(curr_cmd, nm_size, nm_width, nounmenu);
1542 rfree(curr_cmd);
1543 if (choice == -1 || doing_restore) {
1544 rfree(nounmenu);
1545 rfree(nounval);
1546 rfree(lnoun);
1547 return;
1548 }
1549
1550 if (objcnt == 1 && prep_ != 0) { /* VERB PREP OBJ construction */
1551 mobj.obj = nounval[choice];
1552 mobj.num = 0;
1553 mobj.info = D_NOUN;
1554 } else { /* Normal VERB OBJ construction */
1555 lnoun[0].obj = nounval[choice];
1556 lnoun[0].num = 0;
1557 lnoun[0].info = D_NOUN;
1558 lnoun[1].obj = 0;
1559 lnoun[1].num = 0;
1560 lnoun[1].info = D_END;
1561 }
1562 }
1563
1564 if (objcnt >= 2) {
1565 tmp1 = objname(lnoun[0].obj); /* Build up current command line */
1566 tmp2 = concdup(dict[verbword], tmp1); /* VERB NOUN */
1567 rfree(tmp1);
1568 curr_cmd = concdup(tmp2, dict[prep_]); /* VERB NOUN PREP */
1569 rfree(tmp2);
1570
1571 choice = agt_menu(curr_cmd, nm_size, nm_width, nounmenu);
1572 rfree(curr_cmd);
1573 if (choice == -1 || doing_restore) {
1574 rfree(lnoun);
1575 rfree(nounmenu);
1576 rfree(nounval);
1577 return;
1578 }
1579
1580 mobj.obj = nounval[choice];
1581 mobj.num = 0;
1582 mobj.info = D_NOUN;
1583 }
1584
1585 rfree(nounmenu);
1586 rfree(nounval);
1587
1588 if (vnum_ == OLD_VERB + 3) { /* UNDO */
1589 v_undo();
1590 return;
1591 }
1592
1593 if (undo_state != NULL) {
1594 undo_state = getstate(undo_state);
1595 can_undo = 1;
1596 }
1597
1598 /* Now to actually execute the command that has been selected. */
1599 tmpobj(&actrec);
1600 actrec.obj = 0;
1601 exec(&actrec, vnum_, lnoun, prep_, &mobj);
1602 lnoun = NULL; /* exec() is responsible for freeing lnoun */
1603 }
1604
1605
1606 /* Grammer structures:
1607 sverb, dverb (n,s,e,w,...,q,l,....)
1608 overb noun (close,examine,read,eat,drink,pull,light,ext)
1609 averb noun|ALL (drop,get,wear,remove)
1610 TURN noun ON|OFF
1611 TURN ON|OFF noun
1612 a-verb noun ABOUT obj (tell, ask)
1613 pverb noun [prep object] (put, throw)
1614 w-verb noun [WITH object] (attack, open, lock, unlock, push, shoot)
1615 SHOOT noun [AT object]
1616 dummy noun [prep obj]
1617 ( pverb obj noun ==> pverb noun TO obj e.g. give dog the bone)
1618 */
1619
1620 } // End of namespace AGT
1621 } // End of namespace Glk
1622