8160 lines
210 KiB
C++
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
|