Files
scummvm-cursorfix/engines/glk/tads/tads2/vocabulary_parser.cpp
2026-02-02 04:50:13 +01:00

8160 lines
210 KiB
C++

/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/tads/tads2/error.h"
#include "glk/tads/tads2/list.h"
#include "glk/tads/tads2/memory_cache_heap.h"
#include "glk/tads/tads2/os.h"
#include "glk/tads/tads2/run.h"
#include "glk/tads/tads2/vocabulary.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
static const char *type_names[] =
{
"article", "adj", "noun", "prep", "verb", "special", "plural",
"unknown"
};
/* array of flag values for words by part of speech */
static int voctype[] =
{ 0, 0, VOCT_VERB, VOCT_NOUN, VOCT_ADJ, VOCT_PREP, VOCT_ARTICLE };
/* ------------------------------------------------------------------------ */
/*
* Allocate and push a list, given the number of bytes needed for the
* elements of the list.
*/
uchar *voc_push_list_siz(voccxdef *ctx, uint lstsiz)
{
runcxdef *rcx = ctx->voccxrun;
runsdef val;
uchar *lstp;
/* add in the size needed for the list's length prefix */
lstsiz += 2;
/* allocate space in the heap */
runhres(rcx, lstsiz, 0);
/* set up a stack value to push */
val.runstyp = DAT_LIST;
val.runsv.runsvstr = lstp = ctx->voccxrun->runcxhp;
/* set up the list's length prefix */
oswp2(lstp, lstsiz);
lstp += 2;
/* commit the space in the heap */
rcx->runcxhp += lstsiz;
/* push the list value (repush, since we can use the original copy) */
runrepush(rcx, &val);
/* return the list element pointer */
return lstp;
}
/*
* Allocate and push a list. Returns a pointer to the space for the
* list's first element in the heap.
*/
static uchar *voc_push_list(voccxdef *ctx, int ele_count, int ele_size)
{
uint lstsiz;
/*
* Figure the list size - we need space for the given number of
* elements of the given size; in addition, each element requires
* one byte of overhead for its type prefix.
*/
lstsiz = (uint)(ele_count * (1 + ele_size));
/* allocate and return the list */
return voc_push_list_siz(ctx, lstsiz);
}
/*
* Push a list of numbers
*/
static void voc_push_numlist(voccxdef *ctx, uint numlist[], int cnt)
{
int i;
uchar *lstp;
/* allocate space for the list of numbers */
lstp = voc_push_list(ctx, cnt, 4);
/* enter the list elements */
for (i = 0 ; i < cnt ; ++i)
{
/* add the type prefix */
*lstp++ = DAT_NUMBER;
/* add the value */
oswp4(lstp, numlist[i]);
lstp += 4;
}
}
/*
* Push a list of object ID's obtained from a vocoldef array
*/
void voc_push_vocoldef_list(voccxdef *ctx, vocoldef *objlist, int cnt)
{
int i;
uchar *lstp;
uint lstsiz;
/*
* count the size - we need 3 bytes per object (1 for type plus 2
* for the value), and 1 byte per nil
*/
for (lstsiz = 0, i = 0 ; i < cnt ; ++i)
lstsiz += (objlist[i].vocolobj == MCMONINV ? 1 : 3);
/* allocate space for the list */
lstp = voc_push_list_siz(ctx, lstsiz);
/* enter the list elements */
for (i = 0 ; i < cnt ; ++i)
{
if (objlist[i].vocolobj == MCMONINV)
{
/* store the nil */
*lstp++ = DAT_NIL;
}
else
{
/* add the type prefix */
*lstp++ = DAT_OBJECT;
/* add the value */
oswp2(lstp, objlist[i].vocolobj);
lstp += 2;
}
}
}
/*
* Push a list of object ID's
*/
void voc_push_objlist(voccxdef *ctx, objnum objlist[], int cnt)
{
int i;
uchar *lstp;
uint lstsiz;
/*
* count the size - we need 3 bytes per object (1 for type plus 2
* for the value), and 1 byte per nil
*/
for (lstsiz = 0, i = 0 ; i < cnt ; ++i)
lstsiz += (objlist[i] == MCMONINV ? 1 : 3);
/* allocate space for the list */
lstp = voc_push_list_siz(ctx, lstsiz);
/* enter the list elements */
for (i = 0 ; i < cnt ; ++i)
{
if (objlist[i] == MCMONINV)
{
/* store the nil */
*lstp++ = DAT_NIL;
}
else
{
/* add the type prefix */
*lstp++ = DAT_OBJECT;
/* add the value */
oswp2(lstp, objlist[i]);
lstp += 2;
}
}
}
/*
* Push a list of strings, where the strings are stored in memory, one
* after the other, each string separated from the next with a null
* byte. The list is bounded by firstwrd and lastwrd, inclusive of
* both.
*/
static void voc_push_strlist(voccxdef *ctx, const char *firstwrd, const char *lastwrd)
{
size_t curlen;
const char *p;
uint lstsiz;
uchar *lstp;
/*
* Determine how much space we need for the word list. For each
* entry, we need one byte for the type prefix, two bytes for the
* length prefix, and the bytes of the string itself.
*/
for (lstsiz = 0, p = firstwrd ; p != nullptr && p <= lastwrd ; p += curlen + 1)
{
curlen = strlen(p);
lstsiz += curlen + (1+2);
}
/* allocate space for the word list */
lstp = voc_push_list_siz(ctx, lstsiz);
/* enter the list elements */
for (p = firstwrd ; p != nullptr && p <= lastwrd ; p += curlen + 1)
{
/* add the type prefix */
*lstp++ = DAT_SSTRING;
/* add the length prefix for this string */
curlen = strlen(p);
oswp2(lstp, curlen + 2);
lstp += 2;
/* add this string */
memcpy(lstp, p, curlen);
lstp += curlen;
}
}
/*
* Push a list of strings, taking the strings from an array.
*/
static void voc_push_strlist_arr(voccxdef *ctx, char *wordlist[], int cnt)
{
int i;
char **p;
uint lstsiz;
uchar *lstp;
/*
* Add up the lengths of the strings in the array. For each
* element, we need space for the string's bytes, plus two bytes for
* the length prefix, plus one byte for the type prefix.
*/
for (lstsiz = 0, p = wordlist, i = 0 ; i < cnt ; ++i, ++p)
lstsiz += strlen(*p) + 3;
/* allocate space for the list */
lstp = voc_push_list_siz(ctx, lstsiz);
/* enter the list elements */
for (p = wordlist, i = 0 ; i < cnt ; ++i, ++p)
{
size_t curlen;
/* add the type prefix */
*lstp++ = DAT_SSTRING;
/* add the length prefix for this string */
curlen = strlen(*p);
oswp2(lstp, curlen + 2);
lstp += 2;
/* add this string */
memcpy(lstp, *p, curlen);
lstp += curlen;
}
}
/*
* Push a list of strings, taking the strings from an array that was
* prepared by the parser tokenizer. This is almost the same as pushing
* a regular string array, with the difference that we must recognize
* the special format that the tokenizer uses to store string tokens.
*/
static void voc_push_toklist(voccxdef *ctx, char *wordlist[], int cnt)
{
int i;
char **p;
uint lstsiz;
uchar *lstp;
size_t cur_len;
/*
* Add up the lengths of the strings in the array. For each
* element, we need space for the string's bytes, plus two bytes for
* the length prefix, plus one byte for the type prefix.
*/
for (lstsiz = 0, p = wordlist, i = 0 ; i < cnt ; ++i, ++p)
{
/*
* get the length of the current token - check what kind of
* token we have, since we must sense the length of different
* token types in different ways
*/
if (**p == '"')
{
/*
* It's a string token - the string follows with a two-byte
* length prefix; add two bytes for the open and close quote
* characters that we'll add to the output string. Note
* that we must deduct two bytes from the prefix length,
* because the prefix includes the size of the prefix
* itself, which we're not copying and will account for
* separately in the result string.
*/
cur_len = osrp2(*p + 1) - 2 + 2;
}
else
{
/* for anything else, it's just a null-terminated string */
cur_len = strlen(*p);
}
/* add the current length to the total so far */
lstsiz += cur_len + 3;
}
/* allocate space for the list */
lstp = voc_push_list_siz(ctx, lstsiz);
/* enter the list elements */
for (p = wordlist, i = 0 ; i < cnt ; ++i, ++p)
{
char *cur_ptr;
size_t copy_len;
/* add the type prefix */
*lstp++ = DAT_SSTRING;
/* get the information for the string based on the type */
if (**p == '"')
{
/*
* it's a string - use the length prefix (deducting two
* bytes for the prefix itself, which we're not copying)
*/
copy_len = osrp2(*p + 1) - 2;
/* add space in the result for the open and close quotes */
cur_len = copy_len + 2;
/* the string itself follows the length prefix and '"' flag */
cur_ptr = *p + 3;
}
else
{
/* for anything else, it's just a null-terminated string */
cur_len = copy_len = strlen(*p);
cur_ptr = *p;
}
/* write the length prefix for this string */
oswp2(lstp, cur_len + 2);
lstp += 2;
/* add the open quote if this is a quoted string */
if (**p == '"')
*lstp++ = '"';
/* add this string */
memcpy(lstp, cur_ptr, copy_len);
lstp += copy_len;
/* add the close quote if it's a quoted string */
if (**p == '"')
*lstp++ = '"';
}
}
/* ------------------------------------------------------------------------ */
/*
* Read a command from the keyboard, doing all necessary output flushing
* and prompting.
*/
int vocread(voccxdef *ctx, objnum actor, objnum verb,
char *buf, int bufl, int type)
{
const char *prompt;
int ret;
/* presume we'll return success */
ret = VOCREAD_OK;
/* make sure output capturing is off */
tiocapture(ctx->voccxtio, (mcmcxdef *)nullptr, FALSE);
tioclrcapture(ctx->voccxtio);
/*
* Clear out the command buffer. This is important for the
* timeout-based command reader, since it will take what's in the
* buffer as the initial contents of the command line; this lets us
* remember any partial line that the player entered before a
* timeout interrupted their typing and redisplay the original
* partial line on the next command line. Initially, there's no
* partial line, so clear it out.
*/
buf[0] = '\0';
/* show the game-defined prompt, if appropriate */
if (ctx->voccxprom != MCMONINV)
{
runpnum(ctx->voccxrun, (long)type);
runfn(ctx->voccxrun, ctx->voccxprom, 1);
tioflushn(ctx->voccxtio, 0);
prompt = "";
}
else
{
/* there's no game-defined prompt - use our default */
tioblank(tio);
prompt = ">";
}
/* get a line of input */
if (tiogets(ctx->voccxtio, prompt, buf, bufl))
errsig(ctx->voccxerr, ERR_RUNQUIT);
/* abort immediately if we see the special panic command */
if (!strcmp(buf, "$$ABEND"))
{
/* make sure any script file is closed */
qasclose();
/* use the OS-level termination */
os_term(OSEXFAIL);
/* if that returned, signal a quit */
errsig(ctx->voccxerr, ERR_RUNQUIT);
}
/* call the post-prompt function if defined */
if (ctx->voccxpostprom != MCMONINV)
{
runpnum(ctx->voccxrun, (long)type);
runfn(ctx->voccxrun, ctx->voccxpostprom, 1);
}
/*
* If this isn't a type "0" input, and preparseExt() is defined, call
* it. Don't call preparseExt() for type "0" inputs, since these will
* be handled via the conventional preparse().
*/
if (ctx->voccxpre2 != MCMONINV && type != 0)
{
uchar *s;
size_t len;
/* push the arguments - actor, verb, str, type */
runpnum(ctx->voccxrun, (long)type);
runpstr(ctx->voccxrun, buf, (int)strlen(buf), 0);
runpobj(ctx->voccxrun, verb);
runpobj(ctx->voccxrun, actor);
/* call preparseExt() */
runfn(ctx->voccxrun, ctx->voccxpre2, 4);
/* check the result */
switch(runtostyp(ctx->voccxrun))
{
case DAT_SSTRING:
/*
* They returned a string. Replace the input buffer we read
* with the new string. Pop the new string and scan its length
* prefix.
*/
s = runpopstr(ctx->voccxrun);
len = osrp2(s) - 2;
s += 2;
/*
* limit the size we copy to our buffer length (leaving space
* for null termination)
*/
if (len > (size_t)bufl - 1)
len = bufl - 1;
/* copy the new command string into our buffer */
memcpy(buf, s, len);
/* null-terminate the result */
buf[len] = '\0';
/* proceed as normal with the new string */
break;
case DAT_TRUE:
/*
* they simply want to keep the current string as it is -
* proceed as normal
*/
break;
case DAT_NIL:
/*
* They want to skip the special interpretation of the input
* and proceed directly to treating the input as a brand new
* command. The caller will have to take care of the details;
* we need only indicate this to the caller through our "redo"
* result code.
*/
ret = VOCREAD_REDO;
break;
}
}
/* return our result */
return ret;
}
/*
* Compare a pair of words, truncated to six characters or the
* length of the first word, whichever is longer. (The first word is
* the user's entry, the second is the reference word in the dictionary.)
* Returns TRUE if the words match, FALSE otherwise.
*/
static int voceq(const uchar *s1, uint l1, uchar *s2, uint l2)
{
uint i;
if (l1 == 0 && l2 == 0) return(TRUE); /* both NULL - a match */
if (l1 == 0 || l2 == 0) return(FALSE); /* one NULL only - not a match */
if (l1 >= 6 && l2 >= l1) l2 = l1;
if (l1 != l2) return(FALSE); /* ==> not equal */
for (i = 0 ; i < l1 ; i++)
if (*s1++ != *s2++) return(FALSE);
return(TRUE); /* strings match */
}
/* find the next word in a search */
vocwdef *vocfnw(voccxdef *voccx, vocseadef *search_ctx)
{
vocdef *v, *vf;
vocwdef *vw, *vwf = nullptr;
vocdef *c = search_ctx->v;
int first;
/* continue with current word's vocwdef list if anything is left */
first = TRUE;
vw = vocwget(voccx, search_ctx->vw->vocwnxt);
/* keep going until we run out of hash chain entries or find a match */
for (v = c, vf = nullptr ; v != nullptr && vf == nullptr ; v = v->vocnxt, first = FALSE)
{
/* if this word matches, look at the objects in its list */
if (first
|| (voceq(search_ctx->wrd1, search_ctx->len1,
v->voctxt, v->voclen)
&& voceq(search_ctx->wrd2, search_ctx->len2,
v->voctxt + v->voclen, v->vocln2)))
{
/*
* on the first time through, vw has already been set up
* with the next vocwdef in the current list; on subsequent
* times through the loop, start at the head of the current
* word's list
*/
if (!first)
vw = vocwget(voccx, v->vocwlst);
/* search the list from vw forward */
for ( ; vw ; vw = vocwget(voccx, vw->vocwnxt))
{
if (search_ctx->vw->vocwtyp == vw->vocwtyp
&& !(vw->vocwflg & VOCFCLASS)
&& !(vw->vocwflg & VOCFDEL))
{
/*
* remember the first vocdef that we found, and
* remember this, the first matching vocwdef, then
* stop scanning
*/
vf = v;
vwf = vw;
break;
}
}
}
}
/* return the first vocwdef in this word's list */
search_ctx->v = vf;
search_ctx->vw = (vf ? vwf : nullptr);
return(search_ctx->vw);
}
/* find the first vocdef matching a set of words */
vocwdef *vocffw(voccxdef *ctx, const char *wrd, int len, const char *wrd2, int len2,
int p, vocseadef *search_ctx)
{
uint hshval;
vocdef *v, *vf;
vocwdef *vw, *vwf = nullptr;
/* get the word's hash value */
hshval = vochsh((const uchar *)wrd, len);
/* scan the hash list until we run out of entries, or find a match */
for (v = ctx->voccxhsh[hshval], vf = nullptr ; v != nullptr && vf == nullptr ;
v = v->vocnxt)
{
/* if this word matches, look at the objects in its list */
if (voceq((const uchar *)wrd, len, v->voctxt, v->voclen)
&& voceq((const uchar *)wrd2, len2, v->voctxt + v->voclen, v->vocln2))
{
/* look for a suitable object in the vocwdef list */
for (vw = vocwget(ctx, v->vocwlst) ; vw ;
vw = vocwget(ctx, vw->vocwnxt))
{
if (vw->vocwtyp == p && !(vw->vocwflg & VOCFCLASS)
&& !(vw->vocwflg & VOCFDEL))
{
/*
* remember the first vocdef that we found, and
* remember this, the first matching vocwdef; then
* stop scanning, since we have a match
*/
vf = v;
vwf = vw;
break;
}
}
}
}
/* set up the caller-provided search structure for next time */
vw = (vf != nullptr ? vwf : nullptr);
if (search_ctx)
{
/* save the search position */
search_ctx->v = vf;
search_ctx->vw = vw;
/* save the search criteria */
search_ctx->wrd1 = (const uchar *)wrd;
search_ctx->len1 = len;
search_ctx->wrd2 = (const uchar *)wrd2;
search_ctx->len2 = len2;
}
/* return the match */
return vw;
}
/* ------------------------------------------------------------------------ */
/*
* vocerr_va information structure. This is initialized in the call to
* vocerr_va_prep(), and must then be passed to vocerr_va().
*/
struct vocerr_va_info
{
/* parseError/parseErrorParam result */
char user_msg[400];
/* the sprintf-style format string to display */
const char *fmt;
/*
* Pointer to the output buffer to use to format the string 'fmt' with
* its arguments, using vsprintf. The prep function will set this up
* to point to user_msg[].
*/
char *outp;
/* size of the output buffer, in bytes */
size_t outsiz;
};
/*
* General parser error formatter - preparation. This must be called to
* initialize the context before the message can be displayed with
* vocerr_va().
*/
static void vocerr_va_prep(voccxdef *ctx, struct vocerr_va_info *info,
int err, const char *f, va_list argptr)
{
/*
* presume that we'll use the given format string, instead of one
* provided by the program
*/
info->fmt = f;
/* use the output buffer from the info structure */
info->outp = info->user_msg;
info->outsiz = sizeof(info->user_msg);
/*
* if the user has a parseError or parseErrorParam function, see if it
* provides a msg
*/
if (ctx->voccxper != MCMONINV || ctx->voccxperp != MCMONINV)
{
runcxdef *rcx = ctx->voccxrun;
dattyp typ;
size_t len;
int argc;
/* start off with the two arguments that are always present */
argc = 2;
/*
* if we're calling parseErrorParam, and we have additional
* arguments, push them as well
*/
if (ctx->voccxperp != MCMONINV)
{
enum typ_t
{
ARGBUF_STR, ARGBUF_INT, ARGBUF_CHAR
};
struct argbuf_t
{
enum typ_t typ;
union
{
char *strval;
int intval;
char charval;
} val;
};
struct argbuf_t args[5];
struct argbuf_t *argp;
const char *p;
/*
* Retrieve the arguments by examining the format string. We
* must buffer up the arguments before pushing them, because
* we need to push them in reverse order (last to first); so,
* we must scan all arguments before we push the first one.
*/
for (p = f, argp = args ; *p != '\0' ; ++p)
{
/* check if this is a parameter */
if (*p == '%')
{
/* find out what type it is */
switch(*++p)
{
case 's':
/* string - save the char pointer */
argp->val.strval = va_arg(argptr, char *);
argp->typ = ARGBUF_STR;
/* consume an argument slot */
++argp;
break;
case 'd':
/* integer - save the integer */
argp->val.intval = va_arg(argptr, int);
argp->typ = ARGBUF_INT;
/* consume an argument slot */
++argp;
break;
case 'c':
/* character */
argp->val.charval = (char)va_arg(argptr, int);
argp->typ = ARGBUF_CHAR;
/* consume an argument slot */
++argp;
break;
default:
/*
* ignore other types (there shouldn't be any
* other types anyway)
*/
break;
}
}
}
/*
* Push the arguments - keep looping until we get back to the
* first argument slot
*/
while (argp != args)
{
/* move to the next argument, working backwards */
--argp;
/* push this argument */
switch(argp->typ)
{
case ARGBUF_STR:
/* push the string value */
runpstr(rcx, argp->val.strval,
(int)strlen(argp->val.strval), 0);
break;
case ARGBUF_INT:
/* push the number value */
runpnum(rcx, argp->val.intval);
break;
case ARGBUF_CHAR:
/* push the character as a one-character string */
runpstr(rcx, &argp->val.charval, 1, 0);
break;
}
/* count the argument */
++argc;
}
}
/* push standard arguments: error code and default message */
runpstr(rcx, f, (int)strlen(f), 0); /* 2nd arg: default msg */
runpnum(rcx, (long)err); /* 1st arg: error number */
/* invoke parseErrorParam if it's defined, otherwise parseError */
runfn(rcx, (objnum)(ctx->voccxperp == MCMONINV
? ctx->voccxper : ctx->voccxperp), argc);
/* see what the function returned */
typ = runtostyp(rcx);
if (typ == DAT_SSTRING)
{
char *p;
/*
* they returned a string - use it as the error message
* instead of the default message
*/
p = (char *)runpopstr(rcx);
len = osrp2(p) - 2;
p += 2;
if (len > sizeof(info->user_msg) - 1)
len = sizeof(info->user_msg) - 1;
memcpy(info->user_msg, p, len);
info->user_msg[len] = '\0';
/* use the returned string as the message to display */
info->fmt = info->user_msg;
/* use the remainder of the buffer for the final formatting */
info->outp = info->user_msg + len + 1;
info->outsiz = sizeof(info->user_msg) - len - 1;
}
else
{
/* ignore other return values */
rundisc(rcx);
}
}
}
/*
* General parser error formatter.
*
* Before calling this routine, callers MUST invoke vocerr_va_prep() to
* prepare the information structure. Because both this routine and the
* prep routine need to look at the varargs list ('argptr'), the caller
* must call va_start/va_end around the prep call, and then AGAIN on this
* call. va_start/va_end must be used twice to ensure that the argptr is
* property re-initialized for the call to this routine.
*/
static void vocerr_va(voccxdef *ctx, struct vocerr_va_info *info,
int err, const char *f, va_list argptr)
{
char *buf;
/* turn on output */
(void)tioshow(ctx->voccxtio);
/* build the string to display */
if (os_vasprintf(&buf, info->fmt, argptr) >= 0)
{
/* display it */
tioputs(ctx->voccxtio, buf);
/* free the buffer */
osfree(buf);
}
}
/* ------------------------------------------------------------------------ */
/*
* display a parser informational message
*/
void vocerr_info(voccxdef *ctx, int err, const char *f, ...)
{
va_list argptr;
struct vocerr_va_info info;
/* prepare to format the message */
va_start(argptr, f);
vocerr_va_prep(ctx, &info, err, f, argptr);
va_end(argptr);
/* call the general vocerr formatter */
va_start(argptr, f);
vocerr_va(ctx, &info, err, f, argptr);
va_end(argptr);
}
/*
* display a parser error
*/
void vocerr(voccxdef *ctx, int err, const char *f, ...)
{
va_list argptr;
struct vocerr_va_info info;
/*
* If the unknown word flag is set, suppress this error, because
* we're going to be trying the whole parsing from the beginning
* again anyway.
*/
if (ctx->voccxunknown > 0)
return;
/* prepare to format the message */
va_start(argptr, f);
vocerr_va_prep(ctx, &info, err, f, argptr);
va_end(argptr);
/* call the general vocerr formatter */
va_start(argptr, f);
vocerr_va(ctx, &info, err, f, argptr);
va_end(argptr);
}
/*
* Handle an unknown verb or sentence structure. We'll call this when
* we encounter a sentence where we don't know the verb word, or we
* don't know the combination of verb and verb preposition, or we don't
* recognize the sentence structure (for example, an indirect object is
* present, but we don't have a template defined using an indirect
* object for the verb).
*
* This function calls the game-defined function parseUnknownVerb, if it
* exists. If the function doesn't exist, we'll simply display the
* given error message, using the normal parseError mechanism. The
* function should use "abort" or "exit" if it wants to cancel further
* processing of the command.
*
* We'll return true if the function exists, in which case normal
* processing should continue with any remaining command on the command
* line. We'll return false if the function doesn't exist, in which
* case the remainder of the command should be aborted.
*
* 'wrdcnt' is the number of words in the cmd[] array. If wrdcnt is
* zero, we'll automatically count the array entries, with the end of
* the array indicated by a null pointer entry.
*
* 'next_start' is a variable that we may fill in with the index of the
* next word in the command to be parsed. If the user function
* indicates the number of words it consumes, we'll use 'next_start' to
* communicate this back to the caller, so that the caller can resume
* parsing after the part parsed by the function.
*/
int try_unknown_verb(voccxdef *ctx, objnum actor,
char **cmd, int *typelist, int wrdcnt, int *next_start,
int do_fuses, int vocerr_err, const char *vocerr_msg, ...)
{
int show_msg;
va_list argptr;
/* presume we won't show the message */
show_msg = FALSE;
/* determine the word count if the caller passed in zero */
if (wrdcnt == 0)
{
/* count the words before the terminating null entry */
for ( ; cmd[wrdcnt] != nullptr ; ++wrdcnt) ;
}
/* if parseUnknownVerb exists, call it */
if (ctx->voccxpuv != MCMONINV)
{
int err;
int i;
//int do_fuses;
/* no error has occurred yet */
err = 0;
/* presume we will run the fuses */
do_fuses = TRUE;
/* push the error number argument */
runpnum(ctx->voccxrun, (long)vocerr_err);
/* push a list of the word types */
voc_push_numlist(ctx, (uint *)typelist, wrdcnt);
/* push a list of the words */
voc_push_toklist(ctx, cmd, wrdcnt);
/* use "Me" as the default actor */
if (actor == MCMONINV)
actor = ctx->voccxme;
/* push the actor argument */
runpobj(ctx->voccxrun, actor);
/* catch any errors that occur while calling the function */
ERRBEGIN(ctx->voccxerr)
{
/* invoke the function */
runfn(ctx->voccxrun, ctx->voccxpuv, 4);
/* get the return value */
switch(runtostyp(ctx->voccxrun))
{
case DAT_TRUE:
/* the command was handled */
rundisc(ctx->voccxrun);
/* consume the entire command */
*next_start = wrdcnt;
/*
* since the command has now been handled, forget about
* any unknown words
*/
ctx->voccxunknown = 0;
break;
case DAT_NUMBER:
/*
* The command was handled, and the function indicated
* the number of words it wants to skip. Communicate
* this information back to the caller in *next_start.
* Since the routine returns the 1-based index of the
* next entry, we must subtract one to get the number of
* words actually consumed.
*/
*next_start = runpopnum(ctx->voccxrun);
if (*next_start > 0)
--(*next_start);
/* make sure the value is in range */
if (*next_start < 0)
*next_start = 0;
else if (*next_start > wrdcnt)
*next_start = wrdcnt;
/*
* forget about any unknown words in the list up to the
* next word
*/
for (i = 0 ; i < *next_start ; ++i)
{
/* if this word was unknown, forget about that now */
if ((typelist[i] & VOCT_UNKNOWN) != 0
&& ctx->voccxunknown > 0)
--(ctx->voccxunknown);
}
break;
default:
/* treat anything else like nil */
case DAT_NIL:
/* nil - command not handled; show the message */
rundisc(ctx->voccxrun);
show_msg = TRUE;
break;
}
}
ERRCATCH(ctx->voccxerr, err)
{
/* check the error */
switch(err)
{
case ERR_RUNEXIT:
case ERR_RUNEXITOBJ:
/*
* Exit or exitobj was executed - skip to the fuses.
* Forget about any unknown words, since we've finished
* processing this command and we don't want to allow
* "oops" processing.
*/
ctx->voccxunknown = 0;
break;
case ERR_RUNABRT:
/*
* abort was executed - skip to the end of the command,
* but do not execute the fuses
*/
do_fuses = FALSE;
/*
* Since we're aborting the command, ignore any
* remaining unknown words - we're skipping out of the
* command entirely, so we don't care that there were
* unknown words in the command.
*/
ctx->voccxunknown = 0;
break;
default:
/* re-throw any other errors */
errrse(ctx->voccxerr);
}
}
ERREND(ctx->voccxerr);
/* if we're not showing the message, process fuses and daemons */
if (!show_msg)
{
/* execute fuses and daemons */
if (exe_fuses_and_daemons(ctx, err, do_fuses,
actor, MCMONINV, nullptr, 0,
MCMONINV, MCMONINV) != 0)
{
/*
* aborted from fuses and daemons - return false to tell
* the caller not to execute anything left on the
* command line
*/
return FALSE;
}
/* indicate that the game code successfully handled the command */
return TRUE;
}
}
/*
* If we made it here, it means we're showing the default message.
* If we have unknown words, suppress the message so that we show
* the unknown word error instead after returning.
*/
if (ctx->voccxunknown == 0)
{
struct vocerr_va_info info;
/* prepare to format the message */
va_start(argptr, vocerr_msg);
vocerr_va_prep(ctx, &info, vocerr_err, vocerr_msg, argptr);
va_end(argptr);
/* format the mesage */
va_start(argptr, vocerr_msg);
vocerr_va(ctx, &info, vocerr_err, vocerr_msg, argptr);
va_end(argptr);
}
/* indicate that the remainder of the command should be aborted */
return FALSE;
}
/* determine if a tokenized word is a special internal word flag */
/* int vocisspec(char *wrd); */
#define vocisspec(wrd) \
(vocisupper(*wrd) || (!vocisalpha(*wrd) && *wrd != '\'' && *wrd != '-'))
static const vocspdef vocsptab[] =
{
{ "of", VOCW_OF },
{ "and", VOCW_AND },
{ "then", VOCW_THEN },
{ "all", VOCW_ALL },
{ "everyt", VOCW_ALL },
{ "both", VOCW_BOTH },
{ "but", VOCW_BUT },
{ "except", VOCW_BUT },
{ "one", VOCW_ONE },
{ "ones", VOCW_ONES },
{ "it", VOCW_IT },
{ "them", VOCW_THEM },
{ "him", VOCW_HIM },
{ "her", VOCW_HER },
{ "any", VOCW_ANY },
{ "either", VOCW_ANY },
{ nullptr, 0 }
};
/* test a word to see if it's a particular special word */
static int voc_check_special(voccxdef *ctx, const char *wrd, int checktyp)
{
/* search the user or built-in special table, as appropriate */
if (ctx->voccxspp)
{
char *p;
char *endp;
char typ;
int len;
int wrdlen = strlen(wrd);
for (p = ctx->voccxspp, endp = p + ctx->voccxspl ;
p < endp ; )
{
typ = *p++;
len = *p++;
/* if this word matches in type and text, we have a match */
if (typ == checktyp
&& len == wrdlen && !memcmp(p, wrd, (size_t)len))
return TRUE;
/* no match - keep going */
p += len;
}
}
else
{
const vocspdef *x;
for (x = vocsptab ; x->vocspin ; ++x)
{
/* if it matches in type and text, we have a match */
if (x->vocspout == checktyp
&& !strncmp((const char *)wrd, x->vocspin, (size_t)6))
return TRUE;
}
}
/* didn't find a match for the text and type */
return FALSE;
}
/* tokenize a command line - returns number of words in command */
int voctok(voccxdef *ctx, char *cmd, char *outbuf, char **wrd,
int lower, int cvt_ones, int show_errors)
{
int i;
const vocspdef *x;
int l;
char *p;
char *w;
uint len;
for (i = 0 ;; )
{
while (vocisspace(*cmd)) cmd++;
if (!*cmd)
{
wrd[i] = outbuf;
*outbuf = '\0';
return(i);
}
wrd[i++] = outbuf;
if (vocisalpha(*cmd) || *cmd == '-')
{
while(vocisalpha(*cmd) || vocisdigit(*cmd) ||
*cmd=='\'' || *cmd=='-')
{
*outbuf++ = (vocisupper(*cmd) && lower) ? tolower(*cmd) : *cmd;
++cmd;
}
/*
* Check for a special case: abbreviations that end in a
* period. For example, "Mr. Patrick J. Wayne." We wish
* to absorb the period after "Mr" and the one after "J"
* into the respective words; we detect this condition by
* actually trying to find a word in the dictionary that
* has the period.
*/
w = wrd[i-1];
len = outbuf - w;
if (*cmd == '.')
{
*outbuf++ = *cmd++; /* add the period to the word */
*outbuf = '\0'; /* null-terminate it */
++len;
if (!vocffw(ctx, (char *)w, len, nullptr, 0, PRP_NOUN,
(vocseadef *)nullptr)
&& !vocffw(ctx, (char *)w, len, nullptr, 0, PRP_ADJ,
(vocseadef *)nullptr))
{
/* no word with period in dictionary - remove period */
--outbuf;
--cmd;
--len;
}
}
/* null-terminate the buffer */
*outbuf = '\0';
/* find compound words and glue them together */
for (p = ctx->voccxcpp, l = ctx->voccxcpl ; l ; )
{
uint l1 = osrp2(p);
char *p2 = p + l1; /* get second word */
uint l2 = osrp2(p2);
char *p3 = p2 + l2; /* get compound word */
uint l3 = osrp2(p3);
if (i > 1 && len == (l2 - 2)
&& !memcmp(w, p2 + 2, (size_t)len)
&& strlen((char *)wrd[i-2]) == (l1 - 2)
&& !memcmp(wrd[i-2], p + 2, (size_t)(l1 - 2)))
{
memcpy(wrd[i-2], p3 + 2, (size_t)(l3 - 2));
*(wrd[i-2] + l3 - 2) = '\0';
--i;
break;
}
/* move on to the next word */
l -= l1 + l2 + l3;
p = p3 + l3;
}
/*
* Find any special keywords, and set to appropriate flag
* char. Note that we no longer convert "of" in this
* fashion; "of" is now handled separately in order to
* facilitate its use as an ordinary preposition.
*/
if (ctx->voccxspp)
{
//char *p;
char *endp;
char typ;
//int len;
uint wrdlen = strlen((char *)wrd[i-1]);
for (p = ctx->voccxspp, endp = p + ctx->voccxspl ;
p < endp ; )
{
typ = *p++;
len = *p++;
if (len == wrdlen && !memcmp(p, wrd[i-1], (size_t)len)
&& (cvt_ones || (typ != VOCW_ONE && typ != VOCW_ONES))
&& typ != VOCW_OF)
{
*wrd[i-1] = typ;
*(wrd[i-1] + 1) = '\0';
break;
}
p += len;
}
}
else
{
for (x = vocsptab ; x->vocspin ; ++x)
{
if (!strncmp((char *)wrd[i-1], x->vocspin, (size_t)6)
&& (cvt_ones ||
(x->vocspout != VOCW_ONE
&& x->vocspout != VOCW_ONES))
&& x->vocspout != VOCW_OF)
{
*wrd[i-1] = x->vocspout;
*(wrd[i-1] + 1) = '\0';
break;
}
}
}
/* make sure the output pointer is fixed up to the right spot */
outbuf = wrd[i-1];
outbuf += strlen((char *)outbuf);
}
else if (vocisdigit( *cmd ))
{
while(vocisdigit(*cmd) || vocisalpha(*cmd)
|| *cmd == '\'' || *cmd == '-')
*outbuf++ = *cmd++;
}
else switch( *cmd )
{
case '.':
case '!':
case '?':
case ';':
*outbuf++ = VOCW_THEN;
++cmd;
break;
case ',':
case ':':
*outbuf++ = VOCW_AND;
++cmd;
break;
case '"':
case '\'':
{
char *lenptr;
char quote = *cmd++;
/*
* remember that this is a quoted string (it doesn't
* matter whether they're actually using single or
* double quotes - in either case, we use '"' as the
* flag to indicate that it's a quote string)
*/
*outbuf++ = '"';
/* make room for the length prefix */
lenptr = outbuf;
outbuf += 2;
/* copy up to the matching close quote */
while (*cmd && *cmd != quote)
{
char c;
/* get this character */
c = *cmd++;
/* escape the character if necessary */
switch(c)
{
case '\\':
*outbuf++ = '\\';
break;
}
/* copy this character */
*outbuf++ = c;
}
oswp2(lenptr, ((int)(outbuf - lenptr)));
if (*cmd == quote) cmd++;
break;
}
default:
/* display an error if appropriate */
if (show_errors)
{
int hmode = tio_is_html_mode();
/*
* if we're in HTML mode, switch out momentarily, so that
* we show the character literally, even if it's a
* markup-significant character (such as '<' or '&')
*/
if (hmode)
tioputs(ctx->voccxtio, "\\H-");
/* show the message */
vocerr(ctx, VOCERR(1),
"I don't understand the punctuation \"%c\".", *cmd);
/* restore HTML mode if appropriate */
if (hmode)
tioputs(ctx->voccxtio, "\\H+");
}
/* return failure */
return -1;
}
/* null-terminate the result */
*outbuf++ = '\0';
}
}
/* ------------------------------------------------------------------------ */
/*
* Look up a word's type. If 'of_is_spec' is true, we'll treat OF as
* being of type special if it's not otherwise defined.
*/
static int voc_lookup_type(voccxdef *ctx, char *p, int len, int of_is_spec)
{
int t;
/* check for a special word */
if (vocisspec(p))
{
/* it's a special word - this is its type */
t = VOCT_SPEC;
}
else
{
vocwdef *vw;
vocdef *v;
/*
* Now check the various entries of this word to get the word
* type flag bits. The Noun and Adjective flags can be set for
* any word which matches this word in the first six letters (or
* more if more were provided by the player), but the Plural
* flag can only be set if the plural word matches exactly.
* Note that this pass only matches the first word in two-word
* verbs; the second word is considered later during the
* semantic analysis.
*/
for (t = 0, v = ctx->voccxhsh[vochsh((uchar *)p, len)] ; v != nullptr ;
v = v->vocnxt)
{
/* if this hash chain entry matches, add it to our types */
if (voceq((uchar *)p, len, v->voctxt, v->voclen))
{
/* we have a match - look through relation list for word */
for (vw = vocwget(ctx, v->vocwlst) ; vw != nullptr ;
vw = vocwget(ctx, vw->vocwnxt))
{
/* skip this word if it's been deleted */
if (vw->vocwflg & VOCFDEL)
continue;
/* we need a special check for plurals */
if (vw->vocwtyp == PRP_PLURAL)
{
/* plurals must be exact (non-truncated) match */
if (len == v->voclen)
{
/* plurals also count as nouns */
t |= (VOCT_NOUN | VOCT_PLURAL);
}
}
else
{
/* add this type bit to our type value */
t |= voctype[vw->vocwtyp];
}
}
}
}
}
/* check for "of" if the caller wants us to */
if (of_is_spec && t == 0 && voc_check_special(ctx, p, VOCW_OF))
t = VOCT_SPEC;
/* return the type */
return t;
}
/* ------------------------------------------------------------------------ */
/*
* Display an unknown word error, and read a new command, allowing the
* user to respond with the special OOPS command to correct the unknown
* word. Returns a pointer to the start of the replacement text if the
* player entered a correction via OOPS, or a null pointer if the player
* simply entered a new command.
*/
static char *voc_read_oops(voccxdef *ctx, char *oopsbuf, size_t oopsbuflen,
const char *unknown_word)
{
char *p;
/* display the error */
vocerr(ctx, VOCERR(2), "I don't know the word \"%s\".", unknown_word);
/* read a new command */
if (vocread(ctx, MCMONINV, MCMONINV,
oopsbuf, (int)oopsbuflen, 1) == VOCREAD_REDO)
{
/*
* we've already decided it's not an OOPS input - return null to
* indicate to the caller that we have a new command
*/
return nullptr;
}
/* lower-case the string */
for (p = oopsbuf ; *p != '\0' ; ++p)
*p = (vocisupper(*p) ? tolower(*p) : *p);
/* skip leading spaces */
for (p = oopsbuf ; vocisspace(*p) ; ++p) ;
/*
* See if they are saying "oops". Allow "oops" or simply "o",
* followed by either a space or a comma.
*/
if ((strlen(p) > 5 && memcmp(p, "oops ", 5) == 0)
|| (strlen(p) > 5 && memcmp(p, "oops,", 5) == 0))
{
/* we found "OOPS" - move to the next character */
p += 5;
}
else if ((strlen(p) > 2 && memcmp(p, "o ", 2) == 0)
|| (strlen(p) > 2 && memcmp(p, "o,", 2) == 0))
{
/* we found "O" - move to the next character */
p += 2;
}
else
{
/*
* we didn't find any form of "OOPS" response - return null to
* indicate to the caller that the player entered a new command
*/
return nullptr;
}
/* skip spaces before the replacement text */
for ( ; vocisspace(*p) ; ++p) ;
/* return a pointer to the start of the replacement text */
return p;
}
/* ------------------------------------------------------------------------ */
/*
* figure out what parts of speech are associated with each
* word in a tokenized command list
*/
int vocgtyp(voccxdef *ctx, char *cmd[], int types[], char *orgbuf, size_t orgbuflen)
{
int cur;
int t;
char *p;
int len;
int unknown_count = 0;
startover:
if (ctx->voccxflg & VOCCXFDBG)
tioputs(ctx->vocxtio, ". Checking words:\\n");
for (cur = 0 ; cmd[cur] ; ++cur)
{
/* get the word */
p = cmd[cur];
len = strlen(p);
/* look it up */
t = voc_lookup_type(ctx, p, len, FALSE);
/* see if the word was found */
if (t == 0 && !voc_check_special(ctx, p, VOCW_OF))
{
/*
* We didn't find the word. For now, set its type to
* "unknown".
*/
t = VOCT_UNKNOWN;
/*
* If the unknown word count is already non-zero, it means
* that we've tried to let the game resolve this word using
* the parseUnknownDobj/Iobj mechanism, but it wasn't able
* to do so, thus we've come back here to use the normal
* "oops" processing instead.
*
* Don't generate a message until we get to the first
* unknown word from the original list that we weren't able
* to resolve. We may have been able to handle one or more
* of the original list of unknown words (through
* parseNounPhrase or other means), so we don't want to
* generate a message for any words we ended up handling.
* The number we resolved is the last full unknown count
* minus the remaining unknown count.
*/
if (ctx->voccxunknown != 0
&& unknown_count >= ctx->voccxlastunk - ctx->voccxunknown)
{
char oopsbuf[VOCBUFSIZ];
char *p1;
/*
* we can try using the parseUnknownDobj/Iobj again
* after this, so clear the unknown word count for now
*/
ctx->voccxunknown = 0;
/* display an error, and ask for a new command */
p1 = voc_read_oops(ctx, oopsbuf, sizeof(oopsbuf), p);
/* if they responded with replacement text, apply it */
if (p1 != nullptr)
{
char redobuf[200];
char *q;
int i;
int wc;
char **w;
char *outp;
/*
* copy words from the original string, replacing
* the unknown word with what follows the "oops" in
* the new command
*/
for (outp = redobuf, i = 0, w = cmd ; *w != nullptr ; ++i, ++w)
{
/* see what we have */
if (i == cur)
{
/*
* We've reached the word to be replaced.
* Ignore the original token, and replace it
* with the word or words from the OOPS
* command
*/
for (q = p1, len = 0 ;
*q != '\0' && *q != '.' && *q != ','
&& *q != '?' && *q != '!' ; ++q, ++len) ;
memcpy(outp, p1, (size_t)len);
outp += len;
}
else if (**w == '"')
{
char *strp;
char *p2;
char qu;
int rem;
/*
* It's a string - add a quote mark, then
* copy the string as indicated by the
* length prefix, then add another quote
* mark. Get the length by reading the
* length prefix following the quote mark,
* and get a pointer to the text of the
* string, which immediately follows the
* length prefix.
*/
len = osrp2(*w + 1) - 2;
strp = *w + 3;
/*
* We need to figure out what kind of quote
* mark to use. If the string contains any
* embedded double quotes, use single quotes
* to delimit the string; otherwise, use
* double quotes. Presume we'll use double
* quotes as the delimiter, then scan the
* string for embedded double quotes.
*/
for (qu = '"', p2 = strp, rem = len ; rem != 0 ;
--rem, ++p2)
{
/*
* if this is an embedded double quote,
* use single quotes to delimite the
* string
*/
if (*p2 == '"')
{
/* use single quotes as delimiters */
qu = '\'';
/* no need to look any further */
break;
}
}
/* add the open quote */
*outp++ = qu;
/* copy the string */
memcpy(outp, strp, len);
outp += len;
/* add the close quote */
*outp++ = qu;
}
else
{
/*
* it's an ordinary token - copy the
* null-terminated string for the token from
* the original command list
*/
len = strlen(*w);
memcpy(outp, *w, (size_t)len);
outp += len;
}
/* add a space between words */
*outp++ = ' ';
}
/* terminate the new string */
*outp = '\0';
/* try tokenizing the string */
*(cmd[0]) = '\0';
if ((wc = voctok(ctx, redobuf, cmd[0],
cmd, FALSE, FALSE, TRUE)) <= 0)
return 1;
cmd[wc] = nullptr;
/* start over with the typing */
goto startover;
}
else
{
/*
* They didn't start the command with "oops", so
* this must be a brand new command. Replace the
* original command with the new command.
*/
Common::strcpy_s(orgbuf, orgbuflen, oopsbuf);
/*
* forget we had an unknown word so that we're sure
* to start over with a new command
*/
ctx->voccxunknown = 0;
/*
* set the "redo" flag to start over with the new
* command
*/
ctx->voccxredo = 1;
/*
* return an error to indicate the current command
* has been aborted
*/
return 1;
}
}
else
{
/*
* We've now encountered an unknown word, and we're
* going to defer resolution. Remember this; we'll
* count the unknown word in the context when we return
* (do so only locally for now, since we may encounter
* more unknown words before we return, in which case we
* want to know that this is still the first pass).
*/
++unknown_count;
}
}
/* display if in debug mode */
if (ctx->voccxflg & VOCCXFDBG)
{
char buf[128];
size_t i;
//char *p;
int cnt;
(void)tioshow(ctx->voccxtio);
Common::sprintf_s(buf, "... %s (", cmd[cur]);
p = buf + strlen(buf);
cnt = 0;
for (i = 0 ; i < sizeof(type_names)/sizeof(type_names[0]) ; ++i)
{
if (t & (1 << i))
{
if (cnt) *p++ = ',';
Common::strcpy_s(p, sizeof(buf) - (p - buf), type_names[i]);
p += strlen(p);
++cnt;
}
}
*p++ = ')';
*p++ = '\\';
*p++ = 'n';
*p = '\0';
tioputs(ctx->voccxtio, buf);
}
types[cur] = t; /* record type of this word */
}
/* if we found any unknown words, note this in our context */
ctx->voccxunknown = unknown_count;
ctx->voccxlastunk = unknown_count;
/* successful acquisition of types */
return 0;
}
/*
* intersect - takes two lists and puts the intersection of them into
* the first list.
*/
static int vocisect(objnum *list1, objnum *list2)
{
int i, j, k;
for (i = k = 0 ; list1[i] != MCMONINV ; ++i)
{
for (j = 0 ; list2[j] != MCMONINV ; ++j)
{
if (list1[i] == list2[j])
{
list1[k++] = list1[i];
break;
}
}
}
list1[k] = MCMONINV;
return(k);
}
/*
* Intersect lists, including parallel flags lists. The flags from the
* two lists for any matching object are OR'd together.
*/
static int vocisect_flags(objnum *list1, uint *flags1,
objnum *list2, uint *flags2)
{
int i, j, k;
for (i = k = 0 ; list1[i] != MCMONINV ; ++i)
{
for (j = 0 ; list2[j] != MCMONINV ; ++j)
{
if (list1[i] == list2[j])
{
list1[k] = list1[i];
flags1[k] = flags1[i] | flags2[j];
++k;
break;
}
}
}
list1[k] = MCMONINV;
return(k);
}
/*
* get obj list: build a list of the objects that are associated with a
* given word of player input.
*/
static int vocgol(voccxdef *ctx, objnum *list, uint *flags, char **wrdlst,
int *typlst, int first, int cur, int last, int ofword)
{
const char *wrd;
int typ;
vocwdef *v;
int cnt;
int len;
vocseadef search_ctx;
int try_plural;
int try_noun_before_num;
int try_endadj;
int trying_endadj;
int wrdtyp;
/* get the current word and its type */
wrd = wrdlst[cur];
typ = typlst[cur];
/* get the length of the word */
len = strlen(wrd);
/*
* Get word type: figure out the correct part of speech, given by
* context, for a given word. If it could count as only a
* noun/plural or only an adjective, we use that. If it could count
* as either a noun/plural or an adjective, we will treat it as a
* noun/plural if it is the last word in the name or the last word
* before "of", otherwise as an adjective.
*
* If the word is unknown, treat it as a noun or adjective - treat
* it as part of the current noun phrase. One unknown word renders
* the whole noun phrase unknown.
*/
try_plural = (typ & VOCT_PLURAL);
/* presume we won't retry this word as an adjective */
try_endadj = FALSE;
/* presume we won't retry this as a noun before a number */
try_noun_before_num = FALSE;
/* we're not yet trying with adjective-at-end */
trying_endadj = FALSE;
/* check to see what parts of speech are defined for this word */
if ((typ & (VOCT_NOUN | VOCT_PLURAL)) && (typ & VOCT_ADJ))
{
/*
* This can be either an adjective or a plural/noun. If this is
* the last word in the noun phrase, treat it as a noun/plural if
* possible. Otherwise, treat it as an adjective.
*/
if (cur + 1 == last || cur == ofword - 1)
{
/*
* This is the last word in the entire phrase, or the last word
* before an 'of' (which makes it the last word of its
* subphrase). Treat it as a noun if possible, otherwise as a
* plural
*/
wrdtyp = ((typ & VOCT_NOUN) ? PRP_NOUN : PRP_PLURAL);
/*
* If this can be an adjective, too, make a note to come back
* and try it again as an adjective. We prefer not to end a
* noun phrase with an adjective, but we allow it, since it's
* often convenient to abbreviate a noun phrase to just the
* adjectives (as in TAKE RED, where there's only one object
* nearby to which RED applies).
*/
if ((typ & VOCT_ADJ) != 0)
try_endadj = TRUE;
}
else if ((cur + 2 == last || cur == ofword - 2)
&& vocisdigit(wrdlst[cur+1][0]))
{
/*
* This is the second-to-last word, and the last word is
* numeric. In this case, try this word as BOTH a noun and an
* adjective. Try it as an adjective first, but make a note to
* go back and try it again as a noun.
*/
wrdtyp = PRP_ADJ;
try_noun_before_num = TRUE;
}
else
{
/*
* This isn't the last word, so it can only be an adjective.
* Look at it only as an adjective.
*/
wrdtyp = PRP_ADJ;
}
}
else if (typ & VOCT_NOUN)
wrdtyp = PRP_NOUN;
else if (typ & VOCT_UNKNOWN)
wrdtyp = PRP_UNKNOWN;
else
{
/* it's just an adjective */
wrdtyp = PRP_ADJ;
/*
* if this is the last word in the phrase, flag it as an ending
* adjective
*/
if (cur + 1 == last || cur == ofword - 1)
trying_endadj = TRUE;
}
/* display debugger information if appropriate */
if (ctx->voccxflg & VOCCXFDBG)
{
char buf[128];
Common::sprintf_s(buf, "... %s (treating as %s%s)\\n", wrd,
(wrdtyp == PRP_ADJ ? "adjective" :
wrdtyp == PRP_NOUN ? "noun" :
wrdtyp == PRP_INVALID ? "unknown" : "plural"),
(wrdtyp == PRP_NOUN && try_plural ? " + plural" : ""));
tioputs(ctx->vocxtio, buf);
}
/* if this is an unknown word, it doesn't have any objects */
if (wrdtyp == PRP_UNKNOWN)
{
list[0] = MCMONINV;
return 0;
}
/* we have nothing in the list yet */
cnt = 0;
add_words:
for (v = vocffw(ctx, wrd, len, (char *)nullptr, 0, wrdtyp, &search_ctx)
; v != nullptr ; v = vocfnw(ctx, &search_ctx))
{
int i;
/* add the matching object to the output list */
list[cnt] = v->vocwobj;
/* clear the flags */
flags[cnt] = 0;
/* set the PLURAL flag if this is the plural vocabulary usage */
if (wrdtyp == PRP_PLURAL)
flags[cnt] |= VOCS_PLURAL;
/* set the ADJECTIVE AT END flag if appropriate */
if (wrdtyp == PRP_ADJ && trying_endadj)
flags[cnt] |= VOCS_ENDADJ;
/*
* if this is not an exact match for the word, but is merely a
* long-enough leading substring, flag it as truncated
*/
if (len < search_ctx.v->voclen)
flags[cnt] |= VOCS_TRUNC;
/* count the additional word in the list */
++cnt;
/*
* if this object is already in the list with the same flags,
* don't add it again
*/
for (i = 0 ; i < cnt - 1 ; ++i)
{
/* check for an identical entry */
if (list[i] == list[cnt-1] && flags[i] == flags[cnt-1])
{
/* take it back out of the list */
--cnt;
/* no need to continue looking for the duplicate */
break;
}
}
/* make sure we haven't overflowed the list */
if (cnt >= VOCMAXAMBIG)
{
vocerr(ctx, VOCERR(3),
"The word \"%s\" refers to too many objects.", wrd);
list[0] = MCMONINV;
return -1;
}
}
/*
* if we want to go back and try the word again as a noun before a
* number (as in "button 5"), do so now
*/
if (try_noun_before_num && wrdtyp == PRP_ADJ)
{
/* change the word type to noun */
wrdtyp = PRP_NOUN;
/* don't try this again */
try_noun_before_num = FALSE;
/* add the words for the noun usage */
goto add_words;
}
/*
* if we're interpreting the word as a noun, and the word can be a
* plural, add in the plural interpretation as well
*/
if (try_plural && wrdtyp != PRP_PLURAL)
{
/* change the word type to plural */
wrdtyp = PRP_PLURAL;
/* don't try plurals again */
try_plural = FALSE;
/* add the words for the plural usage */
goto add_words;
}
/*
* if this was the last word in the phrase, and it could have been
* an adjective, try it again as an adjective
*/
if (try_endadj && wrdtyp != PRP_ADJ)
{
/* change the word type to adjective */
wrdtyp = PRP_ADJ;
/* note that we're retrying as an adjective */
trying_endadj = TRUE;
/* don't try this again */
try_endadj = FALSE;
/* add the words for the adjective usage */
goto add_words;
}
/*
* If we're interpreting the word as an adjective, and it's
* numeric, include objects with "#" in their adjective list --
* these objects allow arbitrary numbers as adjectives. Don't do
* this if there's only the one word.
*/
if (vocisdigit(wrd[0]) && wrdtyp == PRP_ADJ && first + 1 != last)
{
wrd = "#";
len = 1;
goto add_words;
}
list[cnt] = MCMONINV;
return cnt;
}
/*
* Add the user-defined word for "of" to a buffer. If no such word is
* defined by the user (with the specialWords construct), add "of".
*/
static void vocaddof(voccxdef *ctx, char *buf)
{
if (ctx->voccxspp)
{
size_t len = ctx->voccxspp[1];
size_t oldlen = strlen(buf);
memcpy(buf + oldlen, ctx->voccxspp + 2, len);
buf[len + oldlen] = '\0';
}
else
Common::strcat_s(buf, VOCBUFSIZ, "of");
}
/* ------------------------------------------------------------------------ */
/*
* Call the parseNounPhrase user function, if defined, to attempt to
* parse a noun phrase.
*
* Returns VOC_PNP_ERROR if the hook function indicates that an error
* occurred; PNP_DEFAULT if the hook function told us to use the default
* list; or PNP_SUCCESS to indicate that the hook function provided a
* list to use.
*/
static int voc_pnp_hook(voccxdef *ctx, char *cmd[], int typelist[],
int cur, int *next, int complain,
vocoldef *out_nounlist, int *out_nouncount,
int chkact, int *no_match)
{
runcxdef *rcx = ctx->voccxrun;
runsdef val;
int wordcnt;
char **cmdp;
int outcnt;
vocoldef *outp;
int i;
/* if parseNounPhrase isn't defined, use the default handling */
if (ctx->voccxpnp == MCMONINV)
return VOC_PNP_DEFAULT;
/* push the actor-check flag */
val.runstyp = (chkact ? DAT_TRUE : DAT_NIL);
runpush(rcx, val.runstyp, &val);
/* push the complain flag */
val.runstyp = (complain ? DAT_TRUE : DAT_NIL);
runpush(rcx, val.runstyp, &val);
/* push the current index (adjusted to 1-based user convention) */
runpnum(rcx, cur + 1);
/* count the entries in the command list */
for (wordcnt = 0, cmdp = cmd ; *cmdp != nullptr && **cmdp != '\0' ;
++wordcnt, ++cmdp) ;
/* push the type list */
voc_push_numlist(ctx, (uint *)typelist, wordcnt);
/* push the command word list */
voc_push_strlist_arr(ctx, cmd, wordcnt);
/* call the method */
runfn(rcx, ctx->voccxpnp, 5);
/* check the return value */
if (runtostyp(rcx) == DAT_NUMBER)
{
/* return the status code directly from the hook function */
return (int)runpopnum(rcx);
}
else if (runtostyp(rcx) == DAT_LIST)
{
uchar *lstp;
uint lstsiz;
/* pop the list */
lstp = runpoplst(rcx);
/* read and skip the size prefix */
lstsiz = osrp2(lstp);
lstsiz -= 2;
lstp += 2;
/* the first element should be the next index */
if (lstsiz > 1 && *lstp == DAT_NUMBER)
{
/* set the 'next' pointer, adjusting to 0-based indexing */
*next = osrp4(lstp+1) - 1;
/*
* If 'next' is out of range, force it into range. We can't
* go backwards (so 'next' must always be at least 'cur'),
* and we can't go past the null element at the end of the
* list.
*/
if (*next < cur)
*next = cur;
else if (*next > wordcnt)
*next = wordcnt;
/* skip the list entry */
lstadv(&lstp, &lstsiz);
}
else
{
/* ignore the list and use the default parsing */
return VOC_PNP_DEFAULT;
}
/* read the list entries and store them in the output array */
for (outcnt = 0, outp = out_nounlist ; lstsiz > 0 ; )
{
/* make sure we have room for another entry */
if (outcnt >= VOCMAXAMBIG - 1)
break;
/* get the next list entry, and store it in the output array */
if (*lstp == DAT_NIL)
{
/* set the list entry */
outp->vocolobj = MCMONINV;
/* skip the entry */
lstadv(&lstp, &lstsiz);
}
else if (*lstp == DAT_OBJECT)
{
/* set the list entry */
outp->vocolobj = osrp2(lstp+1);
/* skip the list entry */
lstadv(&lstp, &lstsiz);
}
else
{
/* ignore other types in the list */
lstadv(&lstp, &lstsiz);
continue;
}
/* check for a flag entry */
if (lstsiz > 0 && *lstp == DAT_NUMBER)
{
/* set the flags */
outp->vocolflg = (int)osrp4(lstp+1);
/* skip the number */
lstadv(&lstp, &lstsiz);
}
else
{
/* no flags were specified - use the default */
outp->vocolflg = 0;
}
/* set the word list boundaries */
outp->vocolfst = cmd[cur];
outp->vocollst = cmd[*next - 1];
/* count the entry */
++outp;
++outcnt;
}
/* terminate the list */
outp->vocolobj = MCMONINV;
outp->vocolflg = 0;
/* set the output count */
*out_nouncount = outcnt;
/*
* set "no_match" appropriately -- set "no_match" true if we're
* returning an empty list and we parsed one or more words
*/
if (no_match != nullptr)
*no_match = (outcnt == 0 && *next > cur);
/*
* Adjust the unknown word count in the context. If the routine
* parsed any unknown words, decrement the unknown word count in
* the context by the number of unknown words parsed, since
* these have now been dealt with. If the return list contains
* any objects flagged as having unknown words, add the count of
* such objects back into the context, since we must still
* resolve these at disambiguation time.
*/
for (i = cur ; i < *next ; ++i)
{
/* if this parsed word was unknown, remove it from the count */
if ((typelist[i] & VOCT_UNKNOWN) != 0)
--(ctx->voccxunknown);
}
for (i = 0, outp = out_nounlist ; i < outcnt ; ++i)
{
/* if this object has the unknown flag, count it */
if ((outp->vocolflg & VOCS_UNKNOWN) != 0)
++(ctx->voccxunknown);
}
/* indicate that the hook provided a list */
return VOC_PNP_SUCCESS;
}
else
{
/*
* ignore any other return value - consider others equivalent to
* DEFAULT
*/
rundisc(rcx);
return VOC_PNP_DEFAULT;
}
}
/* ------------------------------------------------------------------------ */
/*
* Build an object name from the words in a command
*/
void voc_make_obj_name(voccxdef *ctx, char *namebuf, char *cmd[],
int firstwrd, int lastwrd)
{
int i;
/* run through the range of words, and add them to the buffer */
for (i = firstwrd, namebuf[0] = '\0' ; i < lastwrd ; ++i)
{
if (voc_check_special(ctx, cmd[i], VOCW_OF))
vocaddof(ctx, namebuf);
else
Common::strcat_s(namebuf, VOCBUFSIZ, cmd[i]);
if (cmd[i][strlen(cmd[i])-1] == '.' && i + 1 < lastwrd)
Common::strcat_s(namebuf, VOCBUFSIZ, "\\");
if (i + 1 < lastwrd)
Common::strcat_s(namebuf, VOCBUFSIZ, " ");
}
}
/*
* Make an object name from a list entry
*/
void voc_make_obj_name_from_list(voccxdef *ctx, char *namebuf,
char *cmd[], const char *firstwrd, const char *lastwrd)
{
int i, i1, i2;
/* find the cmd indices */
for (i = i1 = i2 = 0 ; cmd[i] != nullptr && *cmd[i] != 0 ; ++i)
{
if (cmd[i] == firstwrd)
i1 = i;
if (cmd[i] == lastwrd)
i2 = i + 1;
}
/* build the name */
voc_make_obj_name(ctx, namebuf, cmd, i1, i2);
}
/* ------------------------------------------------------------------------ */
/*
* get 1 obj - attempts to figure out the limits of a single noun
* phrase. Aside from dealing with special words here ("all", "it",
* "them", string objects, numeric objects), we will accept a basic noun
* phrase of the form [article][adjective*][noun]["of" [noun-phrase]].
* (Note that this is not actually recursive; only one "of" can occur in
* a noun phrase.) If successful, we will construct a list of all
* objects that have all the adjectives and nouns in the noun phrase.
* Note that plurals are treated basically like nouns, except that we
* will flag them so that the disambiguator knows to include all objects
* that work with the plural.
*
* Note that we also allow the special constructs "all [of]
* <noun-phrase>" and "both [of] <noun-phrase>"; these are treated
* identically to normal plurals.
*
* If no_match is not null, we'll set it to true if we found valid
* syntax but no matching objects, false otherwise.
*/
static int vocg1o(voccxdef *ctx, char *cmd[], int typelist[],
int cur, int *next, int complain, vocoldef *nounlist,
int chkact, int *no_match)
{
int l1;
int firstwrd;
int i;
int ofword = -1;
int hypothetical_last = -1;
int trim_flags = 0;
int outcnt = 0;
objnum *list1;
uint *flags1;
objnum *list2;
uint *flags2;
char *namebuf;
int has_any = FALSE;
uchar *save_sp;
int found_plural;
int unknown_count;
int trying_count = FALSE;
int retry_with_count;
voc_enter(ctx, &save_sp);
VOC_MAX_ARRAY(ctx, objnum, list1);
VOC_MAX_ARRAY(ctx, uint, flags1);
VOC_MAX_ARRAY(ctx, objnum, list2);
VOC_MAX_ARRAY(ctx, uint, flags2);
VOC_STK_ARRAY(ctx, char, namebuf, VOCBUFSIZ);
/* presume we'll find a match */
if (no_match != nullptr)
*no_match = FALSE;
/* start at the first word */
*next = cur;
/* if we're at the end of the command, return no objects in list */
if (cur == -1 || !cmd[cur]) { VOC_RETVAL(ctx, save_sp, 0); }
/* show trace message if in debug mode */
if (ctx->voccxflg & VOCCXFDBG) {
tioputs(ctx->vocxtio, chkact ? ". Checking for actor\\n" : ". Reading noun phrase\\n");
}
/* try the user parseNounPhrase hook */
switch(voc_pnp_hook(ctx, cmd, typelist, cur, next, complain,
nounlist, &outcnt, chkact, no_match))
{
case VOC_PNP_DEFAULT:
/* continue on to the default processing */
break;
case VOC_PNP_ERROR:
default:
/* return an error */
VOC_RETVAL(ctx, save_sp, -1);
case VOC_PNP_SUCCESS:
/* use their list */
VOC_RETVAL(ctx, save_sp, outcnt);
}
/* check for a quoted string */
if (*cmd[cur] == '"')
{
/* can't use a quoted string as an actor */
if (chkact) { VOC_RETVAL(ctx, save_sp, 0); }
if (ctx->voccxflg & VOCCXFDBG)
tioputs(ctx->vocxtio, "... found quoted string\\n");
nounlist[outcnt].vocolobj = MCMONINV;
nounlist[outcnt].vocolflg = VOCS_STR;
nounlist[outcnt].vocolfst = nounlist[outcnt].vocollst = cmd[cur];
*next = ++cur;
++outcnt;
VOC_RETVAL(ctx, save_sp, outcnt);
}
/* check for ALL/ANY/BOTH/EITHER [OF] <plural> construction */
if ((vocspec(cmd[cur], VOCW_ALL)
|| vocspec(cmd[cur], VOCW_BOTH)
|| vocspec(cmd[cur], VOCW_ANY)) &&
cmd[cur+1] != (char *)nullptr)
{
int nxt;
int n = cur+1;
int has_of;
/* can't use ALL as an actor */
if (chkact) { VOC_RETVAL(ctx, save_sp, 0); }
/* remember whether we have "any" or "either" */
has_any = vocspec(cmd[cur], VOCW_ANY);
/* check for optional 'of' */
if (voc_check_special(ctx, cmd[n], VOCW_OF))
{
if (ctx->voccxflg & VOCCXFDBG)
tioputs(ctx->vocxtio, "... found ALL/ANY/BOTH/EITHER OF\\n");
has_of = TRUE;
n++;
if (!cmd[n])
{
const char *p;
int ver;
if (vocspec(cmd[cur], VOCW_ALL))
{
ver = VOCERR(4);
p = "I think you left something out after \"all of\".";
}
else if (vocspec(cmd[cur], VOCW_ANY))
{
ver = VOCERR(29);
p = "I think you left something out after \"any of\".";
}
else
{
ver = VOCERR(5);
p = "There's something missing after \"both of\".";
}
vocerr(ctx, ver, p);
VOC_RETVAL(ctx, save_sp, -1);
}
}
else
has_of = FALSE;
nxt = n;
if (typelist[n] & VOCT_ARTICLE) ++n; /* skip leading article */
for ( ;; )
{
if (!cmd[n])
break;
if (voc_check_special(ctx, cmd[n], VOCW_OF))
{
++n;
if (!cmd[n])
{
vocerr(ctx, VOCERR(6), "I expected a noun after \"of\".");
VOC_RETVAL(ctx, save_sp, -1);
}
if (*cmd[n] & VOCT_ARTICLE) ++n;
}
else if (typelist[n] & (VOCT_ADJ | VOCT_NOUN))
++n;
else
break;
}
/*
* Accept the ALL if the last word is a plural. Accept the ANY
* if either we don't have an OF (ANY NOUN is okay even without
* a plural), or if we have OF and a plural. (More simply put,
* accept the ALL or ANY if the last word is a plural, or if we
* have ANY but not OF).
*/
if (n > cur && ((typelist[n-1] & VOCT_PLURAL)
|| (has_any && !has_of)))
{
if (ctx->voccxflg & VOCCXFDBG)
tioputs(ctx->vocxtio,
"... found ALL/ANY/BOTH/EITHER + noun phrase\\n");
cur = nxt;
}
}
if (vocspec(cmd[cur], VOCW_ALL) && !has_any)
{
/* can't use ALL as an actor */
if (chkact)
{
VOC_RETVAL(ctx, save_sp, 0);
}
if (ctx->voccxflg & VOCCXFDBG)
tioputs(ctx->vocxtio, "... found ALL\\n");
nounlist[outcnt].vocolobj = MCMONINV;
nounlist[outcnt].vocolflg = VOCS_ALL;
nounlist[outcnt].vocolfst = nounlist[outcnt].vocollst = cmd[cur];
++outcnt;
++cur;
if (cmd[cur] && vocspec(cmd[cur], VOCW_BUT))
{
int cnt;
//int i;
vocoldef *xlist;
uchar *inner_save_sp;
if (ctx->voccxflg & VOCCXFDBG)
tioputs(ctx->vocxtio, "... found ALL EXCEPT\\n");
voc_enter(ctx, &inner_save_sp);
VOC_MAX_ARRAY(ctx, vocoldef, xlist);
cur++;
cnt = vocgobj(ctx, cmd, typelist, cur, next, complain, xlist, 1,
chkact, nullptr);
if (cnt < 0)
{
/*
* An error occurred - return it. Note that, since
* we're returning from the entire function, we're
* popping the save_sp frame, NOT the inner_save_sp
* frame -- the inner frame is nested within the save_sp
* frame, and we want to pop the entire way out since
* we're exiting the entire function.
*/
VOC_RETVAL(ctx, save_sp, cnt);
}
cur = *next;
for (i = 0 ; i < cnt ; ++i)
{
OSCPYSTRUCT(nounlist[outcnt], xlist[i]);
nounlist[outcnt].vocolflg |= VOCS_EXCEPT;
++outcnt;
}
voc_leave(ctx, inner_save_sp);
}
*next = cur;
nounlist[outcnt].vocolobj = MCMONINV;
nounlist[outcnt].vocolflg = 0;
VOC_RETVAL(ctx, save_sp, outcnt);
}
switch(*cmd[cur])
{
case VOCW_IT:
nounlist[outcnt].vocolflg = VOCS_IT;
goto do_special;
case VOCW_THEM:
nounlist[outcnt].vocolflg = VOCS_THEM;
goto do_special;
case VOCW_HIM:
nounlist[outcnt].vocolflg = VOCS_HIM;
goto do_special;
case VOCW_HER:
nounlist[outcnt].vocolflg = VOCS_HER;
/* FALLTHRU */
do_special:
if (ctx->voccxflg & VOCCXFDBG)
tioputs(ctx->vocxtio, "... found pronoun\\n");
*next = cur + 1;
nounlist[outcnt].vocolobj = MCMONINV;
nounlist[outcnt].vocolfst = nounlist[outcnt].vocollst = cmd[cur];
++outcnt;
VOC_RETVAL(ctx, save_sp, outcnt);
default:
break;
}
if (((typelist[cur]
& (VOCT_ARTICLE | VOCT_ADJ | VOCT_NOUN | VOCT_UNKNOWN)) == 0)
&& !vocisdigit(*cmd[cur]))
{
VOC_RETVAL(ctx, save_sp, 0);
}
if (typelist[cur] & VOCT_ARTICLE)
{
++cur;
if (cmd[cur] == (char *)nullptr
|| ((typelist[cur] & (VOCT_ADJ | VOCT_NOUN | VOCT_UNKNOWN)) == 0
&& !vocisdigit(*cmd[cur])))
{
vocerr(ctx, VOCERR(7), "An article must be followed by a noun.");
*next = cur;
VOC_RETVAL(ctx, save_sp, -1);
}
}
/* start at the current word */
firstwrd = cur;
/* scan words for inclusion in this noun phrase */
for (found_plural = FALSE, unknown_count = 0, l1 = 0 ; ; )
{
if (cmd[cur] == (char *)nullptr)
break;
if (typelist[cur] & VOCT_ADJ)
++cur;
else if (typelist[cur] & VOCT_UNKNOWN)
{
/*
* Remember that we found an unknown word, but include it in
* the noun phrase - this will render the entire noun phrase
* unknown, but we'll resolve that later.
*/
++unknown_count;
++cur;
}
else if (typelist[cur] & VOCT_NOUN)
{
++cur;
if (cmd[cur] == (char *)nullptr) break;
if (vocisdigit(*cmd[cur])) ++cur;
if (cmd[cur] == (char *)nullptr) break;
if (!voc_check_special(ctx, cmd[cur], VOCW_OF)) break;
}
else if (vocisdigit(*cmd[cur]))
++cur;
else if (voc_check_special(ctx, cmd[cur], VOCW_OF))
{
++cur;
if (ofword != -1)
{
/* there's already one 'of' - we must be done */
--cur;
break;
}
ofword = cur-1;
if (typelist[cur] & VOCT_ARTICLE) /* allow article after "of" */
++cur;
}
else
break;
/* note whether we found anything that might be a plural */
if (cmd[cur] != nullptr && (typelist[cur] & VOCT_PLURAL) != 0)
found_plural = TRUE;
}
try_again:
/*
* build a printable string consisting of the words in the noun
* phrase, for displaying error messages
*/
voc_make_obj_name(ctx, namebuf, cmd, firstwrd, cur);
/* remember the next word after this noun phrase */
*next = cur;
/*
* If we have any unknown words, we won't be able to match any
* objects for the noun phrase. Return with one entry in the list,
* but use an invalid object and mark the object as containing
* unknown words.
*/
if (unknown_count > 0)
{
/*
* Add one unknown object for each unknown word. This lets us
* communicate the number of unknown words that we found to the
* disambiguator, which will later attempt to resolve the
* reference. Each object we add is the same; they're here only
* for the word count.
*/
for ( ; unknown_count > 0 ; --unknown_count)
{
nounlist[outcnt].vocolobj = MCMONINV;
nounlist[outcnt].vocolflg = VOCS_UNKNOWN;
nounlist[outcnt].vocolfst = cmd[firstwrd];
nounlist[outcnt].vocollst = cmd[cur-1];
++outcnt;
}
nounlist[outcnt].vocolobj = MCMONINV;
nounlist[outcnt].vocolflg = 0;
VOC_RETVAL(ctx, save_sp, outcnt);
}
/* get the list of objects that match the first word */
l1 = vocgol(ctx, list1, flags1, cmd, typelist,
firstwrd, firstwrd, cur, ofword);
/*
* Allow retrying with a count plus a plural if the first word is a
* number, and we have something plural in the list. Only treat "1"
* this way if more words follow in the noun phrase.
*/
retry_with_count = ((vocisdigit(*cmd[firstwrd]) && found_plural)
|| (vocisdigit(*cmd[firstwrd])
&& cur != firstwrd+1
&& atoi(cmd[firstwrd]) == 1));
/* see if we found anything on the first word */
if (l1 <= 0)
{
if (chkact) { VOC_RETVAL(ctx, save_sp, 0); }
if (vocisdigit(*cmd[firstwrd]))
{
if (retry_with_count)
{
/* interpret it as a count plus a plural */
trying_count = TRUE;
/* don't try this again */
retry_with_count = FALSE;
}
else
{
/* not a plural - take the number as the entire noun phrase */
nounlist[outcnt].vocolobj = MCMONINV;
nounlist[outcnt].vocolflg = VOCS_NUM;
nounlist[outcnt].vocolfst = nounlist[outcnt].vocollst =
cmd[firstwrd];
*next = firstwrd + 1;
++outcnt;
nounlist[outcnt].vocolobj = MCMONINV;
nounlist[outcnt].vocolflg = 0;
VOC_RETVAL(ctx, save_sp, outcnt);
}
}
else
{
/*
* display a message if we didn't already (if vocgol
* returned less than zero, it already displayed its own
* error message)
*/
if (l1 == 0)
vocerr(ctx, VOCERR(9), "I don't see any %s here.", namebuf);
/* return failure */
VOC_RETVAL(ctx, save_sp, -1);
}
}
retry_exclude_first:
for (i = firstwrd + 1 ; i < cur ; ++i)
{
int l2;
if (voc_check_special(ctx, cmd[i], VOCW_OF)
|| (typelist[i] & VOCT_ARTICLE))
continue;
/* get the list for this new object */
l2 = vocgol(ctx, list2, flags2, cmd, typelist,
firstwrd, i, cur, ofword);
/* if that failed and displayed an error, return failure */
if (l2 < 0)
{
/* return failure */
VOC_RETVAL(ctx, save_sp, -1);
}
/*
* Intersect the last list with the new list. If the previous
* list didn't have anything in it, it must mean that the word
* list started with a number, in which case we're trying to
* interpret this as a count plus a plural. So, don't intersect
* the list if there was nothing in the first list.
*/
if (l1 == 0)
{
/* just copy the new list */
l1 = l2;
memcpy(list1, list2, (size_t)((l1+1) * sizeof(list1[0])));
memcpy(flags1, flags2, (size_t)(l1 * sizeof(flags1[0])));
}
else
{
/* intersect the two lists */
l1 = vocisect_flags(list1, flags1, list2, flags2);
}
/*
* If there's nothing in the list, it means that there's no
* object that defines all of these words.
*/
if (l1 == 0)
{
if (ctx->voccxflg & VOCCXFDBG)
tioputs(ctx->vocxtio,
"... can't find any objects matching these words\\n");
/*
* If there's an "of", remove the "of" and everything that
* follows, and go back and reprocess the part up to the
* "of" -- treat it as a sentence that has two objects, with
* "of" as the preposition introducing the indirect object.
*/
if (ofword != -1)
{
if (ctx->voccxflg & VOCCXFDBG)
tioputs(ctx->vocxtio,
"... dropping the part after OF and retrying\\n");
/*
* drop the part from 'of' on - scan only from firstwrd
* to the word before 'of'
*/
hypothetical_last = cur;
trim_flags |= VOCS_TRIMPREP;
cur = ofword;
/* forget that we have an 'of' phrase at all */
ofword = -1;
/* retry with the new, shortened phrase */
goto try_again;
}
/*
* Try again with the count + plural interpretation, if
* possible
*/
if (retry_with_count)
{
if (ctx->voccxflg & VOCCXFDBG)
tioputs(ctx->vocxtio,
"... treating the number as a count and retrying\\n");
/* we've exhausted our retries */
retry_with_count = FALSE;
trying_count = TRUE;
/* go try it */
goto retry_exclude_first;
}
/*
* If one of the words will work as a preposition, and we
* took it as an adjective, go back and try the word again
* as a preposition.
*/
for (i = cur - 1; i > firstwrd ; --i)
{
if (typelist[i] & VOCT_PREP)
{
if (ctx->voccxflg & VOCCXFDBG)
tioputs(ctx->vocxtio,
"... changing word to prep and retrying\\n");
hypothetical_last = cur;
trim_flags |= VOCS_TRIMPREP;
cur = i;
goto try_again;
}
}
/* if just checking actor, don't display an error */
if (chkact) { VOC_RETVAL(ctx, save_sp, 0); }
/*
* tell the player about it unless suppressing complaints,
* and return an error
*/
if (complain)
vocerr(ctx, VOCERR(9), "I don't see any %s here.", namebuf);
if (no_match != nullptr)
*no_match = TRUE;
VOC_RETVAL(ctx, save_sp, 0);
}
}
/*
* We have one or more objects, so make a note of how we found
* them.
*/
if (ctx->voccxflg & VOCCXFDBG)
tioputs(ctx->vocxtio, "... found objects matching vocabulary:\\n");
/* store the list of objects that match all of our words */
for (i = 0 ; i < l1 ; ++i)
{
if (ctx->voccxflg & VOCCXFDBG)
{
tioputs(ctx->voccxtio, "..... ");
runppr(ctx->voccxrun, list1[i], PRP_SDESC, 0);
tioflushn(ctx->voccxtio, 1);
}
nounlist[outcnt].vocolfst = cmd[firstwrd];
nounlist[outcnt].vocollst = cmd[cur-1];
nounlist[outcnt].vocolflg =
flags1[i] | (trying_count ? VOCS_COUNT : 0) | trim_flags;
if (trim_flags)
nounlist[outcnt].vocolhlst = cmd[hypothetical_last - 1];
if (has_any)
nounlist[outcnt].vocolflg |= VOCS_ANY;
nounlist[outcnt++].vocolobj = list1[i];
if (outcnt > VOCMAXAMBIG)
{
vocerr(ctx, VOCERR(10),
"You're referring to too many objects with \"%s\".",
namebuf);
VOC_RETVAL(ctx, save_sp, -2);
}
}
/*
* If we have a one-word noun phrase, and the word is a number, add
* the number object into the list of objects we're considering,
* even though we found an object that matches. We'll probably
* easily disambiguate this later, but we need to keep open the
* possibility that they're just referring to a number rather than a
* numbered adjective for now.
*/
if (firstwrd + 1 == cur && vocisdigit(*cmd[firstwrd]))
{
/* add just the number as an object */
nounlist[outcnt].vocolobj = ctx->voccxnum;
nounlist[outcnt].vocolflg = VOCS_NUM;
nounlist[outcnt].vocolfst = nounlist[outcnt].vocollst =
cmd[firstwrd];
++outcnt;
}
/* terminate the list */
nounlist[outcnt].vocolobj = MCMONINV;
nounlist[outcnt].vocolflg = 0;
/* return the number of objects in our match list */
VOC_RETVAL(ctx, save_sp, outcnt);
}
/*
* get obj - gets one or more noun lists (a flag, "multi", says whether
* we should allow multiple lists). We use vocg1o() to read noun lists
* one at a time, and keep going (if "multi" is true) as long as there
* are more "and <noun-phrase>" clauses.
*
* If no_match is not null, we'll set it to true if the syntax was okay
* but we didn't find any match for the list of words, false otherwise.
*/
int vocgobj(voccxdef *ctx, char *cmd[], int typelist[],
int cur, int *next, int complain, vocoldef *nounlist,
int multi, int chkact, int *no_match)
{
int cnt;
int outcnt = 0;
int i;
int again = FALSE;
int lastcur = 0;
vocoldef *tmplist;
uchar *save_sp;
voc_enter(ctx, &save_sp);
VOC_MAX_ARRAY(ctx, vocoldef, tmplist);
for ( ;; )
{
/* try getting a single object */
cnt = vocg1o(ctx, cmd, typelist, cur, next, complain,
tmplist, chkact, no_match);
/* if we encountered a syntax error, return failure */
if (cnt < 0)
{
VOC_RETVAL(ctx, save_sp, cnt);
}
/* if we got any objects, store them in our output list */
if (cnt > 0)
{
for (i = 0 ; i < cnt ; ++i)
{
OSCPYSTRUCT(nounlist[outcnt], tmplist[i]);
if (++outcnt > VOCMAXAMBIG)
{
vocerr(ctx, VOCERR(11),
"You're referring to too many objects.");
VOC_RETVAL(ctx, save_sp, -1);
}
}
}
/* if we didn't find any objects, stop looking */
if (cnt == 0)
{
if (again)
*next = lastcur;
break;
}
/*
* if the caller only wanted a single object (or is getting an
* actor, in which case they implicitly want only a single
* object), stop looking for additional noun phrases
*/
if (!multi || chkact)
break;
/* skip past the previous noun phrase */
cur = *next;
/*
* if we're looking at a noun phrase separator ("and" or a
* comma), get the next noun phrase; otherwise, we're done
*/
if (cur != -1 && cmd[cur] != nullptr && vocspec(cmd[cur], VOCW_AND))
{
lastcur = cur;
while (cmd[cur] && vocspec(cmd[cur], VOCW_AND)) ++cur;
again = TRUE;
if (complain) complain = 2;
}
else
{
/* end of line, or not at a separator - we're done */
break;
}
}
/* terminate the list and return the number of objects we found */
nounlist[outcnt].vocolobj = MCMONINV;
nounlist[outcnt].vocolflg = 0;
VOC_RETVAL(ctx, save_sp, outcnt);
}
/* ------------------------------------------------------------------------ */
/*
* TADS program code interface - tokenize a string. Returns a list of
* strings, with each string giving a token in the command.
*/
void voc_parse_tok(voccxdef *ctx)
{
uchar *save_sp;
runcxdef *rcx = ctx->voccxrun;
char **cmd;
char *inbuf;
char *outbuf;
uchar *p;
uint len;
int cnt;
/* enter our stack frame */
voc_enter(ctx, &save_sp);
/* get the string argument */
p = runpopstr(rcx);
/* get and skip the length prefix */
len = osrp2(p) - 2;
p += 2;
/*
* Allocate space for the original string, and space for the token
* pointers and the tokenized string buffer. We could potentially
* have one token per character in the original string, and we could
* potentially need one extra null terminator for each character in
* the original string; allocate accordingly.
*/
VOC_STK_ARRAY(ctx, char, inbuf, len + 1);
VOC_STK_ARRAY(ctx, char, outbuf, len*2);
VOC_STK_ARRAY(ctx, char *, cmd, len*2);
/* copy the string into our buffer, and null-terminate it */
memcpy(inbuf, p, len);
inbuf[len] = '\0';
/* tokenize the string */
cnt = voctok(ctx, inbuf, outbuf, cmd, TRUE, FALSE, FALSE);
/* check the result */
if (cnt < 0)
{
/* negative count - it's an error, so return nil */
runpnil(rcx);
}
else
{
/* push the return list */
voc_push_toklist(ctx, cmd, cnt);
}
/* leave our stack frame */
voc_leave(ctx, save_sp);
}
/* ------------------------------------------------------------------------ */
/*
* TADS program code interface - get the list of types for a list words.
* The words are simply strings of the type returned from the tokenizer.
* The return value is a list of types, with each entry in the return
* list giving the types of the corresponding entry in the word list.
*/
void voc_parse_types(voccxdef *ctx)
{
runcxdef *rcx = ctx->voccxrun;
uchar *wrdp;
uint wrdsiz;
uchar *typp;
uchar *lstp;
uint lstsiz;
int wrdcnt;
/* get the list of words from the stack */
wrdp = runpoplst(rcx);
/* read and skip the size prefix */
wrdsiz = osrp2(wrdp) - 2;
wrdp += 2;
/* scan the list and count the number of string entries */
for (wrdcnt = 0, lstp = wrdp, lstsiz = wrdsiz ; lstsiz != 0 ;
lstadv(&lstp, &lstsiz))
{
/* if this is a string, count it */
if (*lstp == DAT_SSTRING)
++wrdcnt;
}
/* allocate the return list - one number entry per word */
typp = voc_push_list(ctx, wrdcnt, 4);
/* look up each word and set the corresponding element in the type list */
for (lstp = wrdp, lstsiz = wrdsiz ; lstsiz != 0 ; lstadv(&lstp, &lstsiz))
{
/* if this is a string, look it up in the dictionary */
if (*lstp == DAT_SSTRING)
{
char buf[256];
int curtyp;
uint len;
/* make sure it fits in our buffer */
len = osrp2(lstp+1) - 2;
if (len < sizeof(buf))
{
/* extract the word into our buffer */
memcpy(buf, lstp+3, len);
/* null-terminate it */
buf[len] = '\0';
/* get the type */
curtyp = voc_lookup_type(ctx, buf, len, TRUE);
/* if they didn't find a type at all, set it to UNKNOWN */
if (curtyp == 0)
curtyp = VOCT_UNKNOWN;
}
else
{
/* the string is too big - just mark it as unknown */
curtyp = VOCT_UNKNOWN;
}
/* add this type to the return list */
*typp++ = DAT_NUMBER;
oswp4s(typp, curtyp);
typp += 4;
}
}
}
/* ------------------------------------------------------------------------ */
/*
* Parse a noun phrase from TADS program code. Takes a list of words
* and a list of types from the stack, uses the standard noun phrase
* parser, and returns a list of matching objects. The object list is
* not disambiguated, but merely reflects all matching objects. The
* entire standard parsing algorithm applies, including parseNounPhrase
* invocation if appropriate.
*/
void voc_parse_np(voccxdef *ctx)
{
runcxdef *rcx = ctx->voccxrun;
vocoldef *objlist;
uchar *save_sp;
uchar *wordp;
uint wordsiz;
uchar *typp;
uint typsiz;
int cnt;
char **wordarr;
int wordcnt;
char *wordchararr;
uint wordcharsiz;
int *typarr;
int complain;
int chkact;
int multi;
int no_match;
int next;
int cur;
uchar *lstp;
uint lstsiz;
char *p;
int i;
int old_unknown, old_lastunk;
/* allocate stack arrays */
voc_enter(ctx, &save_sp);
VOC_MAX_ARRAY(ctx, vocoldef, objlist);
/*
* Save the original context unknown values, since we don't want to
* affect the context information in this game-initiated call, then
* clear the unknown word count for the duration of the call.
*/
old_unknown = ctx->voccxunknown;
old_lastunk = ctx->voccxlastunk;
ctx->voccxunknown = ctx->voccxlastunk = 0;
/* get the list of words, and read its length prefix */
wordp = runpoplst(rcx);
wordsiz = osrp2(wordp) - 2;
wordp += 2;
/* get the list of types, and read its length prefix */
typp = runpoplst(rcx);
typsiz = osrp2(typp) - 2;
typp += 2;
/* get the starting index (adjusting for zero-based indexing) */
cur = runpopnum(rcx) - 1;
next = cur;
/* get the flag arguments */
complain = runpoplog(rcx);
multi = runpoplog(rcx);
chkact = runpoplog(rcx);
/* count the words in the word list */
for (wordcnt = 0, lstp = wordp, wordcharsiz = 0, lstsiz = wordsiz ;
lstsiz != 0 ; lstadv(&lstp, &lstsiz))
{
/* count the word */
++wordcnt;
/*
* count the space needed for the word - count the bytes of the
* string plus a null terminator
*/
if (*lstp == DAT_SSTRING)
wordcharsiz += osrp2(lstp+1) + 1;
}
/* allocate space for the word arrays */
VOC_STK_ARRAY(ctx, char, wordchararr, wordcharsiz);
VOC_STK_ARRAY(ctx, char *, wordarr, wordcnt+1);
VOC_STK_ARRAY(ctx, int, typarr, wordcnt+1);
/* build the word array */
for (i = 0, p = wordchararr, lstp = wordp, lstsiz = wordsiz ;
lstsiz != 0 ; lstadv(&lstp, &lstsiz))
{
/* add the word to the word array */
if (*lstp == DAT_SSTRING)
{
uint len;
/* add this entry to the word array */
wordarr[i] = p;
/* copy the word to the character array and null-terminate it */
len = osrp2(lstp + 1) - 2;
memcpy(p, lstp + 3, len);
p[len] = '\0';
/* move the write pointer past this entry */
p += len + 1;
/* bump the index */
++i;
}
}
/* store an empty last entry */
wordarr[i] = nullptr;
/* build the type array */
for (i = 0, lstp = typp, lstsiz = typsiz ;
lstsiz != 0 && i < wordcnt ; lstadv(&lstp, &lstsiz))
{
/* add this entry to the type array */
if (*lstp == DAT_NUMBER)
{
/* set the entry */
typarr[i] = (int)osrp4(lstp + 1);
/* bump the index */
++i;
}
}
/* parse the noun phrase */
cnt = vocgobj(ctx, wordarr, typarr, cur, &next, complain, objlist,
multi, chkact, &no_match);
/* restore context unknown values */
ctx->voccxunknown = old_unknown;
ctx->voccxlastunk = old_lastunk;
/* check the return value */
if (cnt < 0)
{
/* syntax error; return nil to indicate an error */
runpnil(rcx);
}
else if (cnt == 0)
{
/*
* No objects found. Return a list consisting only of the next
* index. If the next index is equal to the starting index,
* this will tell the caller that no noun phrase is
* syntactically present; otherwise, it will tell the caller
* that a noun phrase is present but there are no matching
* objects.
*
* Note that we must increment the returned element index to
* conform with the 1-based index values that the game function
* uses.
*/
++next;
voc_push_numlist(ctx, (uint *)&next, 1);
}
else
{
/*
* We found one or more objects. Return a list whose first
* element is the index of the next word to be parsed, and whose
* remaining elements are sublists. Each sublist contains a
* match for one noun phrase; for each AND adding another noun
* phrase, there's another sublist. Each sublist contains the
* index of the first word of its noun phrase, the index of the
* last word of its noun phrase, and then the objects. For each
* object, there is a pair of entries: the object itself, and
* the flags for the object.
*
* First, figure out how much space we need by scanning the
* return list.
*/
for (lstsiz = 0, i = 0 ; i < cnt ; )
{
int j;
/*
* count the entries in this sublist by looking for the next
* entry whose starting word is different
*/
for (j = i ;
j < cnt && objlist[j].vocolfst == objlist[i].vocolfst ;
++j)
{
/*
* for this entry, we need space for the object (1 + 2
* for an object, or just 1 for nil) and flags (1 + 4)
*/
if (objlist[j].vocolobj == MCMONINV)
lstsiz += 1;
else
lstsiz += 3;
lstsiz += 5;
}
/*
* For this sublist, we need space for the first index (type
* prefix + number = 1 + 4 = 5) and the last index (5).
* We've already counted space for the objects in the list.
* Finally, we need space for the list type and length
* prefixes (1 + 2) for the sublist itself.
*/
lstsiz += (5 + 5) + 3;
/* skip to the next element */
i = j;
}
/*
* finally, we need space for the first element of the list,
* which is the index of the next word to be parsed (1+4)
*/
lstsiz += 5;
/* allocate space for the list */
lstp = voc_push_list_siz(ctx, lstsiz);
/*
* store the first element - the index of the next word to parse
* (adjusting to 1-based indexing)
*/
*lstp++ = DAT_NUMBER;
oswp4s(lstp, next + 1);
lstp += 4;
/* build the list */
for (i = 0 ; i < cnt ; )
{
uchar *sublstp;
int j;
int firstidx = 0;
int lastidx = 0;
/* store the list type prefix */
*lstp++ = DAT_LIST;
/*
* remember where the length prefix is, then skip it - we'll
* come back and fill it in when we're done with the sublist
*/
sublstp = lstp;
lstp += 2;
/* search the array to find the indices of the boundary words */
for (j = 0 ; j < wordcnt ; ++j)
{
/* if this is the first word, remember the index */
if (wordarr[j] == objlist[i].vocolfst)
firstidx = j;
/* if this is the last word, remember the index */
if (wordarr[j] == objlist[i].vocollst)
{
/* remember the index */
lastidx = j;
/*
* we can stop looking now, since the words are
* always in order (so the first index will never be
* after the last index)
*/
break;
}
}
/* add the first and last index, adjusting to 1-based indexing */
*lstp++ = DAT_NUMBER;
oswp4s(lstp, firstidx + 1);
lstp += 4;
*lstp++ = DAT_NUMBER;
oswp4s(lstp, lastidx + 1);
lstp += 4;
/* add the objects */
for (j = i ;
j < cnt && objlist[j].vocolfst == objlist[i].vocolfst ;
++j)
{
/* add this object */
if (objlist[j].vocolobj == MCMONINV)
{
/* just store a nil */
*lstp++ = DAT_NIL;
}
else
{
/* store the object */
*lstp++ = DAT_OBJECT;
oswp2(lstp, objlist[j].vocolobj);
lstp += 2;
}
/* add the flags */
*lstp++ = DAT_NUMBER;
oswp4s(lstp, objlist[i].vocolflg);
lstp += 4;
}
/* fix up the sublist's length prefix */
oswp2(sublstp, lstp - sublstp);
/* move on to the next sublist */
i = j;
}
}
/* exit the stack frame */
voc_leave(ctx, save_sp);
}
/* ------------------------------------------------------------------------ */
/*
* TADS program code interface - given a list of words and a list of
* their types, produce a list of objects that match all of the words.
*/
void voc_parse_dict_lookup(voccxdef *ctx)
{
uchar *save_sp;
runcxdef *rcx = ctx->voccxrun;
uchar *wrdp;
uint wrdsiz;
uchar *typp;
uint typsiz;
objnum *list1;
objnum *list2;
int cnt1;
int cnt2;
/* enter our stack frame and allocate stack arrays */
voc_enter(ctx, &save_sp);
VOC_MAX_ARRAY(ctx, objnum, list1);
VOC_MAX_ARRAY(ctx, objnum, list2);
/* get the word list, and read and skip its size prefix */
wrdp = runpoplst(rcx);
wrdsiz = osrp2(wrdp) - 2;
wrdp += 2;
/* get the type list, and read and skip its size prefix */
typp = runpoplst(rcx);
typsiz = osrp2(typp) - 2;
typp += 2;
/* nothing in the main list yet */
cnt1 = 0;
/* go through the word list */
while (wrdsiz > 0)
{
int curtyp;
int type_prop;
char *curword;
uint curwordlen;
char *curword2;
uint curwordlen2;
vocwdef *v;
char *p;
uint len;
vocseadef search_ctx;
/* if this entry is a string, consider it */
if (*wrdp == DAT_SSTRING)
{
/* get the current word's text string */
curword = (char *)(wrdp + 3);
curwordlen = osrp2(wrdp+1) - 2;
/* check for an embedded space */
for (p = curword, len = curwordlen ; len != 0 && !t_isspace(*p) ;
++p, --len) ;
if (len != 0)
{
/* get the second word */
curword2 = p + 1;
curwordlen2 = len - 1;
/* truncate the first word accordingly */
curwordlen -= len;
}
else
{
/* no embedded space -> no second word */
curword2 = nullptr;
curwordlen2 = 0;
}
/* presume we won't find a valid type property */
type_prop = PRP_INVALID;
/*
* get this type entry, if there's another entry in the
* list, and it's of the appropriate type
*/
if (typsiz > 0 && *typp == DAT_NUMBER)
{
/*
* Figure out what type property we'll be using. We'll
* consider only one meaning for each word, and we'll
* arbitrarily pick one if the type code has more than
* one type, because we expect the caller to provide
* exactly one type per word.
*/
int i;
struct typemap_t
{
int flag;
int prop;
};
static struct typemap_t typemap[] =
{
{ VOCT_ARTICLE, PRP_ARTICLE },
{ VOCT_ADJ, PRP_ADJ },
{ VOCT_NOUN, PRP_NOUN },
{ VOCT_PREP, PRP_PREP },
{ VOCT_VERB, PRP_VERB },
{ VOCT_PLURAL, PRP_PLURAL }
};
struct typemap_t *mapp;
/* get the type */
curtyp = (int)osrp4(typp+1);
/* search for a type */
for (mapp = typemap, i = sizeof(typemap)/sizeof(typemap[0]) ;
i != 0 ; ++mapp, --i)
{
/* if this flag is set, use this type property */
if ((curtyp & mapp->flag) != 0)
{
/* use this one */
type_prop = mapp->prop;
break;
}
}
}
/* nothing in the new list yet */
cnt2 = 0;
/* scan for matching words */
for (v = vocffw(ctx, curword, curwordlen, curword2, curwordlen2,
type_prop, &search_ctx) ;
v != nullptr ;
v = vocfnw(ctx, &search_ctx))
{
int i;
/* make sure we have room in our list */
if (cnt2 >= VOCMAXAMBIG - 1)
break;
/* make sure that this entry isn't already in our list */
for (i = 0 ; i < cnt2 ; ++i)
{
/* if this entry matches, stop looking */
if (list2[i] == v->vocwobj)
break;
}
/* if it's not already in the list, add it now */
if (i == cnt2)
{
/* add it to our list */
list2[cnt2++] = v->vocwobj;
}
}
/* terminate the list */
list2[cnt2] = MCMONINV;
/*
* if there's nothing in the first list, simply copy this
* into the first list; otherwise, intersect the two lists
*/
if (cnt1 == 0)
{
/* this is the first list -> copy it into the main list */
memcpy(list1, list2, (cnt2+1)*sizeof(list2[0]));
cnt1 = cnt2;
}
else
{
/* intersect the two lists */
cnt1 = vocisect(list1, list2);
}
/*
* if there's nothing in the result list now, there's no
* need to look any further, because further intersection
* will yield nothing
*/
if (cnt1 == 0)
break;
}
/* advance the word list */
lstadv(&wrdp, &wrdsiz);
/* if there's anything left in the type list, advance it as well */
if (typsiz > 0)
lstadv(&typp, &typsiz);
}
/* push the result list */
voc_push_objlist(ctx, list1, cnt1);
/* exit our stack frame */
voc_leave(ctx, save_sp);
}
/* ------------------------------------------------------------------------ */
/*
* TADS program code interface - disambiguate a noun list. We take the
* kind of complex object list returned by voc_parse_np(), and produce a
* fully-resolved list of objects.
*/
void voc_parse_disambig(voccxdef *ctx)
{
voccxdef ctx_copy;
uchar *save_sp;
runcxdef *rcx = ctx->voccxrun;
vocoldef *inlist;
vocoldef *outlist;
int objcnt;
char **cmd;
char *cmdbuf;
char *oopsbuf;
objnum actor;
objnum verb;
objnum prep;
objnum otherobj;
prpnum defprop;
prpnum accprop;
prpnum verprop;
uchar *tokp;
uint toksiz;
uchar *objp;
uint objsiz;
uchar *lstp;
uint lstsiz;
int tokcnt;
char *p;
int i;
int silent;
int err;
int unknown_count;
/* allocate our stack arrays */
voc_enter(ctx, &save_sp);
VOC_MAX_ARRAY(ctx, vocoldef, outlist);
VOC_STK_ARRAY(ctx, char, cmdbuf, VOCBUFSIZ);
VOC_STK_ARRAY(ctx, char, oopsbuf, VOCBUFSIZ);
/* get the actor, verb, prep, and otherobj arguments */
actor = runpopobj(rcx);
verb = runpopobj(rcx);
prep = runpopobjnil(rcx);
otherobj = runpopobjnil(rcx);
/*
* get the usage parameter, and use it to select the appropriate
* defprop and accprop
*/
switch(runpopnum(rcx))
{
case VOC_PRO_RESOLVE_DOBJ:
default:
defprop = PRP_DODEFAULT;
accprop = PRP_VALIDDO;
break;
case VOC_PRO_RESOLVE_IOBJ:
defprop = PRP_IODEFAULT;
accprop = PRP_VALIDIO;
break;
case VOC_PRO_RESOLVE_ACTOR:
defprop = PRP_DODEFAULT;
accprop = PRP_VALIDACTOR;
break;
}
/* get the verprop argument */
verprop = runpopprp(rcx);
/* pop the token list, and read and skip the length prefix */
tokp = runpoplst(rcx);
toksiz = osrp2(tokp) - 2;
tokp += 2;
/* pop the object list, and read and skip the length prefix */
objp = runpoplst(rcx);
objsiz = osrp2(objp) - 2;
objp += 2;
/* pop the "silent" flag */
silent = runpoplog(rcx);
/* count the tokens */
for (lstp = tokp, lstsiz = toksiz, tokcnt = 0 ;
lstsiz != 0 ; lstadv(&lstp, &lstsiz))
{
/* if this is a string, count it */
if (*lstp == DAT_SSTRING)
++tokcnt;
}
/* allocate stack space for the command pointers and buffer */
VOC_STK_ARRAY(ctx, char *, cmd, tokcnt + 1);
/* extract the tokens into our pointer buffer */
for (lstp = tokp, lstsiz = toksiz, i = 0, p = cmdbuf ;
lstsiz != 0 ; lstadv(&lstp, &lstsiz))
{
/* if this is a string, count and size it */
if (*lstp == DAT_SSTRING)
{
uint len;
/* store a pointer to the word in the command buffer */
cmd[i++] = p;
/* copy the token into the command buffer and null-terminate it */
len = osrp2(lstp + 1) - 2;
memcpy(p, lstp + 3, len);
p[len] = '\0';
/* move the buffer pointer past this word */
p += len + 1;
}
}
/* store a null at the end of the command pointer list */
cmd[i] = nullptr;
/*
* The object list is provided in the same format as the list
* returned by voc_parse_np(), but the leading index number is
* optional. We don't need the leading index for anything, so if
* it's there, simply skip it so that we can start with the first
* sublist.
*/
if (objsiz > 0 && *objp == DAT_NUMBER)
lstadv(&objp, &objsiz);
/*
* Count the objects in the object list, so that we can figure out
* how much we need to allocate for the input object list.
*/
for (lstp = objp, lstsiz = objsiz, objcnt = 0 ; lstsiz != 0 ;
lstadv(&lstp, &lstsiz))
{
/* if this is a sublist, parse it */
if (*lstp == DAT_LIST)
{
uchar *subp;
uint subsiz;
/* get the sublist pointer, and read and skip the size prefix */
subp = lstp + 1;
subsiz = osrp2(subp) - 2;
subp += 2;
/* scan the sublist */
while (subsiz > 0)
{
/* if this is an object, count it */
if (*subp == DAT_OBJECT || *subp == DAT_NIL)
++objcnt;
/* skip this element */
lstadv(&subp, &subsiz);
}
}
}
/* allocate space for the input list */
VOC_STK_ARRAY(ctx, vocoldef, inlist, objcnt + 1);
/* we don't have any unknown words yet */
unknown_count = 0;
/* parse the list, filling in the input array */
for (lstp = objp, lstsiz = objsiz, i = 0 ; lstsiz != 0 ;
lstadv(&lstp, &lstsiz))
{
/* if this is a sublist, parse it */
if (*lstp == DAT_LIST)
{
uchar *subp;
uint subsiz;
int firstwrd, lastwrd;
/* get the sublist pointer, and read and skip the size prefix */
subp = lstp + 1;
subsiz = osrp2(subp) - 2;
subp += 2;
/* in case we don't find token indices, clear them */
firstwrd = 0;
lastwrd = 0;
/*
* the first two elements of the list are the token indices
* of the first and last words of this object's noun phrase
*/
if (subsiz > 0 && *subp == DAT_NUMBER)
{
/* read the first index, adjusting to zero-based indexing */
firstwrd = (int)osrp4(subp+1) - 1;
/* make sure it's in range */
if (firstwrd < 0)
firstwrd = 0;
else if (firstwrd > tokcnt)
firstwrd = tokcnt;
/* skip it */
lstadv(&subp, &subsiz);
}
if (subsiz > 0 && *subp == DAT_NUMBER)
{
/* read the last index, adjusting to zero-based indexing */
lastwrd = (int)osrp4(subp+1) - 1;
/* make sure it's in range */
if (lastwrd < firstwrd)
lastwrd = firstwrd;
else if (lastwrd > tokcnt)
lastwrd = tokcnt;
/* skip it */
lstadv(&subp, &subsiz);
}
/* scan the sublist */
while (subsiz > 0)
{
/* if this is an object, store it */
if (*subp == DAT_OBJECT || *subp == DAT_NIL)
{
/* store the object */
if (*subp == DAT_OBJECT)
inlist[i].vocolobj = osrp2(subp+1);
else
inlist[i].vocolobj = MCMONINV;
/* set the first and last word pointers */
inlist[i].vocolfst = cmd[firstwrd];
inlist[i].vocollst = cmd[lastwrd];
/* skip the object */
lstadv(&subp, &subsiz);
/* check for flags */
if (subsiz > 0 && *subp == DAT_NUMBER)
{
/* get the flags value */
inlist[i].vocolflg = (int)osrp4(subp+1);
/* skip the number in the list */
lstadv(&subp, &subsiz);
}
else
{
/* clear the flags */
inlist[i].vocolflg = 0;
}
/* if an unknown word was involved, note it */
if ((inlist[i].vocolflg & VOCS_UNKNOWN) != 0)
++unknown_count;
/* consume the element */
++i;
}
else
{
/* skip this element */
lstadv(&subp, &subsiz);
}
}
}
}
/* terminate the list */
inlist[i].vocolobj = MCMONINV;
inlist[i].vocolflg = 0;
/*
* make a copy of our context, so the disambiguation can't make any
* global changes
*/
memcpy(&ctx_copy, ctx, sizeof(ctx_copy));
/*
* Count the unknown words and set the count in the context. This
* will allow us to determine after we call the resolver whether the
* resolution process cleared up the unknown words (via
* parseUnknownDobj/Iobj).
*/
ctx_copy.voccxunknown = ctx_copy.voccxlastunk = unknown_count;
/* disambiguate the noun list */
err = vocdisambig(&ctx_copy, outlist, inlist,
defprop, accprop, verprop, cmd,
otherobj, actor, verb, prep, cmdbuf, VOCBUFSIZ, silent);
/*
* If the error was VOCERR(2) - unknown word - check the input list
* to see if it contained any unknown words. If it does, and we're
* not in "silent" mode, flag the error and then give the user a
* chance to use "oops" to correct the problem. If we're in silent
* mode, don't display an error and don't allow interactive
* correction via "oops."
*
* It is possible that the unknown word is not in the input list,
* but in the user's response to an interactive disambiguation
* query. This is why we must check to see if the unknown word is
* in the original input list or not.
*/
if (err == VOCERR(2) && ctx_copy.voccxunknown != 0 && !silent)
{
char *unk;
int unk_idx = 0;
char *rpl_text;
/*
* forget we have unknown words, since we're going to handle
* them now
*/
ctx_copy.voccxunknown = 0;
/*
* find the unknown word - look up each word until we find one
* that's not in the dictionary
*/
for (i = 0, unk = nullptr ; cmd[i] != nullptr ; ++i)
{
int t;
/*
* get this word's type - if the word has no type, it's an
* unknown word
*/
t = voc_lookup_type(ctx, cmd[i], strlen(cmd[i]), TRUE);
if (t == 0)
{
/* this is it - note it and stop searching */
unk_idx = i;
unk = cmd[i];
break;
}
}
/*
* if we didn't find any unknown words, assume the first word
* was unknown
*/
if (unk == nullptr)
{
unk_idx = 0;
unk = cmd[0];
}
/* display an error, and read a new command */
rpl_text = voc_read_oops(&ctx_copy, oopsbuf, VOCBUFSIZ, unk);
/*
* if they didn't respond with "oops," treat the response as a
* brand new command to replace the current command
*/
if (rpl_text == nullptr)
{
/*
* it's a replacement command - set the redo flag to
* indicate that we should process the replacement command
*/
ctx_copy.voccxredo = TRUE;
/* copy the response into the command buffer */
Common::strcpy_s(cmdbuf, VOCBUFSIZ, oopsbuf);
}
else
{
/* indicate the correction via the result code */
err = VOCERR(45);
/*
* Build the new command string. The new command string
* consists of all of the tokens up to the unknown token,
* then the replacement text, then all of the remaining
* tokens.
*/
for (p = cmdbuf, i = 0 ; cmd[i] != nullptr ; ++i)
{
size_t needed;
/* figure the size needs for this token */
if (i == unk_idx)
{
/* we need to insert the replacement text */
needed = strlen(rpl_text);
}
else
{
/* we need to insert this token string */
needed = strlen(cmd[i]);
}
/*
* if more tokens follow, we need a space after the
* replacement text to separate it from what follows
*/
if (cmd[i+1] != nullptr && needed != 0)
needed += 1;
/* leave room for null termination */
needed += 1;
/* if we don't have room for this token, stop now */
if (needed > (size_t)(VOCBUFSIZ - (p - cmdbuf)))
break;
/*
* if we've reached the unknown token, insert the
* replacement text; otherwise, insert this token
*/
if (i == unk_idx)
{
/* insert the replacement text */
Common::strcpy_s(p, VOCBUFSIZ - (p - cmdbuf), rpl_text);
}
else if (*cmd[i] == '"')
{
char *p1;
char qu;
/*
* Scan the quoted string for embedded double quotes
* - if it has any, use single quotes as the
* delimiter; otherwise, use double quotes as the
* delimiter. Note that we ignore the first and
* last characters in the string, since these are
* always the delimiting double quotes in the
* original token text.
*/
for (qu = '"', p1 = cmd[i] + 1 ;
*p1 != '\0' && *(p1 + 1) != '\0' ; ++p1)
{
/* check for an embedded double quote */
if (*p1 == '"')
{
/* switch to single quotes as delimiters */
qu = '\'';
/* no need to look any further */
break;
}
}
/* add the open quote */
*p++ = qu;
/*
* add the text, leaving out the first and last
* characters (which are the original quotes)
*/
if (strlen(cmd[i]) > 2)
{
memcpy(p, cmd[i] + 1, strlen(cmd[i]) - 2);
p += strlen(cmd[i]) - 2;
}
/* add the closing quote */
*p++ = qu;
/* null-terminate here so we don't skip any further */
*p = '\0';
}
else
{
/* copy this word */
Common::strcpy_s(p, VOCBUFSIZ - (p - cmdbuf), cmd[i]);
}
/* move past this token */
p += strlen(p);
/* add a space if another token follows */
if (cmd[i+1] != nullptr)
*p++ = ' ';
}
/* null-terminate the replacement text */
*p = '\0';
}
}
/*
* Count the objects. An object list is returned only on success or
* VOCERR(44), which indicates that the list is still ambiguous.
*/
if (err == 0 || err == VOCERR(44))
{
/* count the objects in the list */
for (i = 0 ; outlist[i].vocolobj != MCMONINV ; ++i) ;
objcnt = i;
}
else
{
/* there's nothing in the list */
objcnt = 0;
}
/* figure out how much space we need for the objects */
lstsiz = (1+2) * objcnt;
/* add space for the first element, which contains the status code */
lstsiz += (1 + 4);
/* if there's a new command string, we'll store it, so make room */
if (ctx_copy.voccxredo || err == VOCERR(45))
{
/*
* add space for the type prefix (1), length prefix (2), and the
* string bytes (with no null terminator, of course)
*/
lstsiz += (1 + 2 + strlen(cmdbuf));
/*
* if we're retrying due to the redo flag, always return the
* RETRY error code, regardless of what caused us to retry the
* command
*/
if (ctx_copy.voccxredo)
err = VOCERR(43);
}
/* push a list with space for the objects */
lstp = voc_push_list_siz(ctx, lstsiz);
/* store the status code in the first element */
*lstp++ = DAT_NUMBER;
oswp4s(lstp, err);
lstp += 4;
/* store the remainder of the list */
if (err == 0 || err == VOCERR(44))
{
/* fill in the list with the objects */
for (i = 0 ; i < objcnt ; ++i)
{
/* set this element */
*lstp++ = DAT_OBJECT;
oswp2(lstp, outlist[i].vocolobj);
lstp += 2;
}
}
else if (ctx_copy.voccxredo || err == VOCERR(45))
{
uint len;
/* there's a new command - return it as the second element */
*lstp++ = DAT_SSTRING;
/* store the length */
len = strlen(cmdbuf);
oswp2(lstp, len + 2);
lstp += 2;
/* store the string */
memcpy(lstp, cmdbuf, len);
}
/* leave the stack frame */
voc_leave(ctx, save_sp);
}
/* ------------------------------------------------------------------------ */
/*
* TADS program code interface - replace the current command line with a
* new string, aborting the current command.
*/
void voc_parse_replace_cmd(voccxdef *ctx)
{
runcxdef *rcx = ctx->voccxrun;
uchar *p;
uint len;
/* get the string */
p = runpopstr(rcx);
/* read and skip the length prefix */
len = osrp2(p) - 2;
p += 2;
/* make sure it fits in the redo buffer - truncate it if necessary */
if (len + 1 > VOCBUFSIZ)
len = VOCBUFSIZ - 1;
/* copy the string and null-terminate it */
memcpy(ctx->voccxredobuf, p, len);
ctx->voccxredobuf[len] = '\0';
/* set the "redo" flag so that we execute what's in the buffer */
ctx->voccxredo = TRUE;
/* abort the current command so that we start anew with the replacement */
errsig(ctx->voccxerr, ERR_RUNABRT);
}
/* ------------------------------------------------------------------------ */
/*
* This routine gets an actor, which is just a single object reference at
* the beginning of a sentence. We return 0 if we fail to find an actor;
* since this can be either a harmless or troublesome condition, we must
* return additional information. The method used to return back ERROR/OK
* is to set *next != cur if there is an error, *next == cur if not. So,
* getting back (objdef*)0 means that you should check *next. If the return
* value is nonzero, then that object is the actor.
*/
static objnum vocgetactor(voccxdef *ctx, char *cmd[], int typelist[],
int cur, int *next, char *cmdbuf, size_t cmdlen)
{
int l;
vocoldef *nounlist;
vocoldef *actlist;
int cnt;
uchar *save_sp;
prpnum valprop, verprop;
voc_enter(ctx, &save_sp);
VOC_MAX_ARRAY(ctx, vocoldef, nounlist);
VOC_MAX_ARRAY(ctx, vocoldef, actlist);
*next = cur; /* assume no error will occur */
cnt = vocchknoun(ctx, cmd, typelist, cur, next, nounlist, TRUE);
if (cnt > 0 && *next != -1 && cmd[*next] && vocspec(cmd[*next], VOCW_AND))
{
int have_unknown;
/* make a note as to whether the list contains an unknown word */
have_unknown = ((nounlist[0].vocolflg & VOCS_UNKNOWN) != 0);
/*
* If validActor is defined for any of the actors, use it;
* otherwise, for compatibility with past versions, use the
* takeVerb disambiguation mechanism. If we have a pronoun, we
* can't decide yet how to do this, so presume that we'll use
* the new mechanism and switch later if necessary.
*
* If we have don't have a valid object (which will be the case
* for a pronoun), we can't decide until we get into the
* disambiguation process, so presume we'll use validActor for
* now.
*/
verprop = PRP_VERACTOR;
if (nounlist[0].vocolobj == MCMONINV
|| objgetap(ctx->voccxmem, nounlist[0].vocolobj, PRP_VALIDACTOR,
(objnum *)nullptr, FALSE))
valprop = PRP_VALIDACTOR;
else
valprop = PRP_VALIDDO;
/* disambiguate it using the selected properties */
if (vocdisambig(ctx, actlist, nounlist, PRP_DODEFAULT, valprop,
verprop, cmd, MCMONINV, ctx->voccxme,
ctx->voccxvtk, MCMONINV, cmdbuf, cmdlen, FALSE))
{
/*
* if we have an unknown word in the list, assume for the
* moment that this isn't an actor phrase after all, but a
* verb
*/
if (have_unknown)
{
/* indicate that we didn't find a noun phrase syntactically */
*next = cur;
}
/* return no actor */
VOC_RETVAL(ctx, save_sp, MCMONINV);
}
if ((l = voclistlen(actlist)) > 1)
{
vocerr(ctx, VOCERR(12),
"You can only speak to one person at a time.");
*next = cur + 1; /* error flag - return invalid but next!=cur */
VOC_RETVAL(ctx, save_sp, MCMONINV);
}
else if (l == 0)
return(MCMONINV);
if (cmd[*next] && vocspec(cmd[*next], VOCW_AND))
{
++(*next);
VOC_RETVAL(ctx, save_sp, actlist[0].vocolobj);
}
}
if (cnt < 0)
{
/* error - make *next != cur */
*next = cur + 1;
}
else
*next = cur; /* no error condition, but nothing found */
VOC_RETVAL(ctx, save_sp, MCMONINV); /* so return invalid and *next == cur */
}
/* figure out how many objects are in an object list */
int voclistlen(vocoldef *lst)
{
int i;
for (i = 0 ; lst->vocolobj != MCMONINV || lst->vocolflg != 0 ;
++lst, ++i) ;
return(i);
}
/*
* check access - evaluates cmdVerb.verprop(actor, obj, seqno), and
* returns whatever it returns. The seqno parameter is used for special
* cases, such as "ask", when the validation routine wishes to return
* "true" on the first object and "nil" on all subsequent objects which
* correspond to a particular noun phrase. We expect to be called with
* seqno==0 on the first object, non-zero on others; we will pass
* seqno==1 on the first object to the validation property, higher on
* subsequent objects, to maintain consistency with the TADS language
* convention of indexing from 1 up (as seen by the user in indexing
* functions). Note that if we're checking an actor, we'll just call
* obj.validActor() for the object itself (not the verb).
*/
int vocchkaccess(voccxdef *ctx, objnum obj, prpnum verprop,
int seqno, objnum cmdActor, objnum cmdVerb)
{
/*
* special case: the special "string" and "number" objects are
* always accessible
*/
if (obj == ctx->voccxstr || obj == ctx->voccxnum)
return TRUE;
/*
* If the access method is validActor, make sure the object in fact
* has a validActor method defined; if it doesn't, we must be
* running a game from before validActor's invention, so use the old
* ValidXo mechanism instead.
*/
if (verprop == PRP_VALIDACTOR)
{
/* checking an actor - check to see if ValidActor is defined */
if (objgetap(ctx->voccxmem, obj, PRP_VALIDACTOR, (objnum *)nullptr, FALSE))
{
/* ValidActor is present - call ValidActor in the object itself */
runppr(ctx->voccxrun, obj, verprop, 0);
/* return the result */
return runpoplog(ctx->voccxrun);
}
else
{
/* there's no ValidActor - call ValidXo in the "take" verb */
cmdVerb = ctx->voccxvtk;
verprop = PRP_VALIDDO;
}
}
/* call ValidXo in the verb */
runpnum(ctx->voccxrun, (long)(seqno + 1));
runpobj(ctx->voccxrun, obj);
runpobj(ctx->voccxrun,
(objnum)(cmdActor == MCMONINV ? ctx->voccxme : cmdActor));
runppr(ctx->voccxrun, cmdVerb, verprop, 3);
/* return the result */
return runpoplog(ctx->voccxrun);
}
/* ask game if object is visible to the actor */
int vocchkvis(voccxdef *ctx, objnum obj, objnum cmdActor)
{
runpobj(ctx->voccxrun,
(objnum)(cmdActor == MCMONINV ? ctx->voccxme : cmdActor));
runppr(ctx->voccxrun, obj, (prpnum)PRP_ISVIS, 1);
return(runpoplog(ctx->voccxrun));
}
/* set {numObj | strObj}.value, as appropriate */
void vocsetobj(voccxdef *ctx, objnum obj, dattyp typ, const void *val,
vocoldef *inobj, vocoldef *outobj)
{
*outobj = *inobj;
outobj->vocolobj = obj;
objsetp(ctx->voccxmem, obj, PRP_VALUE, typ, val, ctx->voccxundo);
}
/* set up a vocoldef */
static void vocout(vocoldef *outobj, objnum obj, int flg,
const char *fst, const char *lst)
{
outobj->vocolobj = obj;
outobj->vocolflg = flg;
outobj->vocolfst = fst;
outobj->vocollst = lst;
}
/*
* Generate an appropriate error message saying that the objects in the
* command are visible, but can't be used with the command for some
* reason. Use the cantReach method of the verb (the new way), or if
* there is no cantReach in the verb, of each object in the list.
*/
void vocnoreach(voccxdef *ctx, objnum *list1, int cnt,
objnum actor, objnum verb, objnum prep, prpnum defprop,
int show_multi_prefix,
int multi_flags, int multi_base_index, int multi_total_count)
{
/* see if the verb has a cantReach method - use it if so */
if (objgetap(ctx->voccxmem, verb, PRP_NOREACH, (objnum *)nullptr, FALSE))
{
/* push arguments: (actor, dolist, iolist, prep) */
runpobj(ctx->voccxrun, prep);
if (defprop == PRP_DODEFAULT)
{
runpnil(ctx->voccxrun);
voc_push_objlist(ctx, list1, cnt);
}
else
{
voc_push_objlist(ctx, list1, cnt);
runpnil(ctx->voccxrun);
}
runpobj(ctx->voccxrun, actor);
/* invoke the method in the verb */
runppr(ctx->voccxrun, verb, PRP_NOREACH, 4);
}
else
{
int i;
/* use the old way - call obj.cantReach() for each object */
for (i = 0 ; i < cnt ; ++i)
{
/*
* display this object's name if there's more than one, so
* that the player can tell to which object this message
* applies
*/
voc_multi_prefix(ctx, list1[i], show_multi_prefix, multi_flags,
multi_base_index + i, multi_total_count);
/* call cantReach on the object */
runpobj(ctx->voccxrun,
(objnum)(actor == MCMONINV ? ctx->voccxme : actor));
runppr(ctx->voccxrun, list1[i], (prpnum)PRP_NOREACH, 1);
tioflush(ctx->voccxtio);
}
}
}
/*
* Get the specialWords string for a given special word entry. Returns
* the first string if multiple strings are defined for the entry.
*/
static void voc_get_spec_str(voccxdef *ctx, char vocw_id,
char *buf, size_t buflen,
const char *default_name)
{
int found;
/* presume we won't find it */
found = FALSE;
/* if there's a special word list, search it for this entry */
if (ctx->voccxspp != nullptr)
{
char *p;
char *endp;
size_t len;
/* find appropriate user-defined word in specialWords list */
for (p = ctx->voccxspp, endp = p + ctx->voccxspl ; p < endp ; )
{
/* if this one matches, get its first word */
if (*p++ == vocw_id)
{
/* note that we found it */
found = TRUE;
/*
* get the length, and limit it to the buffer size,
* leaving room for null termination
*/
len = *p++;
if (len + 1 > buflen)
len = buflen - 1;
/* copy it and null-terminate the string */
memcpy(buf, p, len);
buf[len] = '\0';
/* we found it - no need to look any further */
break;
}
/*
* move on to the next one - skip the length prefix plus the
* length
*/
p += *p + 1;
}
}
/* if we didn't find it, use the default */
if (!found)
{
Common::strlcpy(buf, default_name, (size_t)buflen);
}
}
/* set it/him/her */
static int vocsetit(voccxdef *ctx, objnum obj, int accprop,
objnum actor, objnum verb, objnum prep,
vocoldef *outobj, const char *default_name, char vocw_id,
prpnum defprop, int silent)
{
if (obj == MCMONINV || !vocchkaccess(ctx, obj, (prpnum)accprop,
0, actor, verb))
{
char nambuf[40];
/* get the display name for this specialWords entry */
voc_get_spec_str(ctx, vocw_id, nambuf, sizeof(nambuf), default_name);
/* show the error if appropriate */
if (!silent)
{
/* use 'noreach' if possible, otherwise use a default message */
if (obj == MCMONINV)
vocerr(ctx, VOCERR(13),
"I don't know what you're referring to with '%s'.",
nambuf);
else
vocnoreach(ctx, &obj, 1, actor, verb, prep,
defprop, FALSE, 0, 0, 1);
}
/* indicate that the antecedent is inaccessible */
return VOCERR(13);
}
/* set the object */
vocout(outobj, obj, 0, default_name, default_name);
return 0;
}
/*
* Get a new numbered object, given a number. This is used for objects
* that define '#' as one of their adjectives; we call the object,
* asking it to create an object with a particular number. The object
* can return nil, in which case we'll reject the command.
*/
static objnum voc_new_num_obj(voccxdef *ctx, objnum objn,
objnum actor, objnum verb,
long num, int plural)
{
/* push the number - if we need a plural object, use nil instead */
if (plural)
runpnil(ctx->voccxrun);
else
runpnum(ctx->voccxrun, num);
/* push the other arguments and call the method */
runpobj(ctx->voccxrun, verb);
runpobj(ctx->voccxrun, actor);
runppr(ctx->voccxrun, objn, PRP_NEWNUMOBJ, 3);
/* if it was rejected, return an invalid object, else return the object */
if (runtostyp(ctx->voccxrun) == DAT_NIL)
{
rundisc(ctx->voccxrun);
return MCMONINV;
}
else
return runpopobj(ctx->voccxrun);
}
/* check if an object defines the special adjective '#' */
static int has_gen_num_adj(voccxdef *ctx, objnum objn)
{
vocwdef *v;
vocseadef search_ctx;
/* scan the list of objects defined '#' as an adjective */
for (v = vocffw(ctx, "#", 1, (char *)nullptr, 0, PRP_ADJ, &search_ctx) ;
v ; v = vocfnw(ctx, &search_ctx))
{
/* if this is the object, return positive indication */
if (v->vocwobj == objn)
return TRUE;
}
/* didn't find it */
return FALSE;
}
/* ------------------------------------------------------------------------ */
/*
* Call the deepverb's disambigDobj or disambigIobj method to perform
* game-controlled disambiguation.
*/
static int voc_disambig_hook(voccxdef *ctx, objnum verb, objnum actor,
objnum prep, objnum otherobj,
prpnum accprop, prpnum verprop,
objnum *objlist, uint *flags, int *objcount,
const char *firstwrd, const char *lastwrd,
int num_wanted, int is_ambig, char *resp,
int silent)
{
runcxdef *rcx = ctx->voccxrun;
prpnum call_prop;
runsdef val;
uchar *lstp;
uint lstsiz;
int ret;
int i;
/* check for actor disambiguation */
if (verprop == PRP_VERACTOR)
{
/* do nothing on actor disambiguation */
return VOC_DISAMBIG_CONT;
}
/* figure out whether this is a dobj method or an iobj method */
call_prop = (accprop == PRP_VALIDDO ? PRP_DISAMBIGDO : PRP_DISAMBIGIO);
/* if the method isn't defined, we can skip this entirely */
if (objgetap(ctx->voccxmem, verb, call_prop, (objnum *)nullptr, FALSE) == 0)
return VOC_DISAMBIG_CONT;
/* push the "silent" flag */
val.runstyp = (silent ? DAT_TRUE : DAT_NIL);
runpush(rcx, val.runstyp, &val);
/* push the "is_ambiguous" flag */
val.runstyp = (is_ambig ? DAT_TRUE : DAT_NIL);
runpush(rcx, val.runstyp, &val);
/* push the "numWanted" count */
runpnum(rcx, num_wanted);
/* push the flag list */
voc_push_numlist(ctx, flags, *objcount);
/* push the object list */
voc_push_objlist(ctx, objlist, *objcount);
/* push the word list */
voc_push_strlist(ctx, firstwrd, lastwrd);
/* push the verification property */
val.runstyp = DAT_PROPNUM;
val.runsv.runsvprp = verprop;
runpush(rcx, DAT_PROPNUM, &val);
/* push the other object */
runpobj(rcx, otherobj);
/* push the preposition and the actor objects */
runpobj(rcx, prep);
runpobj(rcx, actor);
/* call the method */
runppr(rcx, verb, call_prop, 10);
/* check the return value */
switch(runtostyp(rcx))
{
case DAT_LIST:
/* get the list */
lstp = runpoplst(rcx);
/* read the list size prefix */
lstsiz = osrp2(lstp) - 2;
lstp += 2;
/* check for the status code */
if (lstsiz > 0 && *lstp == DAT_NUMBER)
{
/* get the status code */
ret = osrp4s(lstp+1);
/* skip the element */
lstadv(&lstp, &lstsiz);
}
else
{
/* there's no status code - assume CONTINUE */
ret = VOC_DISAMBIG_CONT;
}
/* check for a PARSE_RESP return */
if (ret == VOC_DISAMBIG_PARSE_RESP)
{
/* the second element is the string */
if (*lstp == DAT_SSTRING)
{
uint len;
/* get the length, and limit it to our buffer size */
len = osrp2(lstp+1) - 2;
if (len > VOCBUFSIZ - 1)
len = VOCBUFSIZ - 1;
/* copy the string into the caller's buffer */
memcpy(resp, lstp+3, len);
resp[len] = '\0';
}
else
{
/* there's no string - ignore it */
ret = VOC_DISAMBIG_CONT;
}
}
else
{
/* store the object list in the caller's list */
for (i = 0 ; lstsiz > 0 && i < VOCMAXAMBIG-1 ; ++i)
{
/* get this object */
if (*lstp == DAT_OBJECT)
objlist[i] = osrp2(lstp+1);
else
objlist[i] = MCMONINV;
/* skip the list entry */
lstadv(&lstp, &lstsiz);
/* check for flags */
if (lstsiz > 0 && *lstp == DAT_NUMBER)
{
/* store the flags */
flags[i] = (int)osrp4(lstp+1);
/* skip the flags elements */
lstadv(&lstp, &lstsiz);
}
else
{
/* no flags - use zero by default */
flags[i] = 0;
}
}
/* store a terminator at the end of the list */
objlist[i] = MCMONINV;
flags[i] = 0;
/* store the output count for the caller */
*objcount = i;
}
/* return the result */
return ret;
case DAT_NUMBER:
/* get the status code */
ret = runpopnum(rcx);
/* ignore raw PARSE_RESP codes, since they need to return a string */
if (ret == VOC_DISAMBIG_PARSE_RESP)
ret = VOC_DISAMBIG_CONT;
/* return the status */
return ret;
default:
/* treat anything else as CONTINUE */
rundisc(rcx);
return VOC_DISAMBIG_CONT;
}
}
/* ------------------------------------------------------------------------ */
/*
* Prune a list of matches by keeping only the matches without the given
* flag value, if we have a mix of entries with and without the flag.
* This is a service routine for voc_prune_matches.
*
* The flag indicates a lower quality of matching, so this routine can
* be used to reduce ambiguity by keeping only the best-quality matches
* when matches of mixed quality are present.
*/
static int voc_remove_objs_with_flag(voccxdef *ctx,
objnum *list, uint *flags, int cnt,
int flag_to_remove)
{
int i;
int flag_cnt;
int special_cnt;
/* first, count the number of objects with the flag */
for (i = 0, flag_cnt = special_cnt = 0 ; i < cnt ; ++i)
{
/* if this object exhibits the flag, count it */
if ((flags[i] & flag_to_remove) != 0)
++flag_cnt;
/* if it's numObj or strObj, count it separately */
if (list[i] == ctx->voccxnum || list[i] == ctx->voccxstr)
++special_cnt;
}
/*
* If all of the objects didn't have the flag, omit the ones that
* did, so that we reduce the ambiguity to those without the flag.
* Don't include the special objects (numObj and strObj) in the
* count, since they will never have any of these flags set.
*/
if (flag_cnt != 0 && flag_cnt < cnt - special_cnt)
{
int dst;
/*
* Remove the flagged objects. Note that we can make this
* adjustment to the arrays in place, because they can only
* shrink - there's no need to make an extra temporary copy.
*/
for (i = 0, dst = 0 ; i < cnt ; ++i)
{
/*
* If this one doesn't have the flag, keep it. Always keep
* the special objects (numObj and strObj).
*/
if ((flags[i] & flag_to_remove) == 0
|| list[i] == ctx->voccxnum
|| list[i] == ctx->voccxstr)
{
/* copy this one to the output location */
list[dst] = list[i];
flags[dst] = flags[i];
/* count the new element of the output */
++dst;
}
}
/* set the updated count */
cnt = dst;
list[cnt] = MCMONINV;
}
/* return the new count */
return cnt;
}
/*
* Prune a list of matches by keeping only the best matches when matches
* of different qualities are present.
*
* If we have a mix of objects matching noun phrases that end in
* adjectives and phrases ending in nouns with the same words, remove
* those elements that end in adjectives, keeping only the better
* matches that end in nouns.
*
* If we have a mix of objects where the words match exactly, and others
* where the words are only leading substrings of longer dictionary
* words, keep only the exact matches.
*
* Returns the number of elements in the result list.
*/
static int voc_prune_matches(voccxdef *ctx,
objnum *list, uint *flags, int cnt)
{
/* remove matches that end with an adjective */
cnt = voc_remove_objs_with_flag(ctx, list, flags, cnt, VOCS_ENDADJ);
/* remove matches that use truncated words */
cnt = voc_remove_objs_with_flag(ctx, list, flags, cnt, VOCS_TRUNC);
/* return the new list size */
return cnt;
}
/* ------------------------------------------------------------------------ */
/*
* Count indistinguishable items.
*
* If 'keep_all' is true, we'll keep all of the items, whether or not
* some are indistinguishable from one another. If 'keep_all' is false,
* we'll keep only one item from each set of indistinguishable items.
*/
static int voc_count_diff(voccxdef *ctx, objnum *list, uint *flags, int *cnt,
int keep_all)
{
int i;
int diff_cnt;
/*
* Presume all items will be distinguishable from one another. As
* we scan the list for indistinguishable items, we'll decrement
* this count each time we find an item that can't be distinguished
* from another item.
*/
diff_cnt = *cnt;
/*
* Look for indistinguishable items.
*
* An object is distinguishable if it doesn't have the special
* property marking it as one of a group of equivalent objects
* (PRP_ISEQUIV), or if it has the property but there is no object
* following it in the list which has the same immediate superclass.
*
* Note that we want to keep the duplicates if we're looking for
* plurals, because the player is explicitly referring to all
* matching objects.
*/
for (i = 0 ; i < *cnt ; ++i)
{
/*
* check to see if this object might have indistinguishable
* duplicates - it must be marked with isEquiv for this to be
* possible
*/
runppr(ctx->voccxrun, list[i], PRP_ISEQUIV, 0);
if (runpoplog(ctx->voccxrun))
{
int j;
int dst;
objnum sc;
/* get the superclass, if possible */
sc = objget1sc(ctx->voccxmem, list[i]);
if (sc == MCMONINV)
continue;
/*
* run through the remainder of the list, and remove any
* duplicates of this item
*/
for (j = i + 1, dst = i + 1 ; j < *cnt ; ++j)
{
/*
* see if it matches our object - if not, keep it in the
* list by copying it to our destination position
*/
if (objget1sc(ctx->voccxmem, list[j]) != sc)
{
/* it's distinguishable - keep it */
list[dst] = list[j];
flags[dst++] = flags[j];
}
else
{
/*
* This item is indistinguishable from the list[i].
* First, reduce the count of different items.
*/
--diff_cnt;
/*
* Keep this object only if we're keeping all
* redundant indistinguishable items.
*/
if (keep_all)
{
/* keep all items -> keep this item */
list[dst] = list[j];
flags[dst++] = flags[j];
}
}
}
/* adjust the count to reflect the updated list */
*cnt = dst;
/* add a terminator */
list[dst] = MCMONINV;
flags[dst] = 0;
}
}
/* return the number of distinguishable items */
return diff_cnt;
}
/* ------------------------------------------------------------------------ */
/*
* vocdisambig - determines which nouns in a noun list apply. When this
* is called, we must know the verb that we are processing, so we delay
* disambiguation until quite late in the parsing of a sentence, opting
* to keep all relevant information around until such time as we can
* meaningfully disambiguate.
*
* This routine resolves any "all [except...]", "it", and "them"
* references. We determine if all of the objects listed are accessible
* (via verb.validDo, verb.validIo). We finally try to determine which
* nouns apply when there are ambiguous nouns by using do.verDo<Verb>
* and io.verIo<Verb>.
*/
int vocdisambig(voccxdef *ctx, vocoldef *outlist, vocoldef *inlist,
prpnum defprop, prpnum accprop, prpnum verprop,
char *cmd[], objnum otherobj, objnum cmdActor,
objnum cmdVerb, objnum cmdPrep, char *cmdbuf,
size_t cmdlen, int silent)
{
int inpos;
int outpos;
int listlen = voclistlen(inlist);
int noreach = FALSE;
prpnum listprop;
uchar *save_sp;
int old_unknown, old_lastunk;
int err;
int still_ambig;
static char one_name[] = "ones";
voc_enter(ctx, &save_sp);
ERRBEGIN(ctx->voccxerr)
/* presume we will not leave any ambiguity in the result */
still_ambig = FALSE;
/* loop through all of the objects in the input list */
for (inpos = outpos = 0 ; inpos < listlen ; ++inpos)
{
/*
* reset the stack to our entrypoint value, since our stack
* variables are all temporary for a single iteration
*/
voc_leave(ctx, save_sp);
voc_enter(ctx, &save_sp);
if (inlist[inpos].vocolflg == VOCS_STR)
{
vocsetobj(ctx, ctx->voccxstr, DAT_SSTRING,
inlist[inpos].vocolfst + 1,
&inlist[inpos], &outlist[outpos]);
++outpos;
}
else if (inlist[inpos].vocolflg == VOCS_NUM)
{
long v1;
char vbuf[4];
v1 = atol(inlist[inpos].vocolfst);
oswp4s(vbuf, v1);
vocsetobj(ctx, ctx->voccxnum, DAT_NUMBER, vbuf,
&inlist[inpos], &outlist[outpos]);
++outpos;
}
else if (inlist[inpos].vocolflg == VOCS_IT ||
(inlist[inpos].vocolflg == VOCS_THEM && ctx->voccxthc == 0))
{
err = vocsetit(ctx, ctx->voccxit, accprop, cmdActor,
cmdVerb, cmdPrep, &outlist[outpos],
inlist[inpos].vocolflg == VOCS_IT ? "it" : "them",
(char)(inlist[inpos].vocolflg == VOCS_IT
? VOCW_IT : VOCW_THEM), defprop, silent);
if (err != 0)
goto done;
++outpos;
}
else if (inlist[inpos].vocolflg == VOCS_HIM)
{
err = vocsetit(ctx, ctx->voccxhim, accprop, cmdActor, cmdVerb,
cmdPrep, &outlist[outpos], "him", VOCW_HIM,
defprop, silent);
if (err != 0)
goto done;
++outpos;
}
else if (inlist[inpos].vocolflg == VOCS_HER)
{
err = vocsetit(ctx, ctx->voccxher, accprop, cmdActor, cmdVerb,
cmdPrep, &outlist[outpos], "her", VOCW_HER,
defprop, silent);
if (err != 0)
goto done;
++outpos;
}
else if (inlist[inpos].vocolflg == VOCS_THEM)
{
int i;
int thempos = outpos;
static char them_name[] = "them";
for (i = 0 ; i < ctx->voccxthc ; ++i)
{
if (outpos >= VOCMAXAMBIG)
{
if (!silent)
vocerr(ctx, VOCERR(11),
"You're referring to too many objects.");
err = VOCERR(11);
goto done;
}
/* add object only if it's still accessible */
if (vocchkaccess(ctx, ctx->voccxthm[i], accprop, 0,
cmdActor, cmdVerb))
{
/* it's still accessible - add it to the list */
vocout(&outlist[outpos++], ctx->voccxthm[i], VOCS_THEM,
them_name, them_name);
}
else
{
/* it's not accessible - complain about it */
vocnoreach(ctx, &ctx->voccxthm[i], 1,
cmdActor, cmdVerb, cmdPrep,
defprop, TRUE, VOCS_THEM, i, ctx->voccxthc);
tioflush(ctx->voccxtio);
}
}
/* make sure we found at least one acceptable object */
if (outpos == thempos)
{
if (!silent)
vocerr(ctx, VOCERR(14),
"I don't know what you're referring to.");
err = VOCERR(14);
goto done;
}
}
else if (inlist[inpos].vocolflg == VOCS_ALL)
{
uchar *l;
int exccnt = 0;
int allpos = outpos;
int k;
uint len;
static char all_name[] = "all";
vocoldef *exclist;
vocoldef *exclist2;
VOC_MAX_ARRAY(ctx, vocoldef, exclist);
VOC_MAX_ARRAY(ctx, vocoldef, exclist2);
if (defprop != PRP_IODEFAULT)
runpobj(ctx->voccxrun, otherobj);
runpobj(ctx->voccxrun, cmdPrep);
runpobj(ctx->voccxrun, cmdActor);
runppr(ctx->voccxrun, cmdVerb, defprop,
defprop == PRP_DODEFAULT ? 3 : 2);
if (runtostyp(ctx->voccxrun) == DAT_LIST)
{
l = runpoplst(ctx->voccxrun);
len = osrp2(l) - 2;
l += 2;
while (len)
{
/* add list element to output if it's an object */
if (*l == DAT_OBJECT)
vocout(&outlist[outpos++], (objnum)osrp2(l+1), 0,
all_name, all_name);
/* move on to next list element */
lstadv(&l, &len);
}
vocout(&outlist[outpos], MCMONINV, 0, (char *)nullptr, (char *)nullptr);
}
else
rundisc(ctx->voccxrun); /* discard non-list value */
/* if we didn't get anything, complain about it and quit */
if (outpos <= allpos)
{
if (!silent)
vocerr(ctx, VOCERR(15),
"I don't see what you're referring to.");
err = VOCERR(15);
goto done;
}
/* remove any items in "except" list */
while (inlist[inpos + 1].vocolflg & VOCS_EXCEPT)
{
OSCPYSTRUCT(exclist[exccnt], inlist[++inpos]);
exclist[exccnt++].vocolflg &= ~VOCS_EXCEPT;
}
exclist[exccnt].vocolobj = MCMONINV;
exclist[exccnt].vocolflg = 0;
/* disambiguate "except" list */
if (exccnt)
{
err = vocdisambig(ctx, exclist2, exclist, defprop, accprop,
verprop, cmd, otherobj, cmdActor,
cmdVerb, cmdPrep, cmdbuf, cmdlen, silent);
if (err != 0)
goto done;
exccnt = voclistlen(exclist2);
for (k = 0 ; k < exccnt ; ++k)
{
int i;
for (i = allpos ; i < outpos ; ++i)
{
if (outlist[i].vocolobj == exclist2[k].vocolobj)
{
int j;
for (j = i ; j < outpos ; ++j)
outlist[j].vocolobj = outlist[j+1].vocolobj;
--i;
--outpos;
if (outpos <= allpos)
{
if (!silent)
vocerr(ctx, VOCERR(15),
"I don't see what you're referring to.");
err = VOCERR(15);
goto done;
}
}
}
}
}
}
else /* we have a (possibly ambiguous) noun */
{
int lpos = inpos;
int i = 0;
int cnt;
const char *p;
int cnt2, cnt3;
int trying_again;
int user_count = 0;
objnum *cantreach_list;
int unknown_count;
int use_all_objs;
objnum *list1;
uint *flags1;
objnum *list2;
uint *flags2;
objnum *list3;
uint *flags3;
char *usrobj;
uchar *lstbuf;
char *newobj;
char *disnewbuf;
char *disbuffer;
char **diswordlist;
int *distypelist;
vocoldef *disnounlist;
int dst;
VOC_MAX_ARRAY(ctx, objnum, list1);
VOC_MAX_ARRAY(ctx, objnum, list2);
VOC_MAX_ARRAY(ctx, objnum, list3);
VOC_MAX_ARRAY(ctx, uint, flags1);
VOC_MAX_ARRAY(ctx, uint, flags2);
VOC_MAX_ARRAY(ctx, uint, flags3);
VOC_MAX_ARRAY(ctx, vocoldef, disnounlist);
VOC_STK_ARRAY(ctx, char, disnewbuf, VOCBUFSIZ);
VOC_STK_ARRAY(ctx, char, disbuffer, 2*VOCBUFSIZ);
VOC_STK_ARRAY(ctx, char *, diswordlist, VOCBUFSIZ);
VOC_STK_ARRAY(ctx, int, distypelist, VOCBUFSIZ);
VOC_STK_ARRAY(ctx, char, usrobj, VOCBUFSIZ);
VOC_STK_ARRAY(ctx, char, newobj, VOCBUFSIZ);
VOC_STK_ARRAY(ctx, uchar, lstbuf, 2 + VOCMAXAMBIG*3);
/* presume we won't resolve any unknown words */
unknown_count = 0;
/*
* Presume that we won't use all the objects that match
* these words, since we normally want to try to find a
* single, unambiguous match for a given singular noun
* phrase. Under certain circumstances, we'll want to keep
* all of the words that match the noun phrase, in which
* case we'll set this flag accordingly.
*/
use_all_objs = FALSE;
/*
* go through the objects matching the current noun phrase
* and add them into our list
*/
while (inlist[lpos].vocolfst == inlist[inpos].vocolfst
&& lpos < listlen)
{
/* add this object to the list of nouns */
list1[i] = inlist[lpos].vocolobj;
/*
* note whether this object matched a plural, whether it
* matched adjective-at-end usage, and whether it
* matched a truncated dictionary word
*/
flags1[i] = inlist[lpos].vocolflg
& (VOCS_PLURAL | VOCS_ANY | VOCS_COUNT
| VOCS_ENDADJ | VOCS_TRUNC);
/* if this is a valid object, count it */
if (list1[i] != MCMONINV)
++i;
/* if there's a user count, note it */
if ((inlist[lpos].vocolflg & VOCS_COUNT) != 0)
user_count = atoi(inlist[lpos].vocolfst);
/* if an unknown word was involved, note it */
if ((inlist[lpos].vocolflg & VOCS_UNKNOWN) != 0)
++unknown_count;
/* move on to the next entry */
++lpos;
}
/* terminate the list */
list1[i] = MCMONINV;
cnt = i;
/*
* If this noun phrase contained an unknown word, check to
* see if the verb defines the parseUnknownXobj() method.
* If so, call the method and check the result.
*/
if (unknown_count > 0)
{
prpnum prp;
/*
* figure out which method to call - use
* parseUnknownDobj if we're disambiguating the direct
* object, parseUnknownIobj for the indirect object
*/
prp = (defprop == PRP_DODEFAULT
? PRP_PARSEUNKNOWNDOBJ : PRP_PARSEUNKNOWNIOBJ);
/* check if the verb defines this method */
if (objgetap(ctx->voccxmem, cmdVerb, prp, (objnum *)nullptr, FALSE))
{
uchar *lstp;
uint lstlen;
/* trace the event for debugging */
if (ctx->voccxflg & VOCCXFDBG)
tioputs(ctx->voccxtio,
"... unknown word: calling "
"parseUnknownXobj\\n");
/* push the list of words in the noun phrase */
voc_push_strlist(ctx, inlist[inpos].vocolfst,
inlist[inpos].vocollst);
/* push the other arguments */
runpobj(ctx->voccxrun, otherobj);
runpobj(ctx->voccxrun, cmdPrep);
runpobj(ctx->voccxrun, cmdActor);
/* call the method */
runppr(ctx->voccxrun, cmdVerb, prp, 4);
/* see what they returned */
switch(runtostyp(ctx->voccxrun))
{
case DAT_OBJECT:
/*
* use the object they returned as the match for
* the noun phrase
*/
list1[cnt++] = runpopobj(ctx->voccxrun);
/* terminate the new list */
list1[cnt] = MCMONINV;
break;
case DAT_LIST:
/*
* use the list of objects they returned as the
* match for the noun phrase
*/
lstp = runpoplst(ctx->voccxrun);
/* get the length of the list */
lstlen = osrp2(lstp) - 2;
lstp += 2;
/* run through the list's elements */
while (lstlen != 0)
{
/* if this is an object, add it */
if (*lstp == DAT_OBJECT
&& i < VOCMAXAMBIG)
list1[cnt++] = osrp2(lstp+1);
/* move on to the next element */
lstadv(&lstp, &lstlen);
}
/*
* Note that we want to use all of these objects
* without disambiguation, since the game code
* has explicitly said that this is the list
* that matches the given noun phrase.
*/
use_all_objs = TRUE;
/* terminate the new list */
list1[cnt] = MCMONINV;
break;
case DAT_TRUE:
/*
* A 'true' return value indicates that the
* parseUnknownXobj routine has fully handled
* the command. They don't want anything more
* to be done with these words. Simply remove
* the unknown words and continue with any other
* words in the list.
*/
rundisc(ctx->voccxrun);
/* we're done with this input phrase */
continue;
default:
/*
* For anything else, use the default mechanism.
* Simply return an error; since the "unknown
* word" flag is set, we'll reparse the
* sentence, this time rejecting unknown words
* from the outset.
*
* Return error 2, since that's the generic "I
* don't know the word..." error code.
*/
rundisc(ctx->voccxrun);
err = VOCERR(2);
goto done;
}
/*
* If we made it this far, it means that they've
* resolved the object for us, so we can consider
* the previously unknown words to be known now.
*/
ctx->voccxunknown -= unknown_count;
}
else
{
/* trace the event for debugging */
if (ctx->voccxflg & VOCCXFDBG)
tioputs(ctx->voccxtio,
"... unknown word: no parseUnknownXobj - "
"restarting parsing\\n");
/*
* The verb doesn't define this method, so we should
* use the traditional method; simply return
* failure, and we'll reparse the sentence to reject
* the unknown word in the usual fashion. Return
* error 2, since that's the generic "I don't know
* the word..." error code.
*/
err = VOCERR(2);
goto done;
}
}
/*
* Use a new method to cut down on the time it will take to
* iterate through the verprop's on all of those words.
* We'll call the verb's validXoList method - it should
* return a list containing all of the valid objects for the
* verb (it's sort of a Fourier transform of validDo).
* We'll intersect that list with the list we're about to
* disambiguate, which should provide a list of objects that
* are already qualified, in that validDo should return true
* for every one of them.
*
* The calling sequence is:
* verb.validXoList(actor, prep, otherobj)
*
* For reverse compatibility, if the return value is nil,
* we use the old algorithm and consider all objects
* that match the vocabulary. The return value must be
* a list to be considered.
*
* If disambiguating the actor, skip this phase, since
* we don't have a verb yet.
*/
if (accprop != PRP_VALIDACTOR && cnt != 0)
{
if (defprop == PRP_DODEFAULT)
listprop = PRP_VALDOLIST;
else
listprop = PRP_VALIOLIST;
/* push the arguments: the actor, prep, and other object */
runpobj(ctx->voccxrun, otherobj);
runpobj(ctx->voccxrun, cmdPrep);
runpobj(ctx->voccxrun, cmdActor);
runppr(ctx->voccxrun, cmdVerb, listprop, 3);
if (runtostyp(ctx->voccxrun) == DAT_LIST)
{
uchar *l;
uint len;
int kept_numobj;
/* presume we won't keep numObj */
kept_numobj = FALSE;
/* read the list length prefix, and skip it */
l = runpoplst(ctx->voccxrun);
len = osrp2(l) - 2;
l += 2;
/*
* For each element of the return value, see if
* it's in list1. If so, copy the object into
* list2, unless it's already in list2.
*/
for (cnt2 = 0 ; len != 0 ; )
{
if (*l == DAT_OBJECT)
{
objnum o = osrp2(l+1);
for (i = 0 ; i < cnt ; ++i)
{
if (list1[i] == o)
{
int j;
/* check to see if o is already in list2 */
for (j = 0 ; j < cnt2 ; ++j)
if (list2[j] == o) break;
/* if o is not in list2 yet, add it */
if (j == cnt2)
{
/* add it */
list2[cnt2] = o;
flags2[cnt2] = flags1[i];
++cnt2;
/*
* if it's numObj, note that
* we've already included it in
* the output list, so that we
* don't add it again later
*/
if (o == ctx->voccxnum)
kept_numobj = TRUE;
}
break;
}
}
}
/* move on to next element */
lstadv(&l, &len);
}
/*
* If the original list included numObj, keep it in
* the accessible list for now - we consider numObj
* to be always accessible. The noun phrase matcher
* will include numObj whenever the player enters a
* single number as a noun phrase, even when the
* number matches an object. Note that we can skip
* this special step if we already kept numObj in
* the valid list.
*/
if (!kept_numobj)
{
/* search the original list for numObj */
for (i = 0 ; i < cnt ; ++i)
{
/* if this original entry is numObj, keep it */
if (list1[i] == ctx->voccxnum)
{
/* keep it in the accessible list */
list2[cnt2++] = ctx->voccxnum;
/* no need to look any further */
break;
}
}
}
/* copy list2 into list1 */
memcpy(list1, list2, (size_t)(cnt2 * sizeof(list1[0])));
memcpy(flags1, flags2, (size_t)cnt2 * sizeof(flags1[0]));
cnt = cnt2;
list1[cnt] = MCMONINV;
}
else
rundisc(ctx->voccxrun);
}
/*
* Determine accessibility and visibility. First, limit
* list1 to those objects that are visible OR accessible,
* and limit list3 to those objects that are visible.
*/
for (cnt = cnt3 = i = 0 ; list1[i] != MCMONINV ; ++i)
{
int is_vis;
int is_acc;
/* determine if the object is visible */
is_vis = vocchkvis(ctx, list1[i], cmdActor);
/* determine if it's accessible */
is_acc = vocchkaccess(ctx, list1[i], accprop, i,
cmdActor, cmdVerb);
/* keep items that are visible OR accessible in list1 */
if (is_acc || is_vis)
{
list1[cnt] = list1[i];
flags1[cnt] = flags1[i];
++cnt;
}
/*
* put items that are visible (regardless of whether or
* not they're accessible) in list3
*/
if (is_vis)
{
list3[cnt3] = list1[i];
flags3[cnt3] = flags1[i];
++cnt3;
}
}
/*
* If some of our accessible objects matched with an
* adjective at the end of the noun phrase, and others
* didn't (i.e., the others matched with a noun or plural at
* the end of the noun phrase), eliminate the ones that
* matched with an adjective at the end. Ending a noun
* phrase with an adjective is really a kind of short-hand;
* if we have matches for both the full name version (with a
* noun at the end) and a short-hand version, we want to
* discard the short-hand version so that we don't treat it
* as ambiguous with the long-name version. Likewise, if we
* have some exact matches and some truncations, keep only
* the exact matches.
*/
cnt = voc_prune_matches(ctx, list1, flags1, cnt);
cnt3 = voc_prune_matches(ctx, list3, flags3, cnt3);
/*
* Now, reduce list1 to objects that are accessible. The
* reason for this multi-step process is to ensure that we
* prune the list with respect to every object in scope
* (visible or accessible for the verb), so that we get the
* most sensible pruning behavior. This is more sensible
* than pruning by accessibility only, because sometimes we
* may have objects that are visible but are not accessible;
* as far as the player is concerned, the visible objects
* are part of the current location, so the player should be
* able to refer to them regardless of whether they're
* accessible.
*/
for (dst = 0, i = 0 ; i < cnt ; ++i)
{
/* check this object for accessibility */
if (vocchkaccess(ctx, list1[i], accprop, i,
cmdActor, cmdVerb))
{
/* keep it in the final list */
list1[dst] = list1[i];
flags1[dst] = flags1[i];
/* count the new list entry */
++dst;
}
}
/* terminate list1 */
cnt = dst;
list1[dst] = MCMONINV;
/*
* Go through the list of accessible objects, and perform
* the sensible-object (verXoVerb) check on each. Copy each
* sensible object to list2.
*/
for (i = 0, cnt2 = 0 ; i < cnt ; ++i)
{
/* run it by the appropriate sensible-object check */
if (accprop == PRP_VALIDACTOR)
{
/* run it through preferredActor */
runppr(ctx->voccxrun, list1[i], PRP_PREFACTOR, 0);
if (runpoplog(ctx->voccxrun))
{
list2[cnt2] = list1[i];
flags2[cnt2] = flags1[i];
++cnt2;
}
}
else
{
/* run it through verXoVerb */
tiohide(ctx->voccxtio);
if (otherobj != MCMONINV)
runpobj(ctx->voccxrun, otherobj);
runpobj(ctx->voccxrun, cmdActor);
runppr(ctx->voccxrun, list1[i], verprop,
(otherobj != MCMONINV ? 2 : 1));
/*
* If that didn't result in a message, this object
* passed the tougher test of ver?oX, so include it
* in list2.
*/
if (!tioshow(ctx->voccxtio))
{
list2[cnt2] = list1[i];
flags2[cnt2] = flags1[i];
++cnt2;
}
}
}
/*
* Construct a string consisting of the words the user typed
* to reference this object, in case we need to complain.
*/
usrobj[0] = '\0';
if (inlist[inpos].vocolfst != nullptr && inlist[inpos].vocollst != nullptr)
{
for (p = inlist[inpos].vocolfst ; p <= inlist[inpos].vocollst
; p += strlen(p) + 1)
{
/* add a space if we have a prior word */
if (usrobj[0] != '\0')
{
/* quote the space if the last word ended with '.' */
if (p[strlen(p)-1] == '.')
Common::strcat_s(usrobj, VOCBUFSIZ, "\\");
/* add the space */
Common::strcat_s(usrobj, VOCBUFSIZ, " ");
}
/* add the current word, or "of" if it's "of" */
if (voc_check_special(ctx, p, VOCW_OF))
vocaddof(ctx, usrobj);
else
Common::strcat_s(usrobj, VOCBUFSIZ, p);
}
}
/*
* If there's nothing in the YES list, and we have just a
* single number as our word, act as though they are talking
* about the number itself, rather than one of the objects
* that happened to use the number -- none of those objects
* make any sense, it seems, so fall back on the number.
*
* Note that we may also have only numObj in the YES list,
* because the noun phrase parser normally adds numObj when
* the player types a noun phrase consisting only of a
* number. Do the same thing in this case -- just return
* the number object.
*/
if ((cnt2 == 0
|| (cnt2 == 1 && list2[0] == ctx->voccxnum))
&& inlist[inpos].vocolfst != nullptr
&& inlist[inpos].vocolfst == inlist[inpos].vocollst
&& vocisdigit(*inlist[inpos].vocolfst))
{
long v1;
char vbuf[4];
v1 = atol(inlist[inpos].vocolfst);
oswp4s(vbuf, v1);
vocsetobj(ctx, ctx->voccxnum, DAT_NUMBER, vbuf,
&inlist[inpos], &outlist[outpos]);
outlist[outpos].vocolflg = VOCS_NUM;
++outpos;
/* skip all objects that matched the number */
for ( ; inlist[inpos+1].vocolobj != MCMONINV
&& inlist[inpos+1].vocolfst == inlist[inpos].vocolfst
; ++inpos) ;
continue;
}
/*
* Check if we found anything in either the YES (list2) or
* MAYBE (list1) lists. If there's nothing in either list,
* complain and return.
*/
if (cnt2 == 0 && cnt == 0)
{
/*
* We have nothing sensible, and nothing even
* accessible. If there's anything merely visible,
* complain about those items.
*/
if (cnt3 != 0)
{
/* there are visible items - complain about them */
cnt = cnt3;
cantreach_list = list3;
noreach = TRUE;
/* give the cantReach message, even for multiple objects */
goto noreach1;
}
else
{
/*
* explain that there's nothing visible or
* accessible matching the noun phrase, and abort
* the command with an error
*/
if (!silent)
vocerr(ctx, VOCERR(9),
"I don't see any %s here.", usrobj);
err = VOCERR(9);
goto done;
}
}
/*
* If anything passed the stronger test (objects passing are
* in list2), use this as our proposed resolution for the
* noun phrase. If nothing passed the stronger test (i.e.,
* list2 is empty), simply keep the list of accessible
* objects in list1.
*/
if (cnt2 != 0)
{
/*
* we have items passing the stronger test -- copy the
* stronger list (list2) to list1
*/
cnt = cnt2;
memcpy(list1, list2, (size_t)(cnt2 * sizeof(list1[0])));
memcpy(flags1, flags2, (size_t)(cnt2 * sizeof(flags1[0])));
}
/*
* Check for redundant objects in the list. If the same
* object appears multiple times in the list, remove the
* extra occurrences. Sometimes, a game can inadvertently
* define the same vocabulary word several times for the
* same object, because of the parser's leniency with
* matching leading substrings of 6 characters or longer.
* To avoid unnecessary "which x do you mean..." errors,
* simply discard any duplicates in the list.
*/
for (dst = 0, i = 0 ; i < cnt ; ++i)
{
int dup;
int j;
/* presume we won't find a duplicate of this object */
dup = FALSE;
/*
* look for duplicates of this object in the remainder
* of the list
*/
for (j = i + 1 ; j < cnt ; ++j)
{
/* check for a duplicate */
if (list1[i] == list1[j])
{
/* note that this object has a duplicate */
dup = TRUE;
/* we don't need to look any further */
break;
}
}
/*
* if this object has no duplicate, retain it in the
* output list
*/
if (!dup)
{
/* copy the element to the output */
list1[dst] = list1[i];
flags1[dst] = flags1[i];
/* count the output */
++dst;
}
}
/* update the count to the new list's size */
cnt = dst;
list1[cnt] = MCMONINV;
/*
* If we have more than one object in the list, and numObj
* is still in the list, remove numObj - we don't want to
* consider numObj to be considered ambiguous with another
* object when the other object passes access and validation
* tests.
*/
if (cnt > 1)
{
/* scan the list for numObj */
for (i = 0, dst = 0 ; i < cnt ; ++i)
{
/* if this isn't numObj, keep this element */
if (list1[i] != ctx->voccxnum)
list1[dst++] = list1[i];
}
/* update the final count */
cnt = dst;
list1[cnt] = MCMONINV;
}
/*
* Check for a generic numeric adjective ('#' in the
* adjective list for the object) in each object. If we
* find it, we need to make sure there's a number in the
* name of the object.
*/
for (i = 0 ; i < cnt ; ++i)
{
if (has_gen_num_adj(ctx, list1[i]))
{
/*
* If they specified a count, create the specified
* number of objects. Otherwise, if the object is
* plural, they mean to use all of the objects, so a
* numeric adjective isn't required -- set the
* numeric adjective property in the object to nil
* to so indicate. Otherwise, look for the number,
* and set the numeric adjective property
* accordingly.
*/
if ((flags1[i] & (VOCS_ANY | VOCS_COUNT)) != 0)
{
int n = (user_count ? user_count : 1);
int j;
objnum objn = list1[i];
/*
* They specified a count, so we want to create
* n-1 copies of the numbered object. Make room
* for the n-1 new copies of this object by
* shifting any elements that follow up n-1
* slots.
*/
if (i + 1 != cnt && n > 1)
{
memmove(&list1[i + n - 1], &list1[i],
(cnt - i) * sizeof(list1[i]));
memmove(&flags1[i + n - 1], &flags1[i],
(cnt - i) * sizeof(flags1[i]));
}
/* create n copies of this object */
for (j = 0 ; j < n ; ++j)
{
long l;
/*
* Generate a number for the new object,
* asking the object to tell us what value
* to use for an "any".
*/
runpnum(ctx->voccxrun, (long)(j + 1));
runppr(ctx->voccxrun, objn, PRP_ANYVALUE, 1);
l = runpopnum(ctx->voccxrun);
/* try creating the new object */
list1[i+j] =
voc_new_num_obj(ctx, objn,
cmdActor, cmdVerb,
l, FALSE);
if (list1[i+j] == MCMONINV)
{
err = VOCERR(40);
goto done;
}
}
}
else if ((flags1[i] & VOCS_PLURAL) != 0)
{
/*
* get the plural object by asking for the
* numbered object with a nil number parameter
*/
list1[i] =
voc_new_num_obj(ctx, list1[i], cmdActor, cmdVerb,
(long)0, TRUE);
if (list1[i] == MCMONINV)
{
err = VOCERR(40);
goto done;
}
}
else
{
//char *p;
int found;
/*
* No plural, no "any" - we just want to create
* one numbered object, using the number that
* the player must have specified. Make sure
* the player did, in fact, specify a number.
*/
for (found = FALSE, p = inlist[inpos].vocolfst ;
p != nullptr && p <= inlist[inpos].vocollst ;
p += strlen(p) + 1)
{
/* did we find it? */
if (vocisdigit(*p))
{
long l;
/* get the number */
l = atol(p);
/* create the object with this number */
list1[i] = voc_new_num_obj(ctx, list1[i],
cmdActor, cmdVerb,
l, FALSE);
if (list1[i] == MCMONINV)
{
err = VOCERR(40);
goto done;
}
/* the command looks to be valid */
found = TRUE;
break;
}
}
/* if we didn't find it, stop now */
if (!found)
{
if (!silent)
vocerr(ctx, VOCERR(160),
"You'll have to be more specific about which %s you mean.",
usrobj);
err = VOCERR(160);
goto done;
}
}
}
}
/*
* We still have an ambiguous word - ask the user which of
* the possible objects they meant to use
*/
trying_again = FALSE;
for (;;)
{
int wrdcnt;
int next;
uchar *pu;
int cleared_noun;
int diff_cnt;
int stat;
int num_wanted;
int is_ambig;
int all_plural;
/*
* check for usage - determine if we have singular
* definite, singular indefinite, counted, or plural
* usage
*/
if ((flags1[0] & (VOCS_PLURAL | VOCS_ANY | VOCS_COUNT)) != 0)
{
//int i;
/*
* loop through the objects to AND together the
* flags from all of the objects; we only care about
* the plural flags (PLURAL, ANY, and COUNT), so
* start out with only those, then AND off any that
* aren't in all of the objects
*/
for (all_plural = VOCS_PLURAL | VOCS_ANY | VOCS_COUNT,
i = 0 ; i < cnt ; ++i)
{
/* AND out this object's flags */
all_plural &= flags1[i];
/*
* if we've ANDed down to zero, there's no need
* to look any further
*/
if (!all_plural)
break;
}
}
else
{
/*
* it looks like we want just a single object -
* clear the various plural flags
*/
all_plural = 0;
}
/*
* Count the distinguishable items.
*
* If we're looking for a single object, don't keep
* duplicate indistinguishable items (i.e., keep only
* one item from each set of mutually indistinguishable
* items), since we could equally well use any single
* one of those items. If we're looking for multiple
* objects, keep all of the items, since the user is
* referring to all of them.
*/
diff_cnt = voc_count_diff(ctx, list1, flags1, &cnt,
all_plural != 0 || use_all_objs);
/*
* Determine how many objects we'd like to find. If we
* have a count specified, we'd like to find the given
* number of objects. If we have "ANY" specified, we
* just want to pick one object arbitrarily. If we have
* all plurals, we can keep all of the objects. If the
* 'use_all_objs' flag is true, it means that we can use
* everything in the list.
*/
if (use_all_objs)
{
/* we want to use all of the objects */
num_wanted = cnt;
is_ambig = FALSE;
}
else if ((all_plural & VOCS_COUNT) != 0)
{
/*
* we have a count - we want exactly the given
* number of objects, but we can pick an arbitrary
* subset, so it's not ambiguous even if we have too
* many at the moment
*/
num_wanted = user_count;
is_ambig = FALSE;
}
else if ((all_plural & VOCS_ANY) != 0)
{
/*
* they specified "any", so we want exactly one, but
* we can pick one arbitrarily, so there's no
* ambiguity
*/
num_wanted = 1;
is_ambig = FALSE;
}
else if (all_plural != 0)
{
/*
* we have a simple plural, so we can use all of the
* provided objects without ambiguity
*/
num_wanted = cnt;
is_ambig = FALSE;
}
else
{
/*
* it's a singular, definite usage, so we want
* exactly one item; if we have more than one in our
* list, it's ambiguous
*/
num_wanted = 1;
is_ambig = (cnt != 1);
}
/* call the disambiguation hook */
stat = voc_disambig_hook(ctx, cmdVerb, cmdActor, cmdPrep,
otherobj, accprop, verprop,
list1, flags1, &cnt,
inlist[inpos].vocolfst,
inlist[inpos].vocollst,
num_wanted, is_ambig, disnewbuf,
silent);
/* check the status */
if (stat == VOC_DISAMBIG_DONE)
{
/* that's it - copy the result */
for (i = 0 ; i < cnt ; ++i)
vocout(&outlist[outpos++], list1[i], flags1[i],
inlist[inpos].vocolfst,
inlist[inpos].vocollst);
/* we're done */
break;
}
else if (stat == VOC_DISAMBIG_CONT)
{
/*
* Continue with the new list (which is the same as
* the old list, if it wasn't actually updated by
* the hook routine) - proceed with remaining
* processing, but using the new list.
*
* Because the list has been updated, we must once
* again count the number of distinguishable items,
* since that may have changed.
*/
diff_cnt = voc_count_diff(ctx, list1, flags1, &cnt, TRUE);
}
else if (stat == VOC_DISAMBIG_PARSE_RESP
|| stat == VOC_DISAMBIG_PROMPTED)
{
/*
* The status indicates one of the following:
*
* - the hook prompted for more information and read
* a response from the player, but decided not to
* parse it; we will continue with the current list,
* and parse the player's response as provided by
* the hook.
*
* - the hook prompted for more information, but
* left the reading to us. We'll proceed with the
* current list and read a response as normal, but
* without displaying another prompt.
*
* In any case, just continue processing; we'll take
* appropriate action on the prompting and reading
* when we reach those steps.
*/
}
else
{
/* anything else is an error */
err = VOCERR(41);
goto done;
}
/*
* If we found only one word, or a plural/ANY, we are
* finished. If we found a count, use that count if
* possible.
*/
if (cnt == 1 || all_plural || use_all_objs)
{
int flags;
/* keep only one of the objects if ANY was used */
if ((all_plural & VOCS_COUNT) != 0)
{
if (user_count > cnt)
{
if (!silent)
vocerr(ctx, VOCERR(30),
"I only see %d of those.", cnt);
err = VOCERR(30);
goto done;
}
cnt = user_count;
flags = VOCS_ALL;
}
else if ((all_plural & VOCS_ANY) != 0)
{
cnt = 1;
flags = VOCS_ALL;
}
else
flags = 0;
/* put the list */
for (i = 0 ; i < cnt ; ++i)
vocout(&outlist[outpos++], list1[i], flags,
inlist[inpos].vocolfst,
inlist[inpos].vocollst);
/* we're done */
break;
}
/* make sure output capturing is off */
tiocapture(ctx->voccxtio, (mcmcxdef *)nullptr, FALSE);
tioclrcapture(ctx->voccxtio);
/*
* if we're in "silent" mode, we can't ask the player
* for help, so return an error
*/
if (silent)
{
/*
* We can't disambiguate the list. Fill in the
* return list with what's left, which is still
* ambiguous, and note that we need to return an
* error code indicating that the list remains
* ambiguous.
*/
for (i = 0 ; i < cnt && outpos < VOCMAXAMBIG ; ++i)
vocout(&outlist[outpos++], list1[i], 0,
inlist[inpos].vocolfst,
inlist[inpos].vocollst);
/* note that we have ambiguity remaining */
still_ambig = TRUE;
/* we're done with this sublist */
break;
}
/*
* We need to prompt for more information interactively.
* Figure out how we're going to display the prompt.
*
* - If the disambigXobj hook status (stat) indicates
* that the hook already displayed a prompt of its own,
* we don't need to add anything here.
*
* - Otherwise, if there's a parseDisambig function
* defined in the game, call it to display the prompt.
*
* - Otherwise, display our default prompt.
*/
if (stat == VOC_DISAMBIG_PARSE_RESP
|| stat == VOC_DISAMBIG_PROMPTED)
{
/*
* the disambigXobj hook already asked for a
* response, so don't display any prompt of our own
*/
}
else if (ctx->voccxpdis != MCMONINV)
{
uint l;
/*
* There's a parseDisambig function defined in the
* game - call it to display the prompt, passing the
* list of possible objects and the player's
* original noun phrase text as parameters.
*/
for (i = 0, pu = lstbuf+2 ; i < cnt ; ++i, pu += 2)
{
*pu++ = DAT_OBJECT;
oswp2(pu, list1[i]);
}
l = pu - lstbuf;
oswp2(lstbuf, l);
runpbuf(ctx->voccxrun, DAT_LIST, lstbuf);
runpstr(ctx->voccxrun, usrobj, (int)strlen(usrobj), 1);
runfn(ctx->voccxrun, ctx->voccxpdis, 2);
}
else
{
/* display "again" message, if necessary */
if (trying_again)
vocerr_info(ctx, VOCERR(100), "Let's try it again: ");
/* ask the user about it */
vocerr_info(ctx, VOCERR(101),
"Which %s do you mean, ", usrobj);
for (i = 0 ; i < cnt ; )
{
int eqcnt;
int j;
objnum sc;
/*
* See if we have multiple instances of an
* identical object. All such instances should
* be grouped together (this was done above), so
* we can just count the number of consecutive
* equivalent objects.
*/
eqcnt = 1;
runppr(ctx->voccxrun, list1[i], PRP_ISEQUIV, 0);
if (runpoplog(ctx->voccxrun))
{
/* get the superclass, if possible */
sc = objget1sc(ctx->voccxmem, list1[i]);
if (sc != MCMONINV)
{
/* count equivalent objects that follow */
for (j = i + 1 ; j < cnt ; ++j)
{
if (objget1sc(ctx->voccxmem, list1[j])
== sc)
++eqcnt;
else
break;
}
}
}
/*
* Display this object's name. If we have only
* one such object, display its thedesc,
* otherwise display its adesc.
*/
runppr(ctx->voccxrun, list1[i],
(prpnum)(eqcnt == 1 ?
PRP_THEDESC : PRP_ADESC), 0);
/* display the separator as appropriate */
if (i + 1 < diff_cnt)
vocerr_info(ctx, VOCERR(102), ", ");
if (i + 2 == diff_cnt)
vocerr_info(ctx, VOCERR(103), "or ");
/* skip all equivalent items */
i += eqcnt;
}
vocerr_info(ctx, VOCERR(104), "?");
}
/*
* Read the response. If the disambigXobj hook already
* read the response, we don't need to read anything
* more.
*/
if (stat != VOC_DISAMBIG_PARSE_RESP
&& vocread(ctx, cmdActor, cmdVerb, disnewbuf,
(int)VOCBUFSIZ, 2) == VOCREAD_REDO)
{
/* they want to treat the input as a new command */
Common::strcpy_s(cmdbuf, cmdlen, disnewbuf);
ctx->voccxunknown = 0;
ctx->voccxredo = TRUE;
err = VOCERR(43);
goto done;
}
/*
* parse the response
*/
/* tokenize the list */
wrdcnt = voctok(ctx, disnewbuf, disbuffer, diswordlist,
TRUE, TRUE, TRUE);
if (wrdcnt == 0)
{
/* empty response - run pardon() function and abort */
runfn(ctx->voccxrun, ctx->voccxprd, 0);
err = VOCERR(42);
goto done;
}
if (wrdcnt < 0)
{
/* return the generic punctuation error */
err = VOCERR(1);
goto done;
}
/*
* Before we tokenize the sentence, remember the current
* unknown word count, then momentarily set the count to
* zero. This will cause the tokenizer to absorb any
* unknown words; if there are any unknown words, the
* tokenizer will parse them and set the unknown count.
* If we find any unknown words in the input, we'll
* simply treat the input as an entirely new command.
*/
old_unknown = ctx->voccxunknown;
old_lastunk = ctx->voccxlastunk;
ctx->voccxunknown = 0;
/* clear our internal type list */
memset(distypelist, 0, VOCBUFSIZ * sizeof(distypelist[0]));
/* tokenize the sentence */
diswordlist[wrdcnt] = nullptr;
if (vocgtyp(ctx, diswordlist, distypelist, cmdbuf, cmdlen)
|| ctx->voccxunknown != 0)
{
/*
* there's an unknown word or other problem - retry
* the input as an entirely new command
*/
Common::strcpy_s(cmdbuf, cmdlen, disnewbuf);
ctx->voccxunknown = 0;
ctx->voccxredo = TRUE;
err = VOCERR(2);
goto done;
}
/* restore the original unknown word count */
ctx->voccxunknown = old_unknown;
ctx->voccxlastunk = old_lastunk;
/*
* Find the last word that can be an adj and/or a noun.
* If it can be either (i.e., both bits are set), clear
* the noun bit and make it just an adjective. This is
* because we're asking for an adjective for clarification,
* and we most likely want it to be an adjective in this
* context; if the noun bit is set, too, the object lister
* will think it must be a noun, being the last word.
*/
for (i = 0 ; i < wrdcnt ; ++i)
{
if (!(distypelist[i] &
(VOCT_ADJ | VOCT_NOUN | VOCT_ARTICLE)))
break;
}
if (i && (distypelist[i-1] & VOCT_ADJ)
&& (distypelist[i-1] & VOCT_NOUN))
{
/*
* Note that we're clearing the noun flag. If
* we're unsuccessful in finding the object with the
* noun flag cleared, we'll put the noun flag back
* in and give it another try (by adding VOCT_NOUN
* back into distypelist[cleared_noun], and coming
* back to the label below).
*/
cleared_noun = i-1;
distypelist[i-1] &= ~VOCT_NOUN;
}
else
cleared_noun = -1;
try_current_flags:
/* start with the first word */
if (vocspec(diswordlist[0], VOCW_ALL)
|| vocspec(diswordlist[0], VOCW_BOTH))
{
char *nam;
static char all_name[] = "all";
static char both_name[] = "both";
if (vocspec(diswordlist[0], VOCW_ALL))
nam = all_name;
else
nam = both_name;
for (i = 0 ; i < cnt ; ++i)
vocout(&outlist[outpos++], list1[i], 0, nam, nam);
if (noreach)
{
cantreach_list = list1;
goto noreach1;
}
break;
}
else if (vocspec(diswordlist[0], VOCW_ANY))
{
/* choose the first object arbitrarily */
vocout(&outlist[outpos++], list1[i], VOCS_ALL, "any", "any");
break;
}
else
{
/* check for a word matching the phrase */
cnt2 = vocchknoun(ctx, diswordlist, distypelist,
0, &next, disnounlist, FALSE);
if (cnt2 > 0)
{
/*
* if that didn't consume the entire phrase, or
* at least up to "one" or "ones" or a period,
* disallow it, since they must be entering
* something more complicated
*/
if (diswordlist[next] != nullptr
&& !vocspec(diswordlist[next], VOCW_ONE)
&& !vocspec(diswordlist[next], VOCW_ONES)
&& !vocspec(diswordlist[next], VOCW_THEN))
{
cnt2 = 0;
}
}
else if (cnt2 < 0)
{
/*
* There was a syntax error in the phrase.
* vocchknoun() will have displayed a message in
* this case, so we're done parsing this command.
*/
err = VOCERR(45);
goto done;
}
/* proceed only if we got a valid phrase */
if (cnt2 > 0)
{
//int cnt3;
int newcnt;
/* build the list of matches for the new phrase */
for (i = 0, newcnt = 0 ; i < cnt2 ; ++i)
{
int j;
int found;
/*
* make sure this object isn't already in
* our list - we want each object only once
*/
for (j = 0, found = FALSE ; j < newcnt ; ++j)
{
/* if this is in the list, note it */
if (list2[j] == disnounlist[i].vocolobj)
{
found = TRUE;
break;
}
}
/*
* add it to our list only if it wasn't
* already there
*/
if (!found)
{
/* copy the object ID */
list2[newcnt] = disnounlist[i].vocolobj;
/* copy the flags that we care about */
flags2[newcnt] = disnounlist[i].vocolflg
& (VOCS_PLURAL | VOCS_ANY
| VOCS_COUNT);
/* count the entry */
++newcnt;
}
}
/* terminate the list */
list2[newcnt] = MCMONINV;
/* intersect the new list with the old list */
newcnt = vocisect(list2, list1);
/* count the noun phrases in the new list */
for (i = cnt3 = 0 ; i < cnt2 ; ++i)
{
/* we have one more noun phrase */
++cnt3;
/* if we have a noun phrase, skip matching objs */
if (disnounlist[i].vocolfst != nullptr)
{
int j;
/* skip objects matching this noun phrase */
for (j = i + 1 ; disnounlist[i].vocolfst ==
disnounlist[j].vocolfst ; ++j) ;
i = j - 1;
}
}
/*
* If the count of items in the intersection of
* the original list and the typed-in list is no
* bigger than the number of items specified in
* the typed-in list, we've successfully
* disambiguated the object, because the user's
* new list matches only one object for each set
* of words the user typed.
*/
if (newcnt
&& (newcnt <= cnt3
|| (diswordlist[next]
&& vocspec(diswordlist[next],
VOCW_ONES))))
{
for (i = 0 ; i < cnt ; ++i)
vocout(&outlist[outpos++], list2[i], 0,
one_name, one_name);
if (noreach)
{
cnt = newcnt;
cantreach_list = list2;
noreach1:
if (accprop == PRP_VALIDACTOR)
{
/* for actors, show a special message */
vocerr(ctx, VOCERR(31),
"You can't talk to that.");
}
else
{
/* use the normal no-reach message */
vocnoreach(ctx, cantreach_list, cnt,
cmdActor, cmdVerb, cmdPrep,
defprop, cnt > 1, 0, 0, cnt);
}
err = VOCERR(31);
goto done;
}
break;
}
else if (newcnt == 0)
{
/*
* If we cleared the noun, maybe we actually
* need to treat the word as a noun, so add
* the noun flag back in and give it another
* go. If we didn't clear the noun, there's
* nothing left to try, so explain that we
* don't see any such object and give up.
*/
if (cleared_noun != -1)
{
distypelist[cleared_noun] |= VOCT_NOUN;
cleared_noun = -1;
goto try_current_flags;
}
/* find the first object with a noun phrase */
for (i = 0 ; i < cnt2 ; ++i)
{
/* if we have a noun phrase, stop scanning */
if (disnounlist[i].vocolfst != nullptr)
break;
}
/*
* if we found a noun phrase, build a string
* out of the words used; otherwise, just
* use "such"
*/
if (i != cnt2) {
const char *last;
/* clear the word buffer */
newobj[0] = '\0';
/* build a string out of the words */
p = disnounlist[i].vocolfst;
last = disnounlist[i].vocollst;
for ( ; p <= last ; p += strlen(p) + 1)
{
/*
* If this is a special word, we
* probably can't construct a
* sensible sentence - special words
* are special parts of speech that
* will look weird if inserted into
* our constructed noun phrase. In
* these cases, turn the entire
* thing into "I don't see any
* *such* object" rather than trying
* to make do with pronouns or other
* special words.
*/
if (vocisspec(p))
{
/*
* replace the entire adjective
* phrase with "such"
*/
Common::strcpy_s(newobj, VOCBUFSIZ, "such");
/*
* stop here - don't add any
* more, since "such" is the
* whole thing
*/
break;
}
/* add a space if we have a prior word */
if (newobj[0] != '\0')
Common::strcat_s(newobj, VOCBUFSIZ, " ");
/* add this word */
Common::strcat_s(newobj, VOCBUFSIZ, p);
}
}
else
{
/* no noun phrase found */
Common::strcpy_s(newobj, VOCBUFSIZ, "such");
}
/* didn't find anything - complain and give up */
vocerr(ctx, VOCERR(16),
"You don't see any %s %s here.",
newobj, usrobj);
err = VOCERR(16);
goto done;
}
/*
* If we get here, it means that we have still
* more than one object per noun phrase typed in
* the latest sentence. Limit the list to the
* intersection (by copying list2 to list1), and
* try again.
*/
memcpy(list1, list2,
(size_t)((newcnt + 1) * sizeof(list1[0])));
cnt = newcnt;
trying_again = TRUE;
}
else
{
/*
* We didn't find a noun phrase, so it's probably a
* new command. However, check first to see if we
* were making a trial run with the noun flag
* cleared: if so, go back and make another pass
* with the noun flag added back in to see if that
* works any better.
*/
if (cleared_noun != -1)
{
distypelist[cleared_noun] |= VOCT_NOUN;
cleared_noun = -1;
goto try_current_flags;
}
/* retry as an entire new command */
Common::strcpy_s(cmdbuf, cmdlen, disnewbuf);
ctx->voccxunknown = 0;
ctx->voccxredo = TRUE;
err = VOCERR(43);
goto done;
}
}
}
inpos = lpos - 1;
}
}
/* terminate the output list */
vocout(&outlist[outpos], MCMONINV, 0, (char *)nullptr, (char *)nullptr);
/*
* If we still have ambiguous objects, so indicate. This can only
* happen when we operate in "silent" mode, because only then can we
* give up without fully resolving a list.
*/
if (still_ambig)
err = VOCERR(44);
/* no error */
err = 0;
done:
ERRCLEAN(ctx->voccxerr)
{
/*
* reset the stack before we return, in case the caller handles
* the error without aborting the command
*/
voc_leave(ctx, save_sp);
}
ERRENDCLN(ctx->voccxerr);
/* return success */
VOC_RETVAL(ctx, save_sp, err);
}
/* vocready - see if at end of command, execute & return TRUE if so */
static int vocready(voccxdef *ctx, char *cmd[], int *typelist, int cur,
objnum cmdActor, objnum cmdPrep, char *vverb, char *vprep,
vocoldef *dolist, vocoldef *iolist, int *errp,
char *cmdbuf, size_t cmdlen, int first_word, uchar **preparse_list,
int *next_start)
{
if (cur != -1
&& (cmd[cur] == (char *)nullptr
|| vocspec(cmd[cur], VOCW_AND) || vocspec(cmd[cur], VOCW_THEN)))
{
if (ctx->voccxflg & VOCCXFDBG)
{
char buf[128];
Common::sprintf_s(buf, ". executing verb: %s %s\\n",
vverb, vprep ? vprep : "");
tioputs(ctx->vocxtio, buf);
}
*errp = execmd(ctx, cmdActor, cmdPrep, vverb, vprep, dolist, iolist,
&cmd[first_word], &typelist[first_word],cmdbuf,
cmdlen, cur - first_word, preparse_list, next_start);
return(TRUE);
}
return(FALSE);
}
/* execute a single command */
static int voc1cmd(voccxdef *ctx, char *cmd[], char *cmdbuf, size_t cmdlen,
objnum *cmdActorp, int first)
{
int cur;
int next;
objnum o;
vocwdef *v;
char *vverb;
int vvlen;
char *vprep;
int cnt;
int err;
vocoldef *dolist;
vocoldef *iolist;
int *typelist;
objnum cmdActor = *cmdActorp;
objnum cmdPrep;
int swapObj; /* TRUE -> swap dobj and iobj */
int again;
int first_word;
uchar *preparse_list = nullptr;
int next_start;
struct
{
int active;
int cur;
char **cmd;
char *cmdbuf;
} preparseCmd_stat;
char **newcmd;
char *origcmdbuf;
char *newcmdbuf;
uchar *save_sp;
int no_match;
int retval;
voc_enter(ctx, &save_sp);
VOC_MAX_ARRAY(ctx, vocoldef, dolist);
VOC_MAX_ARRAY(ctx, vocoldef, iolist);
VOC_STK_ARRAY(ctx, int, typelist, VOCBUFSIZ);
VOC_STK_ARRAY(ctx, char *, newcmd, VOCBUFSIZ);
VOC_STK_ARRAY(ctx, char, newcmdbuf, VOCBUFSIZ);
preparseCmd_stat.active = 0;
preparseCmd_stat.cur = 0;
preparseCmd_stat.cmd = nullptr;
preparseCmd_stat.cmdbuf = nullptr;
/* save the original command buf in case we need to redo the command */
origcmdbuf = cmdbuf;
/* clear out the type list */
memset(typelist, 0, VOCBUFSIZ*sizeof(typelist[0]));
/* get the types of the words in the command */
if (vocgtyp(ctx, cmd, typelist, cmdbuf, cmdlen))
{
retval = 1;
goto done;
}
/* start off at the first word */
cur = next = first_word = 0;
/*
* Presume we will be in control of the next word - when execmd() or
* another routine we call decides where the command ends, it will
* fill in a new value here. When this value is non-zero, it will
* tell us where the next sentence start is relative to the previous
* sentence start.
*/
next_start = 0;
/* we don't have a preparseCmd result yet */
preparseCmd_stat.active = FALSE;
/* keep going until we run out of work to do */
for (again = FALSE, err = 0 ; ; again = TRUE)
{
/*
* if preparseCmd sent us back a list, parse that list as a new
* command
*/
if (err == ERR_PREPRSCMDREDO)
{
uchar *src;
size_t len;
size_t curlen;
char *dst;
//int cnt;
/* don't allow a preparseCmd to loop */
if (preparseCmd_stat.active)
{
vocerr(ctx, VOCERR(34),
"Internal game error: preparseCmd loop");
retval = 1;
goto done;
}
/* save our status prior to processing the preparseCmd list */
preparseCmd_stat.active = TRUE;
preparseCmd_stat.cur = cur;
preparseCmd_stat.cmd = cmd;
preparseCmd_stat.cmdbuf = cmdbuf;
/* set up with the new command */
cmd = newcmd;
cmdbuf = newcmdbuf;
cur = 0;
/* break up the list into the new command buffer */
src = preparse_list;
len = osrp2(src) - 2;
for (src += 2, dst = cmdbuf, cnt = 0 ; len ; )
{
/* make sure the next element is a string */
if (*src != DAT_SSTRING)
{
vocerr(ctx, VOCERR(32),
"Internal game error: preparseCmd returned an invalid list");
retval = 1;
goto done;
}
/* get the string */
++src;
curlen = osrp2(src) - 2;
src += 2;
/* make sure it will fit in the buffer */
if (dst + curlen + 1 >= cmdbuf + VOCBUFSIZ)
{
vocerr(ctx, VOCERR(33),
"Internal game error: preparseCmd command too long");
retval = 1;
goto done;
}
/* store the word */
cmd[cnt++] = dst;
memcpy(dst, src, curlen);
dst[curlen] = '\0';
/* move on to the next word */
len -= 3 + curlen;
src += curlen;
dst += curlen + 1;
}
/* enter a null last word */
cmd[cnt] = nullptr;
/* generate the type list for the new list */
if (vocgtyp(ctx, cmd, typelist, cmdbuf, cmdlen))
{
/* return an error */
retval = 1;
goto done;
}
/*
* this is not a new command - it's just further processing
* of the current command
*/
again = FALSE;
/* clear the error */
err = 0;
}
/* initialize locals */
cmdPrep = MCMONINV; /* assume no preposition */
swapObj = FALSE; /* assume no object swapping */
dolist[0].vocolobj = iolist[0].vocolobj = MCMONINV;
dolist[0].vocolflg = iolist[0].vocolflg = 0;
/* check error return from vocready (which returns from execmd) */
if (err)
{
/* return the error */
retval = err;
goto done;
}
skip_leading_stuff:
/*
* If someone updated the sentence start point, jump there. The
* sentence start is relative to the previous sentence start.
*/
if (next_start != 0)
cur = first_word + next_start;
/* clear next_start, so we can tell if someone updates it */
next_start = 0;
/* skip any leading THEN's and AND's */
while (cmd[cur] && (vocspec(cmd[cur], VOCW_THEN)
|| vocspec(cmd[cur], VOCW_AND)))
++cur;
/* see if there's anything left to parse */
if (cmd[cur] == nullptr)
{
/*
* if we've been off doing preparseCmd work, return to the
* original command list
*/
if (preparseCmd_stat.active)
{
/* restore the original status */
cur = preparseCmd_stat.cur;
cmd = preparseCmd_stat.cmd;
cmdbuf = preparseCmd_stat.cmdbuf;
preparseCmd_stat.active = FALSE;
/* get the type list for the original list again */
if (vocgtyp(ctx, cmd, typelist, cmdbuf, cmdlen))
{
/* return the error */
retval = 1;
goto done;
}
/* try again */
goto skip_leading_stuff;
}
else
{
/* nothing to pop - we must be done */
retval = 0;
goto done;
}
}
/*
* display a blank line if this is not the first command on this
* command line, so that we visually separate the results of the
* new command from the results of the previous command
*/
if (again)
outformat("\\b"); /* tioblank(ctx->voccxtio); */
{
/* look for an explicit actor in the command */
if ((o = vocgetactor(ctx, cmd, typelist, cur, &next, cmdbuf, cmdlen))
!= MCMONINV)
{
/* skip the actor noun phrase in the input */
cur = next;
/* remember the actor internally */
cmdActor = *cmdActorp = o;
/* set the actor in the context */
ctx->voccxactor = o;
}
/* if the actor parsing failed, return an error */
if (cur != next)
{
/* error getting actor */
retval = 1;
goto done;
}
}
/* remember where the sentence starts */
first_word = cur;
/* make sure we have a verb */
if ((cmd[cur] == (char *)nullptr) || !(typelist[cur] & VOCT_VERB))
{
/* unknown verb - handle it with parseUnknownVerb if possible */
if (!try_unknown_verb(ctx, cmdActor, &cmd[cur], &typelist[cur],
0, &next_start, TRUE, VOCERR(17),
"There's no verb in that sentence!"))
{
/* error - abort the command */
retval = 1;
goto done;
}
else
{
/* go back for more */
continue;
}
}
vverb = cmd[cur++]; /* this is the verb */
vvlen = strlen(vverb); /* remember length of verb */
vprep = nullptr; /* assume no verb-preposition */
/* execute if the command is just a verb */
if (vocready(ctx, cmd, typelist, cur, cmdActor, cmdPrep,
vverb, vprep, dolist, iolist, &err, cmdbuf,
cmdlen, first_word, &preparse_list, &next_start))
continue;
/*
* If the next word is a preposition, and it makes sense to be
* aggregated with this verb, use it as such.
*/
if (typelist[cur] & VOCT_PREP)
{
if (vocffw(ctx, vverb, vvlen, cmd[cur], (int)strlen(cmd[cur]),
PRP_VERB, (vocseadef *)nullptr))
{
vprep = cmd[cur++];
if (vocready(ctx, cmd, typelist, cur, cmdActor, cmdPrep,
vverb, vprep, dolist, iolist, &err, cmdbuf,
cmdlen, first_word, &preparse_list, &next_start))
continue;
}
else
{
/*
* If we have a preposition which can NOT be aggregated
* with the verb, take command of this form: "verb prep
* iobj dobj". Note that we do *not* do this if the
* word is also a noun, or it's an adjective and a noun
* (possibly separated by one or more adjectives)
* follows.
*/
if ((v = vocffw(ctx, cmd[cur], (int)strlen(cmd[cur]),
(char *)nullptr, 0, PRP_PREP, (vocseadef *)nullptr)) != nullptr)
{
int swap_ok;
/* if it can be an adjective, check further */
if (typelist[cur] & VOCT_NOUN)
{
/* don't allow the swap */
swap_ok = FALSE;
}
else if (typelist[cur] & VOCT_ADJ)
{
int i;
/* look for a noun, possibly preceded by adj's */
for (i = cur + 1 ;
cmd[i] && (typelist[i] & VOCT_ADJ)
&& !(typelist[i] & VOCT_NOUN) ; ++i) ;
swap_ok = (!cmd[i] || !(typelist[i] & VOCT_NOUN));
}
else
{
/* we can definitely allow this swap */
swap_ok = TRUE;
}
if (swap_ok)
{
cmdPrep = v->vocwobj;
swapObj = TRUE;
++cur;
}
}
}
}
retry_swap:
/* get the direct object if there is one */
if ((cnt = vocchknoun2(ctx, cmd, typelist, cur, &next, dolist,
FALSE, &no_match)) > 0)
{
/* we found a noun phrase matching one or more objects */
cur = next;
}
else if (no_match)
{
/*
* we found a valid noun phrase, but we didn't find any
* objects that matched the words -- get the noun again,
* this time showing the error
*/
vocgetnoun(ctx, cmd, typelist, cur, &next, dolist);
/* return the error */
retval = 1;
goto done;
}
else if (cnt < 0)
{
/* invalid syntax - return failure */
retval = 1;
goto done;
}
else
{
/*
* If we thought we were going to get a two-object
* sentence, and we got a zero-object sentence, and it looks
* like the word we took as a preposition is also an
* adjective or noun, go back and treat it as such.
*/
if (swapObj &&
((typelist[cur-1] & VOCT_NOUN)
|| (typelist[cur-1] & VOCT_ADJ)))
{
--cur;
swapObj = FALSE;
cmdPrep = MCMONINV;
goto retry_swap;
}
bad_sentence:
/* find the last word */
while (cmd[cur]) ++cur;
/* try running the sentence through preparseCmd */
err = try_preparse_cmd(ctx, &cmd[first_word], cur - first_word,
&preparse_list);
switch(err)
{
case 0:
/* preparseCmd didn't do anything - the sentence fails */
if (!try_unknown_verb(ctx, cmdActor, &cmd[first_word],
&typelist[first_word], 0, &next_start,
TRUE, VOCERR(18),
"I don't understand that sentence."))
{
/* error - abort the command */
retval = 1;
goto done;
}
else
{
/* success - go back for more */
continue;
}
case ERR_PREPRSCMDCAN:
/* they cancelled - we're done with the sentence */
retval = 0;
goto done;
case ERR_PREPRSCMDREDO:
/* reparse with the new sentence */
continue;
}
}
/* see if we want to execute the command now */
if (vocready(ctx, cmd, typelist, cur, cmdActor, cmdPrep,
vverb, vprep,
swapObj ? iolist : dolist,
swapObj ? dolist : iolist,
&err, cmdbuf, cmdlen, first_word, &preparse_list,
&next_start))
continue;
/*
* Check for an indirect object, which may or may not be preceded
* by a preposition. (Note that the lack of a preposition implies
* that the object we already found is the indirect object, and the
* next object is the direct object. It also implies a preposition
* of "to.")
*/
if (cmdPrep == MCMONINV && (typelist[cur] & VOCT_PREP))
{
char *p1 = cmd[cur++];
/* if this is the end of the sentence, add the prep to the verb */
if (cmd[cur] == (char *)nullptr
|| vocspec(cmd[cur], VOCW_AND)
|| vocspec(cmd[cur], VOCW_THEN))
{
if (vocffw(ctx, vverb, vvlen, p1, (int)strlen(p1), PRP_VERB,
(vocseadef *)nullptr)
&& !vprep)
vprep = p1;
else
{
/* call parseUnknownVerb */
if (!try_unknown_verb(ctx, cmdActor, &cmd[first_word],
&typelist[first_word], 0,
&next_start, TRUE, VOCERR(19),
"There are words after your command I couldn't use."))
{
/* error - abandon the command */
retval = 1;
goto done;
}
else
{
/* go back for more */
continue;
}
}
if ((err = execmd(ctx, cmdActor, cmdPrep, vverb, vprep,
dolist, iolist,
&cmd[first_word], &typelist[first_word],
cmdbuf, cmdlen, cur - first_word,
&preparse_list, &next_start)) != 0)
{
retval = 1;
goto done;
}
continue;
}
/*
* If we have no verb preposition already, and we have
* another prep-capable word following this prep-capable
* word, and this preposition aggregates with the verb, take
* it as a sentence of the form "pry box open with crowbar"
* (where the current word is "with"). We also must have at
* least one more word after that, since there will have to
* be an indirect object.
*/
if (cmd[cur] && (typelist[cur] & VOCT_PREP) && cmd[cur+1]
&& vprep == nullptr
&& vocffw(ctx, vverb, vvlen, p1, (int)strlen(p1), PRP_VERB,
(vocseadef *)nullptr))
{
/*
* check to make sure that the next word, which we're
* about to take for a prep (the "with" in "pry box open
* with crowbar") is actually not part of an object name
* - if it is, use it as the object name rather than as
* the prep
*/
if (vocgobj(ctx, cmd, typelist, cur, &next,
FALSE, iolist, FALSE, FALSE, nullptr) <= 0)
{
/* aggregate the first preposition into the verb */
vprep = p1;
/* use the current word as the object-introducing prep */
p1 = cmd[cur++];
}
}
/* try for an indirect object */
cnt = vocgobj(ctx, cmd, typelist, cur, &next, TRUE, iolist,
TRUE, FALSE, &no_match);
if (cnt > 0)
{
cur = next;
v = vocffw(ctx, p1, (int)strlen(p1), (char *)nullptr, 0, PRP_PREP,
(vocseadef *)nullptr);
if (v == (vocwdef *)nullptr)
{
/* let parseUnknownVerb handle it */
if (!try_unknown_verb(ctx, cmdActor, &cmd[first_word],
&typelist[first_word], 0,
&next_start, TRUE, VOCERR(20),
"I don't know how to use the word \"%s\" like that.", p1))
{
/* error - abort the command */
retval = 1;
goto done;
}
else
{
/* go on to the next command */
continue;
}
}
cmdPrep = v->vocwobj;
if (vocready(ctx, cmd, typelist, cur, cmdActor, cmdPrep,
vverb, vprep, dolist, iolist, &err, cmdbuf,
cmdlen, first_word, &preparse_list, &next_start))
continue;
else if ((typelist[cur] & VOCT_PREP) &&
vocffw(ctx, vverb, vvlen, cmd[cur],
(int)strlen(cmd[cur]), PRP_VERB,
(vocseadef *)nullptr) && !vprep)
{
vprep = cmd[cur++];
if (vocready(ctx, cmd, typelist, cur, cmdActor,
cmdPrep, vverb, vprep, dolist, iolist,
&err, cmdbuf, cmdlen, first_word, &preparse_list,
&next_start))
continue;
else
{
/* let parseUnknownVerb handle it */
if (!try_unknown_verb(ctx, cmdActor, &cmd[first_word],
&typelist[first_word], 0,
&next_start, TRUE, VOCERR(19),
"There are words after your command I couldn't use."))
{
/* error - abandon the command */
retval = 1;
goto done;
}
else
{
/* go on to the next command */
continue;
}
}
}
else
{
/*
* If the latter object phrase is flagged with the
* "trimmed preposition" flag, meaning that we could
* have used the preposition in the noun phrase but
* assumed instead it was part of the verb, reverse
* this assumption now: add the preposition back to the
* noun phrase and explain that there's no such thing
* present.
*
* Otherwise, we simply have an unknown verb phrasing,
* so let parseUnknownVerb handle it.
*/
vocoldef *np1 =
(dolist[0].vocolflg & VOCS_TRIMPREP) != 0
? &dolist[0]
: (iolist[0].vocolflg & VOCS_TRIMPREP) != 0
? &iolist[0]
: nullptr;
if (np1 != nullptr)
{
char namebuf[VOCBUFSIZ];
/* show the name, adding the prep back in */
voc_make_obj_name_from_list(
ctx, namebuf, cmd, np1->vocolfst, np1->vocolhlst);
vocerr(ctx, VOCERR(9), "I don't see any %s here.",
namebuf);
/* error - abort */
retval = 1;
goto done;
}
else if (!try_unknown_verb(
ctx, cmdActor,
&cmd[first_word], &typelist[first_word],
0, &next_start, TRUE, VOCERR(19),
"There are words after your command that I couldn't use."))
{
/* error - abort */
retval = 1;
goto done;
}
else
{
/* continue with the next command */
continue;
}
}
}
else if (cnt < 0)
{
/*
* the noun phrase syntax was invalid - we've already
* displayed an error about it, so simply return failure
*/
retval = 1;
goto done;
}
else if (no_match)
{
/*
* we found a valid noun phrase, but we didn't find any
* matching objects - we've already generated an error,
* so simply return failure
*/
retval = 1;
goto done;
}
else
{
goto bad_sentence;
}
}
else if ((cnt = vocchknoun(ctx, cmd, typelist, cur,
&next, iolist, FALSE)) > 0)
{
/* look for prep at end of command */
cur = next;
if (cmd[cur])
{
if ((typelist[cur] & VOCT_PREP) &&
vocffw(ctx, vverb, vvlen, cmd[cur],
(int)strlen(cmd[cur]), PRP_VERB,
(vocseadef *)nullptr) && !vprep)
{
vprep = cmd[cur++];
}
}
/* the command should definitely be done now */
if (cmd[cur] != nullptr)
{
/* let parseUnknownVerb handle it */
if (!try_unknown_verb(ctx, cmdActor, &cmd[first_word],
&typelist[first_word], 0,
&next_start, TRUE, VOCERR(21),
"There appear to be extra words after your command."))
{
/* error - stop the command */
retval = 1;
goto done;
}
else
{
/* go on to the next command */
continue;
}
}
/*
* If we don't have a preposition yet, we need to find the
* verb's default. If the verb object has a nilPrep
* property defined, use that prep object; otherwise, look
* up the word "to" and use that.
*/
if (cmdPrep == MCMONINV &&
(v = vocffw(ctx, vverb, vvlen,
vprep, (vprep ? (int)strlen(vprep) : 0),
PRP_VERB, (vocseadef *)nullptr)) != nullptr)
{
runppr(ctx->voccxrun, v->vocwobj, PRP_NILPREP, 0);
if (runtostyp(ctx->voccxrun) == DAT_OBJECT)
cmdPrep = runpopobj(ctx->voccxrun);
else
rundisc(ctx->voccxrun);
}
/* if we didn't find anything with nilPrep, find "to" */
if (cmdPrep == MCMONINV)
{
v = vocffw(ctx, "to", 2, (char *)nullptr, 0, PRP_PREP,
(vocseadef *)nullptr);
if (v) cmdPrep = v->vocwobj;
}
/* execute the command */
err = execmd(ctx, cmdActor, cmdPrep, vverb, vprep,
iolist, dolist,
&cmd[first_word], &typelist[first_word], cmdbuf,
cmdlen, cur - first_word, &preparse_list, &next_start);
continue;
}
else if (cnt < 0)
{
retval = 1;
goto done;
}
else
{
goto bad_sentence;
}
}
done:
/* copy back the command if we need to redo */
if (ctx->voccxredo && cmdbuf != origcmdbuf)
Common::strcpy_s(origcmdbuf, cmdlen, cmdbuf);
/* return the status */
VOC_RETVAL(ctx, save_sp, retval);
}
/* execute a player command */
int voccmd(voccxdef *ctx, char *cmd, uint cmdlen)
{
int wrdcnt;
int cur = 0;
int next;
char *buffer;
char **wordlist;
objnum cmdActor;
int first;
/*
* Make sure the stack is set up, resetting the stack on entry. Note
* that this makes this routine non-reentrant - recursively calling
* this routine will wipe out the enclosing caller's stack.
*/
voc_stk_ini(ctx, (uint)VOC_STACK_SIZE);
/* allocate our stack arrays */
VOC_STK_ARRAY(ctx, char, buffer, 2*VOCBUFSIZ);
VOC_STK_ARRAY(ctx, char *, wordlist, VOCBUFSIZ);
/* until further notice, the actor for formatStrings is Me */
tiosetactor(ctx->voccxtio, ctx->voccxme);
/* clear the 'ignore' flag */
ctx->voccxflg &= ~VOCCXFCLEAR;
/* send to game function 'preparse' */
if (ctx->voccxpre != MCMONINV)
{
int typ;
uchar *s;
size_t len;
int err;
/* push the argument string */
runpstr(ctx->voccxrun, cmd, (int)strlen(cmd), 0);
/* presume no error will occur */
err = 0;
/* catch errors that occur during preparse() */
ERRBEGIN(ctx->voccxerr)
{
/* call preparse() */
runfn(ctx->voccxrun, ctx->voccxpre, 1);
}
ERRCATCH(ctx->voccxerr, err)
{
/* see what we have */
switch(err)
{
case ERR_RUNEXIT:
case ERR_RUNEXITOBJ:
case ERR_RUNABRT:
/*
* if we encountered abort, exit, or exitobj, treat it
* the same as a nil result code - simply cancel the
* entire current command
*/
break;
default:
/* re-throw any other error */
errrse(ctx->voccxerr);
}
}
ERREND(ctx->voccxerr);
/* if an error occurred, abort the command */
if (err != 0)
return 0;
/* check the return value's type */
typ = runtostyp(ctx->voccxrun);
if (typ == DAT_SSTRING)
{
/*
* It's a string - replace the command with the new string.
* Pop the new string and scan its length prefix.
*/
s = runpopstr(ctx->voccxrun);
len = osrp2(s) - 2;
s += 2;
/*
* limit the size of the command to our buffer length,
* leaving space for null termination
*/
if (len > cmdlen - 1)
len = cmdlen - 1;
/* copy the new command string into our command buffer */
memcpy(cmd, s, len);
/* null-terminate the command buffer */
cmd[len] = '\0';
}
else
{
/* the value isn't important for any other type */
rundisc(ctx->voccxrun);
/* if they returned nil, we're done processing the command */
if (typ == DAT_NIL)
return 0;
}
}
/* break up into individual words */
if ((wrdcnt = voctok(ctx, cmd, buffer, wordlist, TRUE, FALSE, TRUE)) > 0)
{
/* skip any leading "and" and "then" separators */
for (cur = 0 ; cur < wrdcnt ; ++cur)
{
/* if this isn't "and" or "then", we're done scanning */
if (!vocspec(wordlist[cur], VOCW_THEN)
&& !vocspec(wordlist[cur], VOCW_AND))
break;
}
}
/*
* if we found no words, or we found only useless leading "and" and
* "then" separators, run the "pardon" function to tell the player
* that we didn't find any command on the line
*/
if (wrdcnt == 0 || (wrdcnt > 0 && cur >= wrdcnt))
{
runfn(ctx->voccxrun, ctx->voccxprd, 0);
return 0;
}
/*
* if we got an error tokenizing the word list, return - we've
* already displayed an error message, so there's nothing more for
* us to do here
*/
if (wrdcnt < 0)
return 0;
/* process the commands on the line */
for (first = TRUE, cmdActor = ctx->voccxactor = MCMONINV ; cur < wrdcnt ;
++cur, first = FALSE)
{
/* presume we won't find an unknown word */
ctx->voccxunknown = 0;
/* find the THEN that ends the command, if there is one */
for (next = cur ; cur < wrdcnt && !vocspec(wordlist[cur], VOCW_THEN)
; ++cur) ;
wordlist[cur] = (char *)nullptr;
/* process until we run out of work to do */
for (;;)
{
/* try processing the command */
if (voc1cmd(ctx, &wordlist[next], cmd, cmdlen, &cmdActor, first))
{
/*
* If the unknown word flag is set, try the command
* again from the beginning. This flag means that we
* tried using parseUnknownDobj/Iobj to resolve the
* unknown word, but that failed and we need to try
* again with the normal "oops" processing.
*/
if (ctx->voccxunknown > 0)
continue;
/* return an error */
return 1;
}
/* success - we're done with the command */
break;
}
/* if the rest of the command is to be ignored, we're done */
if (ctx->voccxflg & VOCCXFCLEAR)
return 0;
/* scan past any separating AND's and THEN's */
while (cur + 1 < wrdcnt
&& (vocspec(wordlist[cur+1], VOCW_THEN)
|| vocspec(wordlist[cur+1], VOCW_AND)))
++cur;
/*
* if another command follows, add a blank line to separate the
* results from the previous command and those from the next
* command
*/
if (cur + 1 < wrdcnt)
outformat("\\b");
}
/* done */
return 0;
}
/*
* Off-stack stack management
*/
/* allocate/reset the stack */
void voc_stk_ini(voccxdef *ctx, uint siz)
{
/* allocate it if it's not already allocated */
if (ctx->voc_stk_ptr == nullptr)
{
ctx->voc_stk_ptr = mchalo(ctx->voccxerr, siz, "voc_stk_ini");
ctx->voc_stk_end = ctx->voc_stk_ptr + siz;
}
/* reset the stack */
ctx->voc_stk_cur = ctx->voc_stk_ptr;
}
/* allocate space from the off-stack stack */
void *voc_stk_alo(voccxdef *ctx, uint siz)
{
void *ret;
/* round size up to allocation units */
siz = osrndsz(siz);
/* if there's not space, signal an error */
if (ctx->voc_stk_cur + siz > ctx->voc_stk_end)
errsig(ctx->voccxerr, ERR_VOCSTK);
/* save the return pointer */
ret = ctx->voc_stk_cur;
/* consume the space */
ctx->voc_stk_cur += siz;
/*#define SHOW_HI*/
#ifdef SHOW_HI
{
static uint maxsiz;
if (ctx->voc_stk_cur - ctx->voc_stk_ptr > maxsiz)
{
char buf[20];
maxsiz = ctx->voc_stk_cur - ctx->voc_stk_ptr;
Common::sprintf_s(buf, "%u\n", maxsiz);
os_printz(buf);
}
}
#endif
/* return the space */
return ret;
}
} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk