aboutsummaryrefslogtreecommitdiff
path: root/engines/glk/agt/parser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/glk/agt/parser.cpp')
-rw-r--r--engines/glk/agt/parser.cpp1621
1 files changed, 1621 insertions, 0 deletions
diff --git a/engines/glk/agt/parser.cpp b/engines/glk/agt/parser.cpp
new file mode 100644
index 0000000000..72bb9eaa7b
--- /dev/null
+++ b/engines/glk/agt/parser.cpp
@@ -0,0 +1,1621 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/agt/agility.h"
+#include "glk/agt/interp.h"
+
+namespace Glk {
+namespace AGT {
+
+/*
+
+ This is probably the ugliest and least readable of all of the
+ source files. The parser isn't really that complex in principle,
+ but it has to deal with a lot of of special cases and also be
+ downward-compatible with the original parsers (which sometimes did
+ strange things) which adds a lot of additional code. The noun parsing
+ code is particularly convoluted.
+
+ Also, there are a fair number of global variables in this module
+ that are really local variables at heart but ended up global
+ because they were needed in too many different places and I didn't
+ want to complicate things even more by having to explicitly pass
+ them all up and down through the parsing routines. (One of those
+ times it would be convenient to have the ability to nest procedures
+ and functions like Pascal.)
+
+*/
+
+/* These store the previous turn's values for use in disambiguation and
+ implementing OOPS */
+/* parse_ip saves the ip after parsing, ip_back saves it before. */
+word input_back[MAXINPUT];
+words in_text_back[MAXINPUT];
+int ip_back, parse_ip;
+
+/* The following are global only for rfree() purposes and
+ in order to maintain state for disambiguation */
+static int vnum; /* Verb number from synonym scan */
+
+/* Pointers to negative-terminated arrays of possible nouns */
+static parse_rec *lactor = NULL, *lobj = NULL, *lnoun = NULL;
+
+static int ambig_flag = 0;
+/* Was last input ambiguous? (so player could be entering
+disambiguation info?) 1=ambig actor, 2=ambig noun,
+3=ambig obj */
+
+
+/* Empty ALL error messages, for those verbs that have their own */
+int all_err_msg[] = {73, 83, 113, 103, /* open, close, lock, unlock: 15 - 18 */
+ 239, 239, 239, 239, /* 19 - 22 */
+ 123, 125
+ }; /* eat, drink: 23 - 24 */
+
+
+
+/*-------------------------------------------------------------------*/
+/* DEBUGGING OUTPUT FUNCTIONS & MISC UTILITIES */
+/*-------------------------------------------------------------------*/
+
+/* This routine frees the space used by the parsing data structures */
+static void freeall(void) {
+ rfree(lnoun);
+ rfree(lobj);
+ lnoun = lobj = NULL;
+}
+
+
+/* Print out parse error message and abort the parse */
+static int parseerr(int msgid, const char *s, int n) {
+ if (n >= 0)
+ gen_sysmsg(msgid, s, MSG_PARSE, in_text[n]);
+ else
+ gen_sysmsg(msgid, s, MSG_PARSE, "");
+ freeall();
+ ep = n;
+ ip = -1;
+ return -1;
+}
+
+
+/* Print out noun list; used for debugging the parser */
+static void print_nlist(parse_rec *n) {
+ char *s;
+ int c;
+ char buff[100];
+
+ if (n->info == D_END)
+ writestr("----");
+ if (n->info == D_ALL) {
+ writestr("ALL ");
+ n++;
+ }
+ for (c = 0; n->info != D_END && c < 20; n++, c++)
+ if (n->info == D_AND) writestr(" AND ");
+ else if (n->info == D_NUM) { /* Number entered */
+ sprintf(buff, "#%ld(%d); ", n->num, n->obj);
+ writestr(buff);
+ } else if (n->obj < 0) {
+ writestr(dict[-(n->obj)]);
+ sprintf(buff, "(%d); ", n->obj);
+ writestr(buff);
+ } else {
+ s = objname(n->obj);
+ writestr(s);
+ rfree(s);
+ sprintf(buff, "(%d) ['%s %s']; ", n->obj, dict[n->adj], dict[n->noun]);
+ writestr(buff);
+ }
+ if (n->info != D_END) writestr("///");
+ writeln("");
+}
+
+/* Output the parser's analysis of the current input line; used
+ for debugging */
+static int parse_out(parse_rec *lactor_, int vb_, parse_rec *lnoun_, int prep_,
+ parse_rec *lobj_) {
+ writeln("ANALYSIS:");
+ writestr("Actor: ");
+ print_nlist(lactor_);
+ writestr("Verb:");
+ writeln(dict[ syntbl[auxsyn[vb_]] ]);
+ writestr("DObj: ");
+ print_nlist(lnoun_);
+ writestr("Prep: ");
+ if (prep_ != 0) writeln(dict[prep_]);
+ else writeln("---");
+ writestr("IObj: ");
+ print_nlist(lobj_);
+ return 0;
+}
+
+
+static void save_input(void) {
+ int i;
+
+ for (i = 0; input[i] != -1 && i < MAXINPUT; i++) {
+ input_back[i] = input[i];
+ strncpy(in_text_back[i], in_text[i], 24);
+ }
+ input_back[i] = -1;
+ ip_back = ip;
+}
+
+static void restore_input(void) {
+ int i;
+
+ for (i = 0; input_back[i] != -1 && i < MAXINPUT; i++) {
+ input[i] = input_back[i];
+ strncpy(in_text[i], in_text_back[i], 24);
+ }
+ input[i] = -1;
+ ip = ip_back;
+}
+
+
+
+/*-------------------------------------------------------------------*/
+/* Misc. Parsing Routines (includes parsing of verbs and preps) */
+/*-------------------------------------------------------------------*/
+
+#define w_and(w) (w==ext_code[wand] || w==ext_code[wc])
+#define w_but(w) (w==ext_code[wbut] || w==ext_code[wexcept])
+#define w_isterm(w) (w==ext_code[wp] || w==ext_code[wthen] || \
+ w==ext_code[wsc] || w_and(w) || w==-1)
+
+
+
+static word check_comb(int combptr) {
+ int k;
+ word w;
+
+ if (combptr == 0) return 0;
+ w = syntbl[combptr];
+ for (combptr += 1, k = ip; syntbl[combptr] != 0; combptr++, k++)
+ if (syntbl[combptr] != input[k]) break;
+ if (syntbl[combptr] == 0) {
+ ip = k - 1;
+ return w;
+ }
+ return 0;
+}
+
+
+static int comb_verb(void)
+/* This eliminates two-word verbs */
+{
+ int i;
+ word w;
+
+ for (i = 0; i < num_comb; i++) {
+ w = check_comb(comblist[i]);
+ if (w != 0) return w;
+ }
+
+ if (input[ip] == ext_code[wgo] && verb_authorsyn(ext_code[wgo]) == 0) {
+ /* GO <dir> --> <dir> */
+ w = input[ip + 1];
+ if (w != 0) i = verb_builtin(w);
+ else i = 0;
+ if (i != 0) {
+ ip++;
+ return w;
+ }
+ }
+
+ for (i = 0; i < num_auxcomb; i++) {
+ w = check_comb(auxcomb[i]);
+ if (w != 0) return w;
+ }
+
+ return input[ip];
+}
+
+
+
+
+
+/* This return true if the word in question is in the list
+ of original AGT verbs, but is not the canonical verb.
+ This is needed because verbs on this list
+ are not overridden by dummy_verbs in pre-Magx games. */
+static rbool orig_agt_verb(word w) {
+ int i;
+ if (aver <= AGT10 && w == ext_code[wg]) return 0; /* AGT 1.0 didn't have AGAIN */
+ for (i = 0; old_agt_verb[i] != -1 && old_agt_verb[i] != w; i++);
+ return (old_agt_verb[i] == w);
+}
+
+/* A few comments on id_verb:
+ The sequence is as follows:
+ i) Convert built-in synonyms to the base form (TAKE-->GET, for example)
+ _unless_ there is an author-defined synonym. (The original AGT
+ didn't have this 'unless'.)
+ ii) Check room-synonyms
+ iii) ID the verb based first on game-specific synonyms and then
+ falling back to the built-in ones.
+ (AGT gave the built-in ones higher priority than the game-specific
+ ones, but this causes problems and is generally a bad idea.)
+ */
+
+static int id_verb(void)
+/* Identify verb at ip=i ; return verb id if found, 0 otherwise */
+{
+ word w;
+ int j, canon_word, tmp;
+
+ w = comb_verb(); /* Combine 2-word verbs */
+ if (w == 0) return 0;
+
+ /* Pre-Canonization of w: see if w has any built-in synonyms */
+ canon_word = verb_builtin(w);
+ if (canon_word != 0) {
+ if (aver < AGX00 && orig_agt_verb(w))
+ /* In orig AGT, author-defined verbs don't override builtin syns */
+ tmp = 0;
+ else
+ tmp = verb_authorsyn(w); /* Author-defined verbs override built-in ones */
+ if (tmp == 0 || tmp == canon_word)
+ w = syntbl[auxsyn[canon_word]];
+ }
+
+ /* Now check room-specific synonyms (being the most localized,
+ they have the highest priority) */
+ for (j = room[loc].replacing_word; syntbl[j] != 0; j++)
+ if (w == syntbl[j])
+ w = room[loc].replace_word;
+
+ /* Next check to see if we already have the canonical form of the verb. */
+ /* and go through the built-in list of synonyms */
+ canon_word = verb_code(w);
+ if (!PURE_DUMMY && canon_word == 57) canon_word = 0; /* AFTER */
+ return canon_word;
+}
+
+
+#define compr_prep(w1,w2,r) {if (w1==input[ip] && w2==input[ip+1]) \
+ {ip+=2;return r;}}
+#define cprep(c1,c2,r) compr_prep(ext_code[c1],ext_code[c2],ext_code[r])
+
+/* Eventually should add support for the handful of two word preps */
+/* (eg IN TO, OUT OF,...); otherwise, this is pretty trivial. */
+static int parse_prep(void) {
+ int i;
+ int j, k;
+
+ for (j = 0; j < num_prep; j++) { /* Scan user-defined prepositions */
+ /* This table is formatted like the multi-verb table:
+ end-prep prep-word1 prepword2 etc. */
+ for (k = 0; syntbl[userprep[j] + k + 1] != 0; k++)
+ if (syntbl[userprep[j] + k + 1] != input[ip + k]) break;
+ if (syntbl[userprep[j] + k + 1] == 0) {
+ ip += k;
+ return syntbl[userprep[j]];
+ }
+ }
+ cprep(win, wto, winto);
+ cprep(wout, wof, wfrom);
+ for (i = win; i <= wabout; ++i)
+ if (ext_code[i] == input[ip]) return input[ip++];
+ return 0;
+}
+
+
+
+static int noun_syn(word w, int obj)
+/* Is the word w a synonym for the object obj? */
+/* obj is encoded as ususal. */
+/* 0=no match, 1=adjective match, 2=synonym match, 3=noun match */
+/* 2 will only occur if PURE_SYN is false */
+{
+ int i;
+
+ if (w <= 0) return 0;
+
+ if (obj >= first_noun && obj <= maxnoun) {
+ obj = obj - first_noun;
+ if (w == noun[obj].name) return 3;
+ if (noun[obj].has_syns)
+ for (i = noun[obj].syns; syntbl[i] != 0; i++)
+ if (w == syntbl[i]) return (PURE_SYN ? 3 : 2);
+ if (w == noun[obj].adj) return 1;
+ return 0;
+ }
+ if (obj >= first_creat && obj <= maxcreat) {
+ obj = obj - first_creat;
+ if (w == creature[obj].name) return 3;
+ if (creature[obj].has_syns)
+ for (i = creature[obj].syns; syntbl[i] != 0; i++)
+ if (w == syntbl[i]) return (PURE_SYN ? 3 : 2);
+ if (w == creature[obj].adj) return 1;
+ return 0;
+ }
+ return 0; /* If the object doesn't exist, can't have synonyms */
+}
+
+
+
+/*-------------------------------------------------------------------*/
+/* Noun-list manipulation functions. */
+/*-------------------------------------------------------------------*/
+
+static parse_rec *new_list(void) {
+ parse_rec *list;
+
+ list = (parse_rec *)rmalloc(sizeof(parse_rec));
+ list[0].obj = 0;
+ list[0].num = 0;
+ list[0].adj = list[0].noun = 0;
+ list[0].info = D_END;
+ return list;
+}
+
+static parse_rec *add_w_rec(parse_rec *pold, int obj0, long num0, int info0,
+ word adj0, word noun0) {
+ parse_rec *pnew;
+ int n;
+
+ for (n = 0; pold[n].info != D_END; n++);
+ pnew = (parse_rec *)rrealloc(pold, (n + 2) * sizeof(parse_rec));
+ pnew[n].obj = obj0;
+ pnew[n].num = num0;
+ pnew[n].info = info0;
+ pnew[n].adj = adj0;
+ pnew[n].noun = noun0;
+ pnew[n + 1].obj = 0;
+ pnew[n + 1].info = D_END;
+ return pnew;
+}
+
+static parse_rec *add_rec(parse_rec *old, int obj0, long num0, int info0) {
+ word w;
+
+ if (obj0 < 0) w = -obj0; /* The NOUN field of literal words will just be
+ that word */
+ else w = 0;
+ return add_w_rec(old, obj0, num0, info0, 0, w);
+}
+
+
+static parse_rec *kill_rec(parse_rec *old, int index)
+/* Remove record old[index] */
+{
+ parse_rec *pnew;
+ int i;
+
+ for (i = index; old[i].info != D_END; i++) {
+ old[i].obj = old[i + 1].obj;
+ old[i].num = old[i + 1].num;
+ old[i].noun = old[i + 1].noun;
+ old[i].adj = old[i + 1].adj;
+ old[i].info = old[i + 1].info;
+ old[i].score = old[i + 1].score;
+ }
+ pnew = (parse_rec *)rrealloc(old, i * sizeof(parse_rec)); /* Shrink it by one */
+ return pnew;
+}
+
+static parse_rec *concat_list(parse_rec *dest, parse_rec *src) {
+ int i, j;
+
+ for (i = 0; dest[i].info != D_END; i++); /* Put i at end of first list */
+ for (j = 0; src[j].info != D_END; j++); /* j marks end of the second list */
+ dest = (parse_rec *)rrealloc(dest, sizeof(parse_rec) * (i + j + 1));
+ memcpy(dest + i, src, (j + 1)*sizeof(parse_rec));
+ return dest;
+}
+
+static parse_rec *purge_list(parse_rec *list)
+/* It should be possible to make this more efficiant */
+{
+ int i;
+
+ for (i = 0; list[i].info != D_END;)
+ if ((list[i].info & D_MARK) != 0)
+ list = kill_rec(list, i);
+ else i++; /* (Note: we only increment i if we _don't_ kill) */
+ return list;
+}
+
+static void clean_list(parse_rec *list) {
+ for (; list->info != D_END; list++)
+ list->info &= ~D_MARK; /* Clear mark */
+}
+
+
+static parse_rec *copy_list(parse_rec *list) {
+ parse_rec *cpy;
+ int i;
+
+ cpy = new_list();
+ for (i = 0; list[i].info != D_END; i++);
+ cpy = (parse_rec *)rmalloc(sizeof(parse_rec) * (i + 1));
+ memcpy(cpy, list, (i + 1)*sizeof(parse_rec));
+ return cpy;
+}
+
+
+
+/* Among other things, this is used to add disambiguation information */
+static int scan_rec(int item, int num, parse_rec *list) {
+ int i;
+
+ for (i = 0; list[i].info != D_END && list[i].info != D_AND; i++)
+ if (list[i].obj == item && list[i].num == num)
+ return i;
+ return -1;
+}
+
+static rbool scan_andrec(int item, parse_rec *list) {
+ for (; list->info != D_END; list++)
+ if (list->obj == item && list->info != D_AND
+ && list->info != D_ALL) return 1;
+ return 0;
+}
+
+static rbool multinoun(parse_rec *list)
+/* Determines if LIST refers to a multiple object */
+{
+ if (list->info == D_ALL) return 1;
+ for (; list->info != D_END; list++)
+ if (list->info == D_AND) return 1;
+ return 0;
+}
+
+/* This updates the adj and noun fields of a record and also
+ updates the disambiguation priority */
+/* We are already either D_ADJ or D_SYN; this just handles
+ possible promotion */
+static void add_words_to_rec(parse_rec *nrec, word w, int tmp) {
+ word w2;
+
+ if (tmp == 2 || tmp == 3) {
+ w2 = nrec->noun;
+ nrec->noun = w;
+ w = w2;
+ }
+ if (nrec->adj == 0)
+ nrec->adj = w;
+ if (tmp == 2) nrec->info = D_SYN;
+ else if (tmp == 3) nrec->info = D_NOUN;
+}
+
+/* This updates nrec with the information in newobj; this routine
+ is called while adding disambiguation information; newobj is from
+ what the player just typed in to clarify the ambiguity */
+void update_rec_words(parse_rec *nrec, parse_rec *newobj) {
+ int tmp;
+
+ if (nrec->adj == 0) nrec->adj = newobj->adj;
+ if (newobj->info == D_ADJ) tmp = 1;
+ else if (newobj->info == D_SYN) tmp = 2;
+ else if (newobj->info == D_NOUN) tmp = 3;
+ else return;
+ add_words_to_rec(nrec, newobj->noun, tmp);
+}
+
+
+static rbool ident_objrec(parse_rec *p1, parse_rec *p2) {
+ word noun1, adj1, noun2, adj2;
+
+ if (p1->obj == p2->obj) return 1;
+ if (p1->obj <= 0 || p2->obj <= 0) return 0;
+ if (tnoun(p1->obj)) {
+ noun1 = noun[p1->obj - first_noun].name;
+ adj1 = noun[p1->obj - first_noun].adj;
+ } else if (tcreat(p1->obj)) {
+ noun1 = creature[p1->obj - first_creat].name;
+ adj1 = creature[p1->obj - first_creat].adj;
+ } else return 0;
+ if (tnoun(p2->obj)) {
+ noun2 = noun[p2->obj - first_noun].name;
+ adj2 = noun[p2->obj - first_noun].adj;
+ } else if (tcreat(p2->obj)) {
+ noun2 = creature[p2->obj - first_creat].name;
+ adj2 = creature[p2->obj - first_creat].adj;
+ } else return 0;
+ return (noun1 == noun2 && adj1 == adj2);
+}
+
+/*-------------------------------------------------------------------*/
+/* Disambiguation and ALL expansion routines */
+/*-------------------------------------------------------------------*/
+
+/* Eliminate non-creatures and non-present creatures from list. */
+/* Very similar to disambig below, but simpler. */
+static parse_rec *fix_actor(parse_rec *alist) {
+ int i, cnt;
+
+ assert(alist != NULL);
+ if (alist[0].info == D_ALL) { /* ALL?! */
+ rfree(alist);
+ return new_list();
+ }
+
+ /* Start by eliminating non-creatures */
+ cnt = 0;
+ for (i = 0; alist[i].info != D_END; i++)
+ if ((alist[i].obj < first_creat || alist[i].obj > maxcreat)
+ && alist[i].obj != -ext_code[weverybody]) {
+ if (alist[i].info != D_AND)
+ alist[i].info |= D_MARK;
+ } else cnt++;
+ alist = purge_list(alist);
+ if (cnt <= 1) return alist;
+
+ /* Now eliminate those not present */
+ cnt = 0;
+ for (i = 0; alist[i].info != D_END; i++)
+ if (!genvisible(&alist[i])) {
+ if (alist[i].info != D_AND)
+ alist[i].info |= D_MARK;
+ } else cnt++;
+
+ if (cnt == 0) alist[0].info &= ~D_MARK;
+
+ return purge_list(alist);
+}
+
+
+/* Convert disambig list to all list, moving things up. */
+static parse_rec *convert_to_all(parse_rec *list, int *ofsref) {
+ int i;
+ int cnt;
+
+ for (i = *ofsref; list[i].info != D_AND && list[i].info != D_END; i++);
+ cnt = i - *ofsref; /* Number of objects. We will add cnt-1 ANDs */
+
+ while (list[i].info != D_END) i++;
+ list = (parse_rec *)rrealloc(list, (i + cnt) * sizeof(parse_rec));
+ memmove(list + *ofsref + 2 * cnt - 1, list + *ofsref + cnt,
+ (i + 1 - cnt - *ofsref)*sizeof(parse_rec));
+ for (i = cnt - 1; i >= 0; i--) {
+ int k;
+ list[*ofsref + 2 * i] = list[*ofsref + i];
+ if (i == 0) break;
+ k = *ofsref + 2 * i - 1;
+ list[k].obj = 0;
+ list[k].num = 0;
+ list[k].adj = list[k].noun = 0;
+ list[k].info = D_AND;
+ }
+ /* *ofsref+=2*cnt-1; */
+ return list;
+}
+
+
+
+static parse_rec *add_disambig_info(parse_rec *ilist, int ofs,
+ parse_rec *truenoun)
+/* Basically, try to find interesection of tmp and truenoun,
+or failing that, between ilist and truenoun */
+/* truenoun is what the player just typed in to resolve
+the ambiguity */
+{
+ int i, n;
+
+ for (i = ofs; ilist[i].info != D_AND && ilist[i].info != D_END; i++) {
+ if (truenoun[0].info == D_EITHER) {
+ /* Mark all but the first for deletion */
+ if (i > ofs) ilist[i].info |= D_MARK;
+ } else {
+ n = scan_rec(ilist[i].obj, ilist[i].num, truenoun);
+ if (n == -1)
+ ilist[i].info |= D_MARK;
+ else /* Add any new information to the words */
+ update_rec_words(&ilist[i], &truenoun[n]);
+ }
+ }
+ ilist = purge_list(ilist);
+ truenoun[0].obj = 0;
+ truenoun[0].num = 0;
+ truenoun[0].info = D_END; /* Truenoun no longer useful */
+ return ilist;
+}
+
+static int max_disambig_score;
+
+static int score_disambig(parse_rec *rec, int ambig_type)
+/* This is just a wrapper for check_obj (defined in runverb.c) */
+/* ambig_type=1 for actor, 2 for noun, 3 for object */
+/* We can assume that the earlier bits have already been disambiguated */
+/* Parse of current command in lactor, vnum, lnoun, prep, and lobj */
+{
+ if (ambig_type == 1) /* ACTOR */
+ return DISAMBIG_SUCC;
+ else if (ambig_type == 2) /* NOUN */
+ return check_obj(lactor, vnum, rec, prep, NULL);
+ else if (ambig_type == 3) /* IOBJ */
+ return check_obj(lactor, vnum, lnoun, prep, rec);
+ else fatal("Invalid ambig_type!");
+ return 0;
+}
+
+
+/* This routine does all expansion: it returns a list of ALL objects in the
+ current context (lactor, vnum, <ALL>, prep, lobj) */
+/* lnoun is assumed to be a list of exceptions, which needs to be freed
+ at the end. (i.e. we want to expand to ALL EXCEPT <lnoun>) */
+static parse_rec *expand_all(parse_rec *lnoun_) {
+ parse_rec *list;
+ int i, j;
+ rbool prev_obj; /* Is there a previous object on the list? */
+ rbool kill_obj; /* Don't put current object on ALL list after all */
+ parse_rec temp_obj; /* Used to pass object info to disambiguation routine */
+
+ if (debug_parse) {
+ writestr("ALL BUT:");
+ print_nlist(lnoun_);
+ }
+ tmpobj(&temp_obj);
+ nounloop(i)
+ noun[i].scratch = 0;
+ creatloop(i)
+ creature[i].scratch = 0;
+ objloop(i)
+ if (((verbflag[vnum]&VERB_GLOBAL) != 0 || visible(i))
+ && (lnoun_ == NULL || !scan_andrec(i, lnoun_))) {
+ temp_obj.obj = i;
+ if (score_disambig(&temp_obj, 2) >= 500) {
+ if (tnoun(i)) noun[i - first_noun].scratch = 1;
+ else if (tcreat(i)) creature[i - first_creat].scratch = 1;
+ else writeln("INTERNAL ERROR: Invalid object type in expand_all().");
+ }
+ }
+
+ /* The following ensures that if an object and it's container
+ are both selected, only the container will actually make it
+ onto the list.*/
+ list = new_list();
+ prev_obj = 0;
+ objloop(i)
+ if (it_scratch(i)) {
+ kill_obj = 0;
+ for (j = it_loc(i); tnoun(j) || tcreat(j); j = it_loc(j))
+ if (it_scratch(j)) {
+ kill_obj = 1;
+ break;
+ }
+ if (kill_obj) continue;
+ /* Now actually add object to list. */
+ if (prev_obj) list = add_rec(list, 0, 0, D_AND);
+ list = add_rec(list, i, 0, D_SYN);
+ prev_obj = 1;
+ }
+
+ if (debug_parse) {
+ writestr("ALL==>");
+ print_nlist(list);
+ }
+ rfree(lnoun_);
+ return list;
+}
+
+
+
+/* disambig check checks for the various things that can eliminate a noun
+ from the list. The higher dlev is, the more things that are bad */
+/* Returns 1 if we should keep it, 0 if we should kill it */
+/* The elimination order is based on AGT:
+ 0-Eliminate adjective matches if PURE_NOUN is set and
+ out-of-scope adjectives that are not at the head of the list.
+ 1-Eliminate adj matches if the adjective is out-of-scope.
+ 2-eliminate SCENE and DOOR (D_INTERN)
+ 3-eliminate out-of-scope nouns that are not at the head of the list
+ 4-eliminate out-of-scope nouns
+ 5-eliminate numbers that don't have associated dictionary words
+ 6-eliminate adj only matches (i.e. noun-free) [only if PURE_ADJ]
+ 7-eliminate pronouns (D_PRO)
+ -eliminate ALL (D_ALL)
+ -eliminate numbers
+ (Never eliminated: FLAG,GLOBAL,PIX,SYN,NOUN)
+*/
+
+#define MAX_DSCHEME 3
+#define MAX_DLEV 2
+/* ambig_type=1 for actor, 2 for noun, 3 for object */
+/* We can assume that the earlier bits have already been disambiguated */
+/* Parse of current command in lactor, vnum, lnoun, prep, and lobj */
+/* pick_one is used to select the first noun in the case that all of
+ them get eliminated during visibility testing (if none of the nouns
+ are visible, we don't want to ask disambiguation questions) */
+
+static rbool disambig_check(parse_rec *rec, int dsch, int dlev,
+ int ambig_type, rbool pick_one) {
+ switch (dsch) {
+ case 0:
+ switch (dlev) { /* Syntactic checks: pre-scope */
+ case 0:
+ return (!PURE_ADJ || rec->info != D_ADJ);
+ case 1:
+ return (rec->info != D_INTERN); /* Elim SCENE and DOOR */
+ case 2:
+ return (rec->info != D_NUM || rec->obj != 0); /* Eliminate numbers w/o
+ corrosponding word matches */
+ default:
+ return 0;
+ }
+ case 1:
+ switch (dlev) { /* Scope checks */
+ case 0:
+ /* Just compute the scores, but don't eliminate anything yet */
+ /* if PURE_DISAMBIG, no intel dismbig */
+ if (PURE_DISAMBIG || rec->info == D_NUM)
+ rec->score = DISAMBIG_SUCC;
+ else rec->score = score_disambig(rec, ambig_type);
+ if (rec->score >= max_disambig_score)
+ max_disambig_score = rec->score;
+ return 1;
+ case 1:
+ return (rec->score == max_disambig_score);
+ case 2:
+ return (rec->info == D_NUM
+ || ((verbflag[vnum] & VERB_GLOBAL) != 0 && rec->score >= 500)
+ || (tnoun(rec->obj) && noun[rec->obj - first_noun].scope)
+ || (tcreat(rec->obj) && creature[rec->obj - first_creat].scope));
+ default:
+ return 1;
+ }
+ case 2:
+ switch (dlev) { /* Syntax, take 2 */
+ case 0: /* Reserved for alternative adjective checking */
+ return 1;
+ /* Kill internal matches */
+ case 1:
+ return (rec->info != D_PRO && rec->info != D_ALL &&
+ rec->info != D_INTERN && rec->info != D_NUM);
+ default:
+ return 0;
+ }
+ case 3:
+ return pick_one;
+ default:
+ return 0;
+ }
+}
+
+
+/* disambig_a_noun does disambiguation for a single AND-terminated block */
+/* *list* contains the list of possible objects, */
+/* *ofs* is our starting offset within the list (since with ANDs we */
+/* may not be starting at list[0]) */
+
+static parse_rec *disambig_a_noun(parse_rec *list, int ofs, int ambig_type)
+/* ambig_type=1 for actor, 2 for noun, 3 for object */
+/* We can assume that the earlier bits have already been disambiguated */
+/* Parse of current command in lactor, vnum, lnoun, prep, and lobj */
+{
+ int i, cnt; /* cnt keeps track of how many nouns we've let through */
+ int dsch; /* Dismabiguation scheme; we run through these in turn,
+ pushing dlev as high as possible for each scheme */
+ int dlev; /* Disambiguation level: how picky do we want to be?
+ We keep raising this until we get a unique choice
+ or until we reach MAX_DLEV and have to give up */
+ rbool one_in_scope; /* True if at least one noun is visible; if no nouns
+ are visible then we never ask for disambiguation
+ from the player (to avoid giving things away) but
+ just take the first one. */
+
+ cnt = 2; /* Arbitrary number > 1 */
+ one_in_scope = 0;
+ max_disambig_score = -1000; /* Nothing built in returns anything lower than 0,
+ but some game author might come up with a
+ clever application of negative scores */
+ for (dsch = 0; dsch <= MAX_DSCHEME; dsch++)
+ for (dlev = 0; dlev <= MAX_DLEV; dlev++) {
+ if (DEBUG_DISAMBIG)
+ rprintf("\nDISAMBIG%c%d:%d: ", (dsch == 1 ? '*' : ' '), dsch, dlev);
+ cnt = 0;
+ for (i = ofs; list[i].info != D_END && list[i].info != D_AND; i++)
+ if (disambig_check(&list[i], dsch, dlev, ambig_type,
+ one_in_scope || (i == ofs))
+ ) {
+ cnt++;
+ if (DEBUG_DISAMBIG)
+ rprintf("+%d ", list[i].obj);
+ } else {
+ if (DEBUG_DISAMBIG)
+ rprintf("-%d ", list[i].obj);
+ list[i].info |= D_MARK; /* Mark it for deletion */
+ }
+ if (cnt != 0) {
+ list = purge_list(list); /* Delete marked items */
+ if (cnt == 1) return list;
+ if (dsch == 1 && dlev == MAX_DLEV)
+ one_in_scope = 1;
+ } else {
+ clean_list(list); /* Remove marks; we're not purging */
+ break;
+ }
+ }
+ /* Check to make sure we don't have a list of multiple identical items */
+ for (i = ofs; list[i].info != D_END && list[i].info != D_AND; i++) {
+ if (!ident_objrec(&list[i], &list[ofs])) break;
+ list[i].info |= D_MARK;
+ }
+ if (list[i].info == D_END || list[i].info == D_AND) {
+ /* If all of the items are identical, just pick the first */
+ if (one_in_scope) writeln("(Picking one at random)");
+ list[0].info &= ~D_MARK;
+ list = purge_list(list);
+ } else clean_list(list);
+ if (DEBUG_DISAMBIG) rprintf("\n");
+ return list;
+}
+
+
+
+/* Note that this routine must be _restartable_, when new input comes in. */
+/* Truenoun is 0 or else the "correct" noun for where disambig first
+ got stuck. */
+/* Returns the offset at which it got stuck, or -1 if everything
+ went ok. Return -2 if we eliminate everything */
+/* *tn_ofs* contains the offset where truenoun is supposed to be used */
+
+#define list (*ilist) /* in disambig_phrase only */
+
+
+static int disambig_phrase(parse_rec **ilist, parse_rec *truenoun, int tn_ofs,
+ int ambig_type)
+/* Note that ilist is double dereferenced: this is so we can realloc it */
+/* ambig_type=1 for actor, 2 for noun, 3 for object */
+/* We can assume that the earlier bits have already been disambiguated */
+{
+ int ofs, i;
+ char *s;
+
+ ofs = 0;
+ if (list[0].info == D_END) return -1; /* No nouns, so no ambiguity */
+ if (list[0].info == D_ALL) ofs = 1; /* might have ALL EXCEPT construction */
+#ifdef OMEGA
+ return -1; /* No ambiguity over ALL, either */
+ /* (at least if it appears as the first element of the list) */
+#endif
+
+ while (list[ofs].info != D_END) { /* Go through each AND block */
+ if (ofs == tn_ofs) {
+ if (truenoun[0].info == D_ALL) /* Convert to ALL list */
+ list = convert_to_all(list, &ofs);
+ else {
+ list = add_disambig_info(list, ofs, truenoun);
+ if (list[ofs].info == D_END) { /* We have eliminated all matches */
+ gen_sysmsg(240, "In that case, I don't know what you mean.",
+ MSG_PARSE, "");
+ return -2;
+ }
+ }
+ }
+ list = disambig_a_noun(list, ofs, ambig_type);
+ assert(list[ofs].info != D_END && list[ofs].info != D_AND);
+ if (list[ofs + 1].info != D_END && list[ofs + 1].info != D_AND)
+ /* Disambiguation failed */
+ {
+ writestr("Do you mean");
+ for (i = ofs; list[i].info != D_END && list[i].info != D_AND; i++) {
+ if (list[i + 1].info == D_END || list[i + 1].info == D_AND)
+ if (i > ofs + 1) writestr(", or");
+ else writestr(" or");
+ else if (i > ofs) writestr(",");
+ writestr(" the ");
+ if (list[i].info != D_NUM || list[i].obj != 0)
+ s = (char *)objname(list[i].obj);
+ else {
+ s = (char *)rmalloc(30 * sizeof(char));
+ sprintf(s, "%ld", list[i].num);
+ }
+ writestr(s);
+ rfree(s);
+ }
+ writeln("?");
+ return ofs;
+ }
+ /* Skip forward to next AND */
+ while ((*ilist)[ofs].info != D_END && (*ilist)[ofs].info != D_AND)
+ ofs++;
+ if ((*ilist)[ofs].info == D_AND) ofs++;
+ }
+ return -1;
+}
+
+#undef list
+
+static int disambig_ofs; /* Offset where disambig failed */
+
+/* ambig_flag stores what we were disambiguating the last time this
+ routine was called: it tells us where we failed so that if the
+ player enters new disambiguation information, we can figure out where
+ it should go */
+
+static parse_rec *disambig(int ambig_set, parse_rec *list, parse_rec *truenoun)
+/* ambig_set = 1 for actor, 2 for noun, 3 for object */
+{
+ if (ambig_flag == ambig_set || ambig_flag == 0) { /* restart where we left off...*/
+ if (truenoun == NULL || truenoun[0].info == D_END) disambig_ofs = -1;
+ disambig_ofs = disambig_phrase(&list, truenoun, disambig_ofs, ambig_set);
+ if (disambig_ofs == -1) ambig_flag = 0; /* Success */
+ else if (disambig_ofs == -2) ambig_flag = -1; /* Error: elim all choices */
+ else ambig_flag = ambig_set;
+ }
+ return list;
+}
+
+
+
+
+/*-------------------------------------------------------------------*/
+/* Noun parsing routines */
+/*-------------------------------------------------------------------*/
+
+
+/* PARSE_A_NOUN(), parses a single noun, leaves ip pointing after it. */
+/* Just be greedy: grab as many of the input words as possible */
+/* Leave ip pointing _after_ last word we get. */
+/* Return list of all possible objects */
+/* Go to some difficullty to make sure "all the kings men" will
+ not be accidentally parsed as "all" + "the kings men" */
+/* (Yes, yes, I know -- you can't have an AGT object with a name as
+ complex as 'all the king's men'-- but you could try to simulate it using
+ synonyms) */
+/* May also want to use more intellegence along the adj--noun distinction */
+/* all_ok indicates whether ALL is acceptable */
+
+static parse_rec *parse_a_noun(void)
+/* Returns a list of parse_rec's containing the various possible
+ nouns. */
+{
+ parse_rec *nlist;
+ char *errptr;
+ int i, tmp, numval, num, oip;
+
+ nlist = new_list();
+ oip = ip; /* Save starting input pointer value */
+
+ if (input[ip] == -1) /* End of input */
+ return nlist;
+ if (input[ip] == 0) { /* i.e. tokeniser threw up its hands */
+ numval = strtol(in_text[ip], &errptr, 10); /* Is it a number? */
+ if (errptr == in_text[ip]) /* Nope. */
+ return nlist;
+ if (*errptr != 0) return nlist; /* May want to be less picky */
+ nlist = add_rec(nlist, 0, numval, D_NUM);
+ ip++;
+ return nlist;
+ }
+
+ /* Basic strategy: try to match nouns. If all matches are of length<=1,
+ then go looking for flag nouns, global nouns, ALL, DOOR, etc. */
+ num = 0;
+ objloop(i)
+ if ((tmp = noun_syn(input[ip], i)) != 0) {
+ numval = strtol(in_text[ip], &errptr, 10); /* Is it a number, too? */
+ if (*errptr != 0) numval = 0; /* Only accept perfectly formed numbers */
+ nlist = add_w_rec(nlist, i, numval,
+ (tmp == 1) ? D_ADJ : (tmp == 2 ? D_SYN : D_NOUN),
+ (tmp == 1) ? input[ip] : 0, /* Adjective */
+ (tmp == 1) ? 0 : input[ip]); /* Noun */
+ num++;
+ }
+
+ /* Now we need to try to match more words and reduce our list. */
+ /* Basically, we keep advancing ip until we get no matches at all. */
+ /* Note that nouns may only come at the end, so if we find one we know */
+ /* we are done. Synonyms and adjectives can come anywhere */
+ /* (If PURE_SYN is set, then we won't see any synonyms-- they */
+ /* get converted into nouns by noun_syn() */
+ /* *num* is used to keep track of how many matches we have so we know */
+ /* when to stop. */
+
+ /* compare against the next word in the queue */
+ while (num > 0 && input[ip] != -1 && ip < MAXINPUT) {
+ ip++;
+ if (input[ip] == -1) break;
+ num = 0;
+ for (i = 0; nlist[i].info != D_END; i++) /* Which nouns match the new word? */
+ if (nlist[i].info == D_NOUN) /* Nothing can come after a noun */
+ nlist[i].info |= D_MARK;
+ else if ((tmp = noun_syn(input[ip], nlist[i].obj)) == 0)
+ nlist[i].info |= D_MARK; /* Mark this noun to be eliminated */
+ else { /* Noun _does_ match */
+ num++; /* Count them up */
+ add_words_to_rec(&nlist[i], input[ip], tmp);
+ }
+ /* If we had any matches, kill the nouns that didn't match */
+ if (num != 0) {
+ nlist = purge_list(nlist);
+ for (i = 0; nlist[i].info != D_END; i++)
+ nlist[i].num = 0; /* Multi-word nouns can't be numbers */
+ } else /* num==0; we need to clear all of the marks */
+ clean_list(nlist);
+ }
+
+ /* So at this point num is zero-- that is we have reached the limit
+ of our matches. */
+ /* If ip==oip is 0 (meaning no matches so far) or ip==oip+1
+ (meaning we have a one-word match) then we need to check for
+ flag nouns, global nouns, ALL, DOOR, pronouns, etc.
+ and add them to the list if neccessary */
+
+ if (ip == oip || ip == oip + 1) {
+
+ /* First match the built in things... */
+ if ((input[oip] == ext_code[wdoor] && aver <= AGX00)
+ || input[oip] == ext_code[wscene])
+ nlist = add_rec(nlist, -input[oip], 0, D_INTERN);
+ else if (input[oip] == ext_code[wall] ||
+ input[oip] == ext_code[weverything])
+ nlist = add_rec(nlist, ALL_MARK, 0, D_ALL);
+ else if (input[oip] == ext_code[whe] || input[oip] == ext_code[whim])
+ nlist = add_rec(nlist, last_he, 0, D_PRO);
+ else if (input[oip] == ext_code[wshe] || input[oip] == ext_code[wher])
+ nlist = add_rec(nlist, last_she, 0, D_PRO);
+ else if (input[oip] == ext_code[wit])
+ nlist = add_rec(nlist, last_it, 0, D_PRO);
+ else if (input[oip] == ext_code[wthey] || input[oip] == ext_code[wthem])
+ nlist = add_rec(nlist, last_they, 0, D_PRO);
+ else for (i = 0; i < 10; i++) /* Match direction names */
+ if (input[oip] == syntbl[auxsyn[i]])
+ nlist = add_rec(nlist, -syntbl[auxsyn[i]], 0, D_INTERN);
+ else if (input[oip] == ext_code[weveryone] ||
+ input[oip] == ext_code[weverybody])
+ nlist = add_rec(nlist, -ext_code[weverybody], 0, D_INTERN);
+
+ /* Next look for number word matches */
+ numval = strtol(in_text[oip], &errptr, 10); /* Is it a number? */
+ if (*errptr == 0) /* Yes */
+ nlist = add_rec(nlist, -input[oip], numval, D_NUM);
+
+ /* Next handle the flag nouns and global nouns */
+ if (globalnoun != NULL)
+ for (i = 0; i < numglobal; i++)
+ if (input[oip] == globalnoun[i])
+ nlist = add_rec(nlist, -input[oip], 0, D_GLOBAL);
+ for (i = 0; i < MAX_FLAG_NOUN; i++)
+ if (flag_noun[i] != 0 && input[oip] == flag_noun[i]
+#if 0
+ && (room[loc].flag_noun_bits & (1L << i)) != 0
+#endif
+ )
+ nlist = add_rec(nlist, -input[oip], 0, D_FLAG);
+
+ /* Finally the PIX names */
+ for (i = 0; i < MAX_PIX; i++)
+ if (pix_name[i] != 0 && input[oip] == pix_name[i] &&
+ (room[loc].PIX_bits & (1L << i)) != 0)
+ nlist = add_rec(nlist, -input[oip], 0, D_PIX);
+
+ if (nlist[0].info != D_END) ip = oip + 1;
+ }
+ return nlist;
+}
+
+static parse_rec *parse_noun(int and_ok, int is_actor)
+/* handles multiple nouns */
+/* and_ok indicates wheter multiple objects are acceptable */
+/* Either_ok indicates whether EITHER is okay (only true when
+resolving disambiguation) */
+{
+ parse_rec *next, *lnoun_;
+ int saveinfo;
+ rbool all_except;
+
+ all_except = 0;
+ next = lnoun_ = parse_a_noun();
+ saveinfo = next[0].info;
+
+#if 0 /* Let the main parser sort this out */
+ if (!and_ok) return lnoun_; /* If no ANDs allowed, stop here. */
+#endif
+ /* We need to explicitly handle the actor case here because
+ the comma after an actor can be confused with the comma
+ used to separate multiple objects */
+ if (is_actor) return lnoun_; /* If no ANDs allowed, stop here. */
+
+ if (lnoun_[0].info == D_ALL && w_but(input[ip])) /* ALL EXCEPT ... */
+ all_except = 1; /* This will cause us to skip over EXCEPT and
+ start creating an AND list */
+
+ /* Now, itereate over <noun> AND <noun> AND ... */
+ while ((all_except || w_and(input[ip])) &&
+ saveinfo != D_END) { /* && saveinfo!=D_ALL */
+ ip++; /* Skip over AND or EXCEPT */
+ next = parse_a_noun();
+ saveinfo = next[0].info;
+ if (next[0].info != D_END) { /* We found a word */
+ if (!all_except) lnoun_ = add_rec(lnoun_, AND_MARK, 0, D_AND);
+ lnoun_ = concat_list(lnoun_, next);
+ } else ip--; /* We hit trouble: back up to the AND */
+ all_except = 0; /* Only skip EXCEPT once */
+ rfree(next);
+ }
+ return lnoun_;
+}
+
+
+parse_rec *parse_disambig_answer(void) {
+ parse_rec *temp;
+
+ if (input[ip + 1] == -1) {
+ if (input[ip] == ext_code[wall] || input[ip] == ext_code[weverything]
+ || input[ip] == ext_code[wboth]) {
+ temp = new_list();
+ ip++;
+ return add_rec(temp, ALL_MARK, 0, D_ALL);
+ }
+ if (input[ip] == ext_code[weither] || input[ip] == ext_code[w_any]) {
+ temp = new_list();
+ ip++;
+ return add_rec(temp, 0, 0, D_EITHER);
+ }
+ }
+ return parse_noun(0, 0);
+}
+
+
+
+/*-------------------------------------------------------------------*/
+/* Main parsing routines */
+/*-------------------------------------------------------------------*/
+
+
+static int parse_cmd(void)
+/* Parse entered text and execute it, one statement at a time */
+/* Needs to leave ip pointing at beginning of next statement */
+{
+ rbool new_actor; /* This is used for some error checking; it is set
+ if we just found an actor on this command
+ (as opposed to inheriting one from a previous
+ command in a multiple statement) */
+ parse_rec *tmp;
+ int tp;
+
+ /* First go looking for an actor. */
+ ap = ip;
+ new_actor = 0;
+ if (lactor == NULL) {
+ new_actor = 1;
+ lactor = parse_noun(0, 1);
+ /* Check that actor is a creature. */
+ if (lactor[0].info != D_END) {
+ lactor = fix_actor(lactor); /* eliminate non-creatures */
+ if (lactor[0].info == D_END) { /* Not a creature. */
+ /* print intelligent error message */
+ if (input[ip] == ext_code[wc]) /* ie there is a comma */
+ return parseerr(229, "Who is this '$word$' you are addressing?", ap);
+ else ip = ap; /* Otherwise, assume we shouldn't have parsed it as
+ an actor-- it may be a verb. */
+ }
+ }
+ if (lactor[0].info != D_END && input[ip] == ext_code[wc])
+ ip++; /* this skips over a comma after an actor. */
+ }
+ /* Now onwards... */
+ vp = ip;
+ vnum = id_verb(); /* May increment ip (ip will point at last word in verb) */
+ if (vnum == 0 && new_actor && lactor[0].info != D_END) {
+ /* maybe actor is messing us up. */
+ ip = ap; /* restart from beginning */
+ vnum = id_verb();
+ if (vnum == 0) /* if it's still bad, probably we really have an actor */
+ ip = vp;
+ else { /* no actor; really a verb */
+ lactor[0].obj = 0;
+ lactor[0].info = D_END;
+ vp = ap;
+ }
+ }
+
+TELLHack: /* This is used to restart the noun/prep/object scan
+ if we find a TELL <actor> TO <verb> ... command */
+
+ if (vnum == 0)
+ return parseerr(230, "I don't understand '$word$' as a verb.", ip);
+
+ /* Now we need to find noun, obj, and prep (if they all exist) */
+ /* standard grammer: verb noun prep obj */
+ prep = 0;
+ np = ++ip; /* ip now points just _after_ verb */
+ lnoun = parse_noun((verbflag[vnum] & VERB_MULTI) != 0, 0);
+ /* leaves ip pointing just after it.;
+ lnoun[0].info==D_END means no noun. */
+ if (prep == 0) { /* prep==0 unless we've met the special TURN ON|OFF case */
+ pp = ip;
+ prep = parse_prep(); /* Should be trivial */
+ op = ip;
+ lobj = parse_noun(prep == 0, 0); /* and_ok if no prep */
+ }
+
+ /* Check for TELL <actor> TO <verb> ... construction */
+ /* If found, convert to <actor>, <verb> ... construction */
+ if (lactor[0].info == D_END && lnoun[0].info != D_END &&
+ vnum == 31 && prep == ext_code[wto] && !multinoun(lnoun)) {
+ ip = op; /* Back up */
+ rfree(lactor);
+ rfree(lobj);
+ lactor = lnoun;
+ lnoun = NULL;
+ vp = ip; /* Replace TELL with new verb */
+ vnum = id_verb(); /* May increment ip (ip points att last word in verb) */
+ goto TELLHack; /* Go back up and reparse the sentence from
+ the new start point. */
+ }
+
+ /* Convert TURN <noun> ON to TURN ON <noun> */
+ if (vnum == 35 && (prep == ext_code[won] || prep == ext_code[woff])
+ && lobj[0].info == D_END) {
+ tmp = lobj;
+ lobj = lnoun;
+ lnoun = tmp;
+ tp = op;
+ np = op;
+ op = tp;
+ }
+
+
+ /* For pre-Magx versions of AGT,
+ convert <verb> <prep> <noun> ==> <verb> <noun> <prep> <noun> */
+ if (aver < AGX00 && lnoun[0].info == D_END && lobj[0].info != D_END) {
+ rfree(lnoun);
+ lnoun = copy_list(lobj);
+ np = op;
+ }
+
+ /* Next we check to convert SHOOT <noun> AT <object> to
+ SHOOT <object> WITH <noun> */
+ if (vnum == 49 && prep == ext_code[wat] && !multinoun(lnoun)) {
+ tmp = lobj;
+ lobj = lnoun;
+ lnoun = tmp;
+ tp = np;
+ np = op;
+ op = tp;
+ prep = ext_code[wwith];
+ }
+
+ /* Now to convert SHOW <*nothing*> to SHOW EXITS */
+ if (vnum == 40 && prep == 0 && lnoun[0].info == D_END && lobj[0].info == D_END)
+ vnum = 42; /* LISTEXITS */
+
+ /* Convert LOOK <something> into EXAMINE <something> */
+ if (smart_look && vnum == 19 && lnoun[0].info != D_END) vnum = 20;
+
+ /* need better error msgs */
+ if ((verbflag[vnum]&VERB_MULTI) == 0 && multinoun(lnoun)) {
+ /* Multiple objects when they are not allowed */
+ int msgnum;
+ if (vnum == 31) msgnum = 155; /* TALK */
+ else if (vnum == 34) msgnum = 160; /* ASK */
+ else msgnum = 231;
+ return parseerr(msgnum,
+ "The verb '$word$' doesn't take multiple objects.", vp);
+ } else if (multinoun(lobj))
+ return parseerr(232, "You can't use multiple indirect objects.", op);
+ else if (lnoun[0].info == D_END && !w_isterm(input[np]) && np != pp)
+ /* i.e. invalid noun */
+ return parseerr(233, "I don't understand the word '$word$' as a noun.", np);
+ else if (lnoun[0].obj == 0 && lnoun[0].info == D_PRO)
+ return parseerr(234, "I don't know to what '$word$' refers.", np);
+ else if (lobj[0].info == D_END && !w_isterm(input[op]))
+ /* i.e. invalid object */
+ return parseerr(235, "I don't understand the word '$word$' as a noun.", op);
+ else if (lobj[0].obj == 0 && lobj[0].info == D_PRO)
+ return parseerr(236, "I don't know to what '$word$' refers.", op);
+ else if (!w_isterm(input[ip]))
+ return parseerr(238, "Extra input found: \"$word$...\"", ip);
+
+ return 0;
+}
+
+
+static void v_undo(void) {
+ if (undo_state == NULL) {
+ writeln("There is insufficiant memory to support UNDO");
+ ip = -1;
+ return;
+ }
+ if (can_undo == 0) {
+ if (newlife_flag)
+ writeln("You can't UNDO on the first turn.");
+ else writeln("You can only UNDO one turn.");
+ ip = -1;
+ return;
+ }
+ writeln("");
+ writeln("UNDOing a turn...");
+ can_undo = 0;
+ putstate(undo_state);
+ ip = 1;
+ set_statline();
+ return;
+}
+
+rbool parse(void)
+/* Wrapper around parse_cmd to handle disambiguation, etc */
+/* It returns 1 if everything is okay; 0 if there is ambiguity */
+{
+ parse_rec *currnoun;
+ int fixword;
+ int start_ip;
+
+ currnoun = NULL;
+ start_ip = ip;
+ /* First, we need to see if someone has issued an OOPS command.
+ OOPS commands are always assumed to stand alone. (i.e. no
+ THEN nonsense). OOPS commands are always of the form
+ OOPS <word> */
+ if (ip == 0 && input[0] == ext_code[woops] && input[1] > 0 &&
+ input[2] == -1 && ep > -1) {
+ fixword = input[ip + 1];
+ restore_input();
+ input[ep] = fixword;
+ ambig_flag = 0;
+ }
+ ep = -1;
+
+
+ /* Next, we need to determine if someone is trying to do
+ disambiguation. This is only the case if
+ i)ambig_flag is set
+ ii)ip=0 (no multiple command nonsense)
+ iii)there is only one noun on the line. */
+ if (ip != 0) ambig_flag = 0;
+ if (ambig_flag) {
+ currnoun = parse_disambig_answer();
+ if (input[ip] == -1 && currnoun[0].info != D_END) {
+ restore_input(); /* Yep, we're disambiguaing. */
+ ip = parse_ip;
+ } else { /* nope; it's a new command */
+ ip = 0;
+ ambig_flag = 0;
+ rfree(currnoun);
+ freeall();
+ currnoun = NULL;
+ }
+ }
+
+ /* Next we go looking for UNDO; again, this must be the first
+ thing on an empty line. */
+ if (ip == 0 && input[0] == ext_code[wundo] && input[1] == -1) {
+ v_undo();
+ return 1;
+ }
+
+ save_input();
+
+ /* If starting a new line, clear out old the old actor */
+ if (ip == 0) {
+ actor_in_scope = 0; /* Clear this */
+ rfree(lactor); /* This resets lactor to NULL */
+ }
+
+ if (!ambig_flag)
+ if (parse_cmd() == -1)
+ return 1; /* error condition */
+
+ parse_ip = ip;
+
+ if (debug_parse)
+ parse_out(lactor, vnum, lnoun, prep, lobj);
+
+ /*Disambiguation routines; do it here instead of earlier to get
+ error messages in the right place (it's silly and annoying to ask the
+ player for disambiguation and then die on a parse error after they've
+ provided it) */
+ compute_scope(); /* The disambig routines use this information */
+ lactor = disambig(1, lactor, currnoun);
+ lnoun = disambig(2, lnoun, currnoun);
+ lobj = disambig(3, lobj, currnoun);
+ if (ambig_flag > 0) return 0; /* We need to get disambig info */
+ if (ambig_flag == -1) {
+ ambig_flag = 0;
+ return 1;
+ }
+ /* We got rid of too much */
+ rfree(currnoun);
+
+ /* Next, expand ALL if neccessary */
+ if (!PURE_ALL && lnoun[0].info == D_ALL) {
+ lnoun = expand_all(lnoun);
+ if (lnoun[0].info == D_END) { /* ALL expands to nothing */
+ int msgnum;
+ if (vnum >= 15 && vnum <= 24)
+ msgnum = all_err_msg[vnum - 15];
+ else
+ msgnum = 239;
+ parseerr(msgnum, "I don't know what you are referring to.", np);
+ return 1;
+ }
+ }
+
+ /* Now that we know that we have an executable command,
+ we save the undo state before executing if this is the first command
+ in a sequence. (That is, UNDO undoes whole lines of commands,
+ not just individual commands) */
+ if (start_ip == 0 && undo_state != NULL) {
+ undo_state = getstate(undo_state);
+ can_undo = 1;
+ }
+
+ /* Now to actually execute the command that has been parsed. */
+ /* Remember: disambiguation has been done by this time. */
+
+ exec(&lactor[0], vnum, lnoun, prep, &lobj[0]);
+ rfree(lobj);
+
+ /* exec is responsible for freeing or whatever lnoun (this is for AGAIN) */
+
+ /* Now we clear lnoun and lobj; lactor is handled elsewhere since
+ we might have FRED, GET ROCK THEN GO NORTH */
+ lnoun = lobj = NULL;
+
+ /* Finally check for THENs */
+
+ if (ip != -1 && w_and(input[ip]) && input[ip + 1] == ext_code[wthen])
+ ip++; /* AND THEN construction */
+ if (ip != -1 && input[ip] != -1) ip++;
+ return 1;
+}
+
+
+
+
+void menu_cmd(void) {
+ int i, j;
+ int choice;
+ char *curr_cmd, *tmp1, *tmp2; /* String of current command */
+ int objcnt; /* Number of objects taken by the current verb */
+ int verbword; /* Verb word */
+ parse_rec actrec;
+
+ parse_rec mobj;
+ int vnum_; /* Verb number */
+ word prep_;
+
+ menuentry *nounmenu;
+ int *nounval; /* Object id's for the menu entries */
+ int nm_size, nm_width; /* Size and width of noun menu */
+
+
+ nounval = NULL;
+ nounmenu = NULL;
+ /* Get verb+prep */
+ choice = agt_menu("", vm_size, vm_width, verbmenu);
+ if (choice == -1 || doing_restore) return;
+
+ verbword = verbinfo[choice].verb;
+ prep_ = verbinfo[choice].prep;
+ objcnt = verbinfo[choice].objnum;
+
+ /* Now identify the verb */
+ ip = 0;
+ input[0] = verbword;
+ input[1] = input[2] = -1;
+ if (objcnt <= 1 && prep_ != 0) input[1] = prep_;
+ vnum_ = id_verb();
+
+ lnoun = (parse_rec *)rmalloc(sizeof(parse_rec) * 2);
+ lnoun[0].obj = 0;
+ lnoun[0].num = 0;
+ lnoun[0].info = D_END;
+
+ nm_size = nm_width = 0;
+
+ if (objcnt >= 1) {
+ /* Construct noun list */
+ nounval = get_nouns();
+ for (nm_size = 0; nounval[nm_size] != 0; nm_size++);
+ nounmenu = (menuentry *)rmalloc(nm_size * sizeof(menuentry));
+ nm_width = 0;
+ for (i = 0; i < nm_size; i++) {
+ tmp1 = objname(nounval[i]);
+ strncpy(nounmenu[i], tmp1, MENU_WIDTH);
+ j = strlen(tmp1);
+ if (j > nm_width) nm_width = j;
+ }
+ if (nm_width > MENU_WIDTH) nm_width = MENU_WIDTH;
+
+ if (objcnt >= 2 || prep_ == 0)
+ curr_cmd = rstrdup(dict[verbword]);
+ else
+ curr_cmd = concdup(dict[verbword], dict[prep_]);
+
+ choice = agt_menu(curr_cmd, nm_size, nm_width, nounmenu);
+ rfree(curr_cmd);
+ if (choice == -1 || doing_restore) {
+ rfree(nounmenu);
+ rfree(nounval);
+ rfree(lnoun);
+ return;
+ }
+
+ if (objcnt == 1 && prep_ != 0) { /* VERB PREP OBJ construction */
+ mobj.obj = nounval[choice];
+ mobj.num = 0;
+ mobj.info = D_NOUN;
+ } else { /* Normal VERB OBJ construction */
+ lnoun[0].obj = nounval[choice];
+ lnoun[0].num = 0;
+ lnoun[0].info = D_NOUN;
+ lnoun[1].obj = 0;
+ lnoun[1].num = 0;
+ lnoun[1].info = D_END;
+ }
+ }
+
+ if (objcnt >= 2) {
+ tmp1 = objname(lnoun[0].obj); /* Build up current command line */
+ tmp2 = concdup(dict[verbword], tmp1); /* VERB NOUN */
+ rfree(tmp1);
+ curr_cmd = concdup(tmp2, dict[prep_]); /* VERB NOUN PREP */
+ rfree(tmp2);
+
+ choice = agt_menu(curr_cmd, nm_size, nm_width, nounmenu);
+ rfree(curr_cmd);
+ if (choice == -1 || doing_restore) {
+ rfree(lnoun);
+ rfree(nounmenu);
+ rfree(nounval);
+ return;
+ }
+
+ mobj.obj = nounval[choice];
+ mobj.num = 0;
+ mobj.info = D_NOUN;
+ }
+
+ rfree(nounmenu);
+ rfree(nounval);
+
+ if (vnum_ == OLD_VERB + 3) { /* UNDO */
+ v_undo();
+ return;
+ }
+
+ if (undo_state != NULL) {
+ undo_state = getstate(undo_state);
+ can_undo = 1;
+ }
+
+ /* Now to actually execute the command that has been selected. */
+ tmpobj(&actrec);
+ actrec.obj = 0;
+ exec(&actrec, vnum_, lnoun, prep_, &mobj);
+ lnoun = NULL; /* exec() is responsible for freeing lnoun */
+}
+
+
+/* Grammer structures:
+ sverb, dverb (n,s,e,w,...,q,l,....)
+ overb noun (close,examine,read,eat,drink,pull,light,ext)
+ averb noun|ALL (drop,get,wear,remove)
+ TURN noun ON|OFF
+ TURN ON|OFF noun
+ a-verb noun ABOUT obj (tell, ask)
+ pverb noun [prep object] (put, throw)
+ w-verb noun [WITH object] (attack, open, lock, unlock, push, shoot)
+ SHOOT noun [AT object]
+ dummy noun [prep obj]
+( pverb obj noun ==> pverb noun TO obj e.g. give dog the bone)
+*/
+
+} // End of namespace AGT
+} // End of namespace Glk