aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Gilbert2019-05-17 11:02:01 -1000
committerPaul Gilbert2019-05-24 18:21:06 -0700
commite83972f502142b4e6191b962a7be89829bd8d708 (patch)
treeebb795b1f58f7f8c2acc8cf7e19c3ea7fb2e0077
parent0279143a62c2cdab635894103da03b43eba7cf9c (diff)
downloadscummvm-rg350-e83972f502142b4e6191b962a7be89829bd8d708.tar.gz
scummvm-rg350-e83972f502142b4e6191b962a7be89829bd8d708.tar.bz2
scummvm-rg350-e83972f502142b4e6191b962a7be89829bd8d708.zip
GLK: TADS2: Added code for tokenizer & vocabulary
-rw-r--r--engines/glk/module.mk1
-rw-r--r--engines/glk/tads/osfrobtads.cpp14
-rw-r--r--engines/glk/tads/osfrobtads.h24
-rw-r--r--engines/glk/tads/tads2/file_io.h19
-rw-r--r--engines/glk/tads/tads2/lib.h2
-rw-r--r--engines/glk/tads/tads2/line_source_file.cpp33
-rw-r--r--engines/glk/tads/tads2/line_source_file.h170
-rw-r--r--engines/glk/tads/tads2/tads2.cpp3
-rw-r--r--engines/glk/tads/tads2/tokenizer.cpp1526
-rw-r--r--engines/glk/tads/tads2/tokenizer.h11
-rw-r--r--engines/glk/tads/tads2/vocabulary.cpp902
-rw-r--r--engines/glk/tads/tads2/vocabulary.h11
12 files changed, 2685 insertions, 31 deletions
diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index 43c2523b80..9f95fc0cb6 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -99,6 +99,7 @@ MODULE_OBJS := \
tads/tads2/error.o \
tads/tads2/error_handling.o \
tads/tads2/file_io.o \
+ tads/tads2/line_source_file.o \
tads/tads2/memory_cache.o \
tads/tads2/memory_cache_heap.o \
tads/tads2/memory_cache_loader.o \
diff --git a/engines/glk/tads/osfrobtads.cpp b/engines/glk/tads/osfrobtads.cpp
index fbcf1c1050..13229e5ae2 100644
--- a/engines/glk/tads/osfrobtads.cpp
+++ b/engines/glk/tads/osfrobtads.cpp
@@ -34,8 +34,20 @@ osfildef *osfoprb(const char *fname, os_filetype_t typ) {
return nullptr;
}
+osfildef *osfoprwtb(const char *fname, os_filetype_t typ) {
+ Common::DumpFile *df = new Common::DumpFile();
+ if (df->open(fname))
+ return df;
+ delete df;
+ return nullptr;
+}
+
int osfrb(osfildef *fp, void *buf, size_t count) {
- return fp->read(buf, count);
+ return dynamic_cast<Common::ReadStream *>(fp)->read(buf, count);
+}
+
+bool osfwb(osfildef *fp, void *buf, size_t count) {
+ return dynamic_cast<Common::WriteStream *>(fp)->write(buf, count) != count;
}
} // End of namespace TADS
diff --git a/engines/glk/tads/osfrobtads.h b/engines/glk/tads/osfrobtads.h
index 2dce84c5ec..b22dbaecac 100644
--- a/engines/glk/tads/osfrobtads.h
+++ b/engines/glk/tads/osfrobtads.h
@@ -38,6 +38,12 @@
namespace Glk {
namespace TADS {
+#define OSPATHCHAR '/'
+#define OSPATHALT ""
+#define OSPATHURL "/"
+#define OSPATHSEP ':'
+#define OS_NEWLINE_SEQ "\n"
+
/* Defined for Gargoyle. */
#define HAVE_STDINT_
@@ -92,8 +98,12 @@ namespace TADS {
#define OSFNMAX 255
-/* File handle structure for osfxxx functions. */
-typedef Common::SeekableReadStream osfildef;
+/**
+ * File handle structure for osfxxx functions
+ * Note that we need to define it as a Common::Stream since the type is used by
+ * TADS for both reading and writing files
+ */
+typedef Common::Stream osfildef;
/* Directory handle for searches via os_open_dir() et al. */
typedef Common::FSNode *osdirhdl_t;
@@ -188,18 +198,18 @@ osfildef *osfoprwt(const char *fname, os_filetype_t typ);
#define osfoprs osfoprt
/* Open binary file for reading. */
-osfildef *osfoprb(const char *fname, os_filetype_t typ);
+inline osfildef *osfoprb(const char *fname, os_filetype_t typ);
/* Open binary file for reading/writing. If the file already exists,
* keep the existing contents. Create a new file if it doesn't already
* exist. */
osfildef*
-osfoprwb( const char* fname, os_filetype_t typ );
+osfoprwb(const char *fname, os_filetype_t typ);
-/* Open binary file for reading/writing. If the file already exists,
+/* Open binary file for writing. If the file already exists,
* truncate the existing contents. Create a new file if it doesn't
* already exist. */
-#define osfoprwtb(fname,typ) (fopen((fname),"w+b"))
+inline osfildef *osfoprwtb(const char *fname, os_filetype_t typ);
/* Get a line of text from a text file. */
#define osfgets fgets
@@ -208,7 +218,7 @@ osfoprwb( const char* fname, os_filetype_t typ );
#define osfputs fputs
/* Write bytes to file. */
-#define osfwb(fp,buf,bufl) (fwrite((buf),(bufl),1,(fp))!=1)
+inline bool osfwb(osfildef *fp, void *buf, size_t count);
/* Flush buffered writes to a file. */
#define osfflush fflush
diff --git a/engines/glk/tads/tads2/file_io.h b/engines/glk/tads/tads2/file_io.h
index 8599c4909f..0edfbaee6d 100644
--- a/engines/glk/tads/tads2/file_io.h
+++ b/engines/glk/tads/tads2/file_io.h
@@ -28,6 +28,8 @@
#define GLK_TADS_TADS2_FILE_IO
#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/memory_cache_loader.h"
+#include "glk/tads/tads2/object.h"
namespace Glk {
namespace TADS {
@@ -35,6 +37,9 @@ namespace TADS2 {
/* forward declarations */
struct voccxdef;
+struct tokpdef;
+struct tokthdef;
+struct tokcxdef;
/* load-on-demand context (passed in by mcm in load callback) */
typedef struct fiolcxdef fiolcxdef;
@@ -65,20 +70,16 @@ void fiowrt(struct mcmcxdef *mctx, voccxdef *vctx,
#define FIOFLIN2 0x80 /* new-style line records */
/* read game from binary file; sets up loader callback context */
-void fiord(struct mcmcxdef *mctx, voccxdef *vctx,
- struct tokcxdef *tctx,
- char *fname, char *exename,
- struct fiolcxdef *setupctx, objnum *preinit, uint *flagp,
- struct tokpdef *path, uchar **fmtsp, uint *fmtlp,
- uint *pcntptr, int flags, struct appctxdef *appctx,
- char *argv0);
+void fiord(mcmcxdef *mctx, voccxdef *vctx, tokcxdef *tctx, char *fname,
+ char *exename, fiolcxdef *setupctx, objnum *preinit, uint *flagp,
+ tokpdef *path, uchar **fmtsp, uint *fmtlp, uint *pcntptr, int flags,
+ appctxdef *appctx, char *argv0);
/* shut down load-on-demand subsystem, close load file */
void fiorcls(fiolcxdef *ctx);
/* loader callback - load an object on demand */
-void OS_LOADDS fioldobj(void *ctx, mclhd handle, uchar *ptr,
- ushort siz);
+void OS_LOADDS fioldobj(void *ctx, mclhd handle, uchar *ptr, ushort siz);
/*
* Save a game - returns TRUE on failure. We'll save the file to
diff --git a/engines/glk/tads/tads2/lib.h b/engines/glk/tads/tads2/lib.h
index 605f072390..095abfc69f 100644
--- a/engines/glk/tads/tads2/lib.h
+++ b/engines/glk/tads/tads2/lib.h
@@ -134,7 +134,7 @@ void varused();
* anything outside of the normal ASCII set as spaces.
*/
#define t_isspace(c) \
- (((unsigned char)(c)) <= 127 && isspace((unsigned char)(c)))
+ (((unsigned char)(c)) <= 127 && Common::isSpace((unsigned char)(c)))
/* round a size to worst-case alignment boundary */
diff --git a/engines/glk/tads/tads2/line_source_file.cpp b/engines/glk/tads/tads2/line_source_file.cpp
new file mode 100644
index 0000000000..26536c06a4
--- /dev/null
+++ b/engines/glk/tads/tads2/line_source_file.cpp
@@ -0,0 +1,33 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/tads2/line_source_file.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/line_source_file.h b/engines/glk/tads/tads2/line_source_file.h
new file mode 100644
index 0000000000..a8b90d87c9
--- /dev/null
+++ b/engines/glk/tads/tads2/line_source_file.h
@@ -0,0 +1,170 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef GLK_TADS_TADS2_LINE_SOURCE_FILE
+#define GLK_TADS_TADS2_LINE_SOURCE_FILE
+
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/debug.h"
+#include "glk/tads/tads2/line_source.h"
+#include "glk/tads/tads2/object.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+struct tokpdef;
+
+/* maximum number of pages of debugging records we can keep */
+#define LINFPGMAX 128
+
+/*
+ * executable line information structure: this record relates one
+ * executable line to the object containing the p-code, and the offset
+ * in the object of the p-code for the start of the line
+ */
+struct linfinfo {
+ /*
+ * OPCLINE data (file seek position or line number, depending on how
+ * the game was compiled: -ds -> file seek offset, -ds2 -> line
+ * number)
+ */
+ ulong fpos;
+
+ /* object number */
+ objnum objn;
+
+ /* offset from start of code */
+ uint ofs;
+};
+
+/*
+ * file line source
+ */
+struct linfdef {
+ lindef linflin; /* superclass data */
+ osfildef *linffp; /* file pointer for this line source */
+ char linfbuf[100]; /* buffer for the line contents */
+ int linfbufnxt; /* offset in buffer of start of next line */
+ int linfnxtlen; /* length of data after linfbufnxt */
+ ulong linfnum; /* current line number */
+ ulong linfseek; /* seek position of current line */
+ mcmcxdef *linfmem; /* memory manager context */
+ mcmon linfpg[LINFPGMAX]; /* pages for debugging records */
+ ulong linfcrec; /* number of debugger records written so far */
+ char linfnam[1]; /* name of file being read */
+};
+
+/* initialize a file line source, opening the file for the line source */
+linfdef *linfini(mcmcxdef *mctx, errcxdef *errctx, char *filename,
+ int flen, tokpdef *path, int must_find_file,
+ int new_line_records);
+
+/* initialize a pre-allocated linfdef, skipping debugger page setup */
+void linfini2(mcmcxdef *mctx, linfdef *linf,
+ char *filename, int flen, osfildef *fp, int new_line_records);
+
+/* get next line from line source */
+int linfget(lindef *lin);
+
+/* generate printable rep of current position in source (for errors) */
+void linfppos(lindef *lin, char *buf, uint bufl);
+
+/* close line source */
+void linfcls(lindef *lin);
+
+/* generate source-line debug instruction operand */
+void linfglop(lindef *lin, uchar *buf);
+
+/* generate new-style source-line debug instructino operand */
+void linfglop2(lindef *lin, uchar *buf);
+
+/* save line source to binary (.gam) file */
+int linfwrt(lindef *lin, osfildef *fp);
+
+/* load a file-line-source from binary (.gam) file */
+int linfload(osfildef *fp, dbgcxdef *dbgctx, errcxdef *ec,
+ tokpdef *path);
+
+/* add a debugger line record for the current line */
+void linfcmp(lindef *lin, uchar *buf);
+
+/* find nearest line record to a file seek location */
+void linffind(lindef *lin, char *buf, objnum *objp, uint *ofsp);
+
+/* activate line source for debugging */
+void linfact(lindef *lin);
+
+/* disactivate line source */
+void linfdis(lindef *lin);
+
+/* get current seek position */
+void linftell(lindef *lin, uchar *pos);
+
+/* seek */
+void linfseek(lindef *lin, uchar *pos);
+
+/* read */
+int linfread(lindef *lin, uchar *buf, uint siz);
+
+/* add a signed delta to a seek positon */
+void linfpadd(lindef *lin, uchar *pos, long delta);
+
+/* query whether at top of file */
+int linfqtop(lindef *lin, uchar *pos);
+
+/* read one line at current seek position */
+int linfgets(lindef *lin, uchar *buf, uint bufsiz);
+
+/* get name of line source */
+void linfnam(lindef *lin, char *buf);
+
+/* get the current line number */
+ulong linflnum(lindef *lin);
+
+/* go to top or bottom */
+void linfgoto(lindef *lin, int where);
+
+/* return the current offset in the line source */
+long linfofs(lindef *lin);
+
+/* renumber an object */
+void linfren(lindef *lin, objnum oldnum, objnum newnum);
+
+/* delete an object */
+void linfdelnum(lindef *lin, objnum objn);
+
+/* copy line records to an array of linfinfo structures */
+void linf_copy_linerecs(linfdef *linf, linfinfo *info);
+
+/* debugging echo */
+#ifdef DEBUG
+# define LINFDEBUG(x) x
+#else /* DEBUG */
+# define LINFDEBUG(x)
+#endif /* DEBUG */
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/tads2.cpp b/engines/glk/tads/tads2/tads2.cpp
index b391857613..e1f59212b0 100644
--- a/engines/glk/tads/tads2/tads2.cpp
+++ b/engines/glk/tads/tads2/tads2.cpp
@@ -56,6 +56,7 @@ void TADS2::runGame() {
}
void TADS2::trdmain1(errcxdef *errctx) {
+#ifdef TODO
osfildef *swapfp = (osfildef *)0;
runcxdef runctx;
bifcxdef bifctx;
@@ -99,7 +100,7 @@ void TADS2::trdmain1(errcxdef *errctx) {
char *charmap = 0; /* character map file */
int charmap_none; /* explicitly do not use a character set */
int doublespace = TRUE; /* formatter double-space setting */
-#ifdef TODO
+
NOREG((&loadopen))
/* initialize the output formatter */
diff --git a/engines/glk/tads/tads2/tokenizer.cpp b/engines/glk/tads/tads2/tokenizer.cpp
index 59dceebaf6..96bd2f18fb 100644
--- a/engines/glk/tads/tads2/tokenizer.cpp
+++ b/engines/glk/tads/tads2/tokenizer.cpp
@@ -21,12 +21,1536 @@
*/
#include "glk/tads/tads2/tokenizer.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/memory_cache_heap.h"
+#include "glk/tads/tads2/os.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
-// TODO: Rest of tokenizer stuff
+
+/* special temporary buffers for <<expr>> macro expansion */
+static char tokmac1[] = ",say((";
+static char tokmac1s[] = "(say((";
+static char tokmac2[] = "),nil),\"";
+static char tokmac3[] = "),nil))";
+static char tokmac4[] = ")";
+
+/* forward definition of static functions */
+static int tokdfhsh(char *sym, int len);
+
+
+/* find a #define symbol */
+static tokdfdef *tok_find_define(tokcxdef *ctx, char *sym, int len)
+{
+ int hsh;
+ tokdfdef *df;
+
+ /* find the appropriate chain the hash table */
+ hsh = tokdfhsh(sym, len);
+
+ /* search the chain for this symbol */
+ for (df = ctx->tokcxdf[hsh] ; df ; df = df->nxt)
+ {
+ /* if this one matches, return it */
+ if (df->len == len && !memcmp(df->nm, sym, (size_t)len))
+ {
+ /* fix it up if it's the special __FILE__ or __LINE__ symbol */
+ if (len == 8)
+ {
+ if (!memcmp(sym, "__FILE__", (size_t)8))
+ {
+ size_t elen;
+
+ /*
+ * put in the opening single quote, since we want
+ * the expanded result to be a string
+ */
+ df->expan[0] = '\'';
+
+ /* get the name */
+ linnam(ctx->tokcxlin, df->expan+1);
+
+ /* get the length, and add the closing quote */
+ elen = strlen(df->expan);
+ df->expan[elen] = '\'';
+
+ /*
+ * set the length of the expansion, including the
+ * quotes (the first quote was measured in the
+ * length originally, but the second quote hasn't
+ * been counted yet, so add one to our original
+ * length)
+ */
+ df->explen = (int)elen + 1;
+
+ /* if the expansion is too long, it's an error */
+ if (df->explen >= OSFNMAX)
+ errsig(ctx->tokcxerr, ERR_LONG_FILE_MACRO);
+ }
+ else if (!memcmp(sym, "__LINE__", (size_t)8))
+ {
+ ulong l;
+
+ /* get the line number */
+ l = linlnum(ctx->tokcxlin);
+
+ /* convert it to a textual format for the expansion */
+ sprintf(df->expan, "%lu", l);
+
+ /* set the expanded value's length */
+ df->explen = strlen(df->expan);
+
+ /* make sure the expansion isn't too long */
+ if (df->explen >= 40)
+ errsig(ctx->tokcxerr, ERR_LONG_LINE_MACRO);
+ }
+ }
+
+ /* return it */
+ return df;
+ }
+ }
+
+ /* didn't find anything */
+ return 0;
+}
+
+/*
+ * Write preprocessor state to a file
+ */
+void tok_write_defines(tokcxdef *ctx, osfildef *fp, errcxdef *ec)
+{
+ int i;
+ tokdfdef **dfp;
+ tokdfdef *df;
+ char buf[4];
+
+ /* write each element of the hash chains */
+ for (i = TOKDFHSHSIZ, dfp = ctx->tokcxdf ; i ; ++dfp, --i)
+ {
+ /* write each entry in this hash chain */
+ for (df = *dfp ; df ; df = df->nxt)
+ {
+ /* write this entry */
+ oswp2(buf, df->len);
+ oswp2(buf + 2, df->explen);
+ if (osfwb(fp, buf, 4)
+ || osfwb(fp, df->nm, df->len)
+ || (df->explen != 0 && osfwb(fp, df->expan, df->explen)))
+ errsig(ec, ERR_WRTGAM);
+ }
+
+ /* write a zero-length entry to indicate the end of this chain */
+ oswp2(buf, 0);
+ if (osfwb(fp, buf, 4)) errsig(ec, ERR_WRTGAM);
+ }
+}
+
+/*
+ * Read preprocessor state from a file
+ */
+void tok_read_defines(tokcxdef *ctx, osfildef *fp, errcxdef *ec)
+{
+ int i;
+ tokdfdef **dfp;
+ tokdfdef *df;
+ char buf[4];
+
+ /* write each element of the hash chains */
+ for (i = TOKDFHSHSIZ, dfp = ctx->tokcxdf ; i ; ++dfp, --i)
+ {
+ /* read this hash chain */
+ for (;;)
+ {
+ /* read the next entry's header, and stop if this is the end */
+ if (osfrb(fp, buf, 4)) errsig(ec, ERR_RDGAM);
+ if (osrp2(buf) == 0) break;
+
+ /* set up a new symbol of the appropriate size */
+ df = (tokdfdef *)mchalo(ec,
+ (sizeof(tokdfdef) + osrp2(buf)
+ + osrp2(buf+2) - 1),
+ "tok_read_defines");
+ df->explen = osrp2(buf+2);
+ df->nm = df->expan + df->explen;
+ df->len = osrp2(buf);
+
+ /* read the rest of the symbol */
+ if (osfrb(fp, df->nm, df->len)
+ || (df->explen != 0 && osfrb(fp, df->expan, df->explen)))
+ errsig(ec, ERR_RDGAM);
+
+ /*
+ * If a symbol with this name already exists in the table,
+ * discard the new one -- the symbols defined by -D and the
+ * current set of built-in symbols takes precedence over the
+ * set loaded from the file.
+ */
+ if (tok_find_define(ctx, df->nm, df->len))
+ {
+ /* simply discard this symbol */
+ mchfre(df);
+ }
+ else
+ {
+ /* link it into this hash chain */
+ df->nxt = *dfp;
+ *dfp = df;
+ }
+ }
+ }
+}
+
+
+
+/* compute a #define symbol's hash value */
+static int tokdfhsh(char *sym, int len)
+{
+ uint hsh;
+
+ for (hsh = 0 ; len ; ++sym, --len)
+ hsh = (hsh + *sym) & TOKDFHSHMASK;
+ return hsh;
+}
+
+/* convert a #define symbol to lower case if folding case */
+static char *tok_casefold_defsym(tokcxdef *ctx, char *outbuf,
+ char *src, int len)
+{
+ if (ctx->tokcxflg & TOKCXCASEFOLD)
+ {
+ char *dst;
+ int rem;
+
+ /* make a lower-case copy of the symbol */
+ rem = (len > TOKNAMMAX ? TOKNAMMAX : len);
+ for (dst = outbuf ; rem > 0 ; ++dst, ++src, --rem)
+ *dst = (Common::isUpper((uchar)*src) ? Common::isLower((uchar)*src) : *src);
+
+ /* use the lower-case copy instead of the original */
+ return outbuf;
+ }
+ else
+ {
+ /* return the original unchanged */
+ return src;
+ }
+}
+
+/*
+ * convert a token to lower-case if we're folding case
+ */
+void tok_case_fold(tokcxdef *ctx, tokdef *tok)
+{
+ /* if we're in case-insensitive mode, convert the token to lower-case */
+ if (ctx->tokcxflg & TOKCXCASEFOLD)
+ {
+ char *p;
+ int len;
+
+ /* convert each character in the token to lower-case */
+ for (p = tok->toknam, len = tok->toklen ; len != 0 ; ++p, --len)
+ {
+ /* if this character is upper-case, convert it to lower-case */
+ if (Common::isUpper((uchar)*p))
+ *p = Common::isLower((uchar)*p);
+ }
+ }
+}
+
+/* add a symbol to the #define symbol table, folding case if necessary */
+void tok_add_define_cvtcase(tokcxdef *ctx, char *sym, int len,
+ char *expan, int explen)
+{
+ char mysym[TOKNAMMAX];
+
+ /* convert to lower-case if necessary */
+ sym = tok_casefold_defsym(ctx, mysym, sym, len);
+
+ /* add the symbol */
+ tok_add_define(ctx, sym, len, expan, explen);
+}
+
+/* add a symbol to the #define symbol table */
+void tok_add_define(tokcxdef *ctx, char *sym, int len,
+ char *expan, int explen)
+{
+ int hsh;
+ tokdfdef *df;
+
+ /* if it's already defined, ignore it */
+ if (tok_find_define(ctx, sym, len))
+ return;
+
+ /* find the appropriate entry in the hash table */
+ hsh = tokdfhsh(sym, len);
+
+ /* allocate space for the symbol */
+ df = (tokdfdef *)mchalo(ctx->tokcxerr,
+ (sizeof(tokdfdef) + len + explen - 1),
+ "tok_add_define");
+
+ /* set up the new symbol */
+ df->nm = df->expan + explen;
+ df->len = len;
+ df->explen = explen;
+ memcpy(df->expan, expan, explen);
+ memcpy(df->nm, sym, len);
+
+ /* link it into the hash chain */
+ df->nxt = ctx->tokcxdf[hsh];
+ ctx->tokcxdf[hsh] = df;
+}
+
+/* add a #define symbol with a numeric value */
+void tok_add_define_num_cvtcase(tokcxdef *ctx, char *sym, int len, int num)
+{
+ char buf[20];
+
+ /* convert the value to a string */
+ sprintf(buf, "%d", num);
+
+ /* add the text value */
+ tok_add_define_cvtcase(ctx, sym, len, buf, strlen(buf));
+}
+
+/* undefine a #define symbol */
+void tok_del_define(tokcxdef *ctx, char *sym, int len)
+{
+ int hsh;
+ tokdfdef *df;
+ tokdfdef *prv;
+
+ /* find the appropriate chain the hash table */
+ hsh = tokdfhsh(sym, len);
+
+ /* search the chain for this symbol */
+ for (prv = 0, df = ctx->tokcxdf[hsh] ; df ; prv = df, df = df->nxt)
+ {
+ /* if this one matches, delete it */
+ if (df->len == len && !memcmp(df->nm, sym, (size_t)len))
+ {
+ /* unlink it from the chain */
+ if (prv)
+ prv->nxt = df->nxt;
+ else
+ ctx->tokcxdf[hsh] = df->nxt;
+
+ /* delete this symbol, and we're done */
+ mchfre(df);
+ break;
+ }
+ }
+}
+
+/* scan a #define symbol to see how long it is */
+static int tok_scan_defsym(tokcxdef *ctx, char *p, int len)
+{
+ int symlen;
+
+ /* make sure it's a valid symbol */
+ if (!(Common::isAlpha((uchar)*p) || *p == '_' || *p == '$'))
+ {
+ errlog(ctx->tokcxerr, ERR_REQSYM);
+ return 0;
+ }
+
+ /* count characters as long as we have valid symbol characters */
+ for (symlen = 0 ; len && TOKISSYM(*p) ; ++p, --len, ++symlen) ;
+ return symlen;
+}
+
+/* process a #define */
+static void tokdefine(tokcxdef *ctx, char *p, int len)
+{
+ char *sym;
+ int symlen;
+ char *expan;
+ char mysym[TOKNAMMAX];
+
+ /* get the symbol */
+ sym = p;
+ if (!(symlen = tok_scan_defsym(ctx, p, len)))
+ return;
+
+ /* if it's already in the table, log an error */
+ if (tok_find_define(ctx, sym, symlen))
+ {
+ errlog(ctx->tokcxerr, ERR_DEFREDEF);
+ return;
+ }
+
+ /* skip whitespace following the symbol */
+ expan = sym + symlen;
+ len -= symlen;
+ while (len && t_isspace(*expan)) --len, ++expan;
+
+ /* if we're folding case, convert the symbol to lower case */
+ sym = tok_casefold_defsym(ctx, mysym, sym, symlen);
+
+ /* define the symbol */
+ tok_add_define(ctx, sym, symlen, expan, len);
+}
+
+/*
+ * Update the #if status for the current nesting. Any enclosing
+ * negative #if will override everything inside, so we need to look
+ * through the nesting from the outside in until we either determine
+ * that everything is affirmative or we find a negative anywhere in the
+ * nesting.
+ */
+static void tok_update_if_stat(tokcxdef *ctx)
+{
+ int i;
+
+ /* look through nesting from the outermost level */
+ for (i = 0 ; i < ctx->tokcxifcnt ; ++i)
+ {
+ /* assume this level will apply to everything inside */
+ ctx->tokcxifcur = ctx->tokcxif[i];
+
+ /* if this level is off, everything inside is off */
+ switch (ctx->tokcxif[i])
+ {
+ case TOKIF_IF_NO:
+ case TOKIF_ELSE_NO:
+ /*
+ * this level is off, hence everything inside is off -- stop
+ * here with the current (negative) determination
+ */
+ return;
+
+ default:
+ /* so far we're in the "on" section, so keep looking */
+ break;
+ }
+ }
+}
+
+/* process an #ifdef or a #ifndef */
+static void tok_ifdef_ifndef(tokcxdef *ctx, char *p, int len, int is_ifdef)
+{
+ int symlen;
+ char *sym;
+ int stat;
+ int found;
+ char mysym[TOKNAMMAX];
+
+ /* get the symbol */
+ sym = p;
+ if (!(symlen = tok_scan_defsym(ctx, p, len)))
+ return;
+
+ /* if we're folding case, convert the symbol to lower case */
+ sym = tok_casefold_defsym(ctx, mysym, sym, symlen);
+
+ /* see if we can find it in the table, and set the status accordingly */
+ found = (tok_find_define(ctx, sym, symlen) != 0);
+
+ /* invert the test if this is an ifndef */
+ if (!is_ifdef) found = !found;
+
+ /* set the #if status accordingly */
+ if (found)
+ stat = TOKIF_IF_YES;
+ else
+ stat = TOKIF_IF_NO;
+ ctx->tokcxif[ctx->tokcxifcnt] = stat;
+
+ /* allocate a new #if level (making sure we have room) */
+ if (ctx->tokcxifcnt >= TOKIFNEST)
+ {
+ errlog(ctx->tokcxerr, ERR_MANYPIF);
+ return;
+ }
+ ctx->tokcxifcnt++;
+
+ /* update the current status */
+ tok_update_if_stat(ctx);
+}
+
+/* process a #error */
+static void tok_p_error(tokcxdef *ctx, char *p, int len)
+{
+ errlog1(ctx->tokcxerr, ERR_P_ERROR,
+ ERRTSTR, errstr(ctx->tokcxerr, p, len));
+}
+
+/* process a #ifdef */
+static void tokifdef(tokcxdef *ctx, char *p, int len)
+{
+ tok_ifdef_ifndef(ctx, p, len, TRUE);
+}
+
+/* process a #ifndef */
+static void tokifndef(tokcxdef *ctx, char *p, int len)
+{
+ tok_ifdef_ifndef(ctx, p, len, FALSE);
+}
+
+/* process a #if */
+static void tokif(tokcxdef *ctx, char *p, int len)
+{
+ errsig(ctx->tokcxerr, ERR_PIF_NA);
+}
+
+/* process a #elif */
+static void tokelif(tokcxdef *ctx, char *p, int len)
+{
+ errsig(ctx->tokcxerr, ERR_PELIF_NA);
+}
+
+/* process a #else */
+static void tokelse(tokcxdef *ctx, char *p, int len)
+{
+ int cnt;
+
+ /* if we're not expecting #else, it's an error */
+ cnt = ctx->tokcxifcnt;
+ if (cnt == 0 || ctx->tokcxif[cnt-1] == TOKIF_ELSE_YES
+ || ctx->tokcxif[cnt-1] == TOKIF_ELSE_NO)
+ {
+ errlog(ctx->tokcxerr, ERR_BADPELSE);
+ return;
+ }
+
+ /* switch to the appropriate #else state (opposite the #if state) */
+ if (ctx->tokcxif[cnt-1] == TOKIF_IF_YES)
+ ctx->tokcxif[cnt-1] = TOKIF_ELSE_NO;
+ else
+ ctx->tokcxif[cnt-1] = TOKIF_ELSE_YES;
+
+ /* update the current status */
+ tok_update_if_stat(ctx);
+}
+
+/* process a #endif */
+static void tokendif(tokcxdef *ctx, char *p, int len)
+{
+ /* if we're not expecting #endif, it's an error */
+ if (ctx->tokcxifcnt == 0)
+ {
+ errlog(ctx->tokcxerr, ERR_BADENDIF);
+ return;
+ }
+
+ /* remove the #if level */
+ ctx->tokcxifcnt--;
+
+ /* update the current status */
+ tok_update_if_stat(ctx);
+}
+
+/* process a #undef */
+static void tokundef(tokcxdef *ctx, char *p, int len)
+{
+ char *sym;
+ int symlen;
+ char mysym[TOKNAMMAX];
+
+ /* get the symbol */
+ sym = p;
+ if (!(symlen = tok_scan_defsym(ctx, p, len)))
+ return;
+
+ /* if we're folding case, convert the symbol to lower case */
+ sym = tok_casefold_defsym(ctx, mysym, sym, symlen);
+
+ /* if it's not defined, log a warning */
+ if (!tok_find_define(ctx, sym, symlen))
+ {
+ errlog(ctx->tokcxerr, ERR_PUNDEF);
+ return;
+ }
+
+ /* undefine the symbol */
+ tok_del_define(ctx, sym, symlen);
+}
+
+/* process a #pragma directive */
+static void tokpragma(tokcxdef *ctx, char *p, int len)
+{
+ /* ignore empty pragmas */
+ if (len == 0)
+ {
+ errlog(ctx->tokcxerr, ERR_PRAGMA);
+ return;
+ }
+
+ /* see what we have */
+ if (len > 1
+ && (*p == 'c' || *p == 'C')
+ && (*(p+1) == '+' || *(p+1) == '-' || t_isspace(*(p+1))))
+ {
+ /* skip spaces after the 'C', if any */
+ for (++p, --len ; len && t_isspace(*p) ; ++p, --len) ;
+
+ /* look for the + or - flag */
+ if (len && *p == '+')
+ ctx->tokcxflg |= TOKCXFCMODE;
+ else if (len && *p == '-')
+ ctx->tokcxflg &= ~TOKCXFCMODE;
+ else
+ {
+ errlog(ctx->tokcxerr, ERR_PRAGMA);
+ return;
+ }
+ }
+ else
+ {
+ errlog(ctx->tokcxerr, ERR_PRAGMA);
+ }
+}
+
+/* process a #include directive */
+static void tokinclude(tokcxdef *ctx, char *p, int len)
+{
+ linfdef *child;
+ tokpdef *path;
+ char *fname;
+ int match;
+ int flen;
+ linfdef *lin;
+ char *q;
+ size_t flen2;
+
+ /* find the filename portion */
+ fname = p + 1; /* remember start of filename */
+ path = ctx->tokcxinc; /* start with first path entry */
+
+ if (!len)
+ {
+ errlog(ctx->tokcxerr, ERR_INCNOFN);
+ return;
+ }
+
+ switch(*p)
+ {
+ case '<':
+ match = '>';
+ if (path && path->tokpnxt) path = path->tokpnxt; /* skip 1st path */
+ goto find_matching_delim;
+
+ case '"':
+ match = '"';
+
+ find_matching_delim:
+ for (++p, --len ; len && *p != match ; --len, ++p) ;
+ if (len == 0 || *p != match) errlog(ctx->tokcxerr, ERR_INCMTCH);
+ break;
+
+ default:
+ errlog(ctx->tokcxerr, ERR_INCSYN);
+ return;
+ }
+
+ flen = p - fname; /* compute length of filename */
+ for (q = p, flen2 = 0 ;
+ q > fname && *(q-1) != OSPATHCHAR && !strchr(OSPATHALT, *(q-1)) ;
+ --q, ++flen2) ;
+
+ /* check to see if this file has already been included */
+ for (lin = ctx->tokcxhdr ; lin ; lin = (linfdef *)lin->linflin.linnxt)
+ {
+ char *p2 = lin->linfnam;
+
+ p2 += strlen(p2);
+
+ while (p2 > lin->linfnam && *(p2-1) != OSPATHCHAR
+ && !strchr(OSPATHALT, *(p2-1)))
+ --p2;
+ if (strlen(p2) == flen2
+ && !memicmp(p2, q, flen2))
+ {
+ errlog1(ctx->tokcxerr, ERR_INCRPT, ERRTSTR,
+ errstr(ctx->tokcxerr, fname, flen));
+ return;
+ }
+ }
+
+ /* initialize the line source */
+ child = linfini(ctx->tokcxmem, ctx->tokcxerr, fname, flen, path, TRUE,
+ (ctx->tokcxflg & TOKCXFLIN2) != 0);
+
+ /* if not found, signal an error */
+ if (!child) errsig1(ctx->tokcxerr, ERR_INCSEAR,
+ ERRTSTR, errstr(ctx->tokcxerr, fname, flen));
+
+ /* link into tokenizer list of line records */
+ child->linflin.linnxt = (lindef *)ctx->tokcxhdr;
+ ctx->tokcxhdr = child;
+
+ /* if we're tracking sources for debugging, add into the chain */
+ if (ctx->tokcxdbg)
+ {
+ ctx->tokcxdbg->dbgcxlin = &child->linflin;
+ child->linflin.linid = ctx->tokcxdbg->dbgcxfid++;
+ }
+
+ /* remember my C-mode setting */
+ if (ctx->tokcxflg & TOKCXFCMODE)
+ ctx->tokcxlin->linflg |= LINFCMODE;
+ else
+ ctx->tokcxlin->linflg &= ~LINFCMODE;
+
+ child->linflin.linpar = ctx->tokcxlin; /* remember parent line source */
+ ctx->tokcxlin = &child->linflin; /* make the child the current source */
+}
+
+/* get a new line from line source, processing '#' directives */
+static int tokgetlin(tokcxdef *ctx, int dopound)
+{
+ for (;;)
+ {
+ if (linget(ctx->tokcxlin))
+ {
+ /* at eof in current source; resume parent if there is one */
+ if (ctx->tokcxlin->linpar)
+ {
+ lindef *parent;
+
+ parent = ctx->tokcxlin->linpar; /* remember parent */
+ lincls(ctx->tokcxlin); /* close included file */
+ if (!ctx->tokcxdbg) /* if no debug context... */
+ mchfre(ctx->tokcxlin); /* free line source */
+ ctx->tokcxlin = parent; /* reset to parent line source */
+ if (parent->linflg & LINFCMODE)
+ ctx->tokcxflg |= TOKCXFCMODE;
+ else
+ ctx->tokcxflg &= ~TOKCXFCMODE;
+ continue; /* back for another attempt */
+ }
+ else
+ {
+ /* check for outstanding #if/#ifdef */
+ if (ctx->tokcxifcnt)
+ errlog(ctx->tokcxerr, ERR_NOENDIF);
+
+ /* return end-of-file indication */
+ return TRUE;
+ }
+ }
+
+ /* if this is a multi-segment line, copy it into our own buffer */
+ if (ctx->tokcxlin->linflg & LINFMORE)
+ {
+ char *p;
+ uint rem;
+ int done;
+
+ if (!ctx->tokcxbuf)
+ {
+ /* allocate 1k as a default buffer */
+ ctx->tokcxbuf = (char *)mchalo(ctx->tokcxerr, 1024,
+ "tok");
+ ctx->tokcxbsz = 1024;
+ }
+ ctx->tokcxlen = 0;
+
+ for (done = FALSE, p = ctx->tokcxbuf, rem = ctx->tokcxbsz ;
+ !done ; )
+ {
+ size_t len = ctx->tokcxlin->linlen;
+
+ /* add the current segment's length into line length */
+ ctx->tokcxlen += len;
+
+ /* we're done after this piece if the last fetch was all */
+ done = !(ctx->tokcxlin->linflg & LINFMORE);
+ if (len + 1 > rem)
+ {
+ char *newp;
+
+ /* increase the size of the buffer */
+ if (ctx->tokcxbsz > (unsigned)0x8000)
+ errsig(ctx->tokcxerr, ERR_LONGLIN);
+ rem += 4096;
+ ctx->tokcxbsz += 4096;
+
+ /* allocate a new buffer and copy line into it */
+ newp = (char *)mchalo(ctx->tokcxerr, ctx->tokcxbsz, "tok");
+ memcpy(newp, ctx->tokcxbuf, (size_t)(p - ctx->tokcxbuf));
+
+ /* free the original buffer, and use the new one */
+ p = (p - ctx->tokcxbuf) + newp;
+ mchfre(ctx->tokcxbuf);
+ ctx->tokcxbuf = newp;
+ }
+
+ /* add the line to the buffer */
+ memcpy(p, ctx->tokcxlin->linbuf, len);
+ p += len;
+ rem -= len;
+
+ /* get the next piece of the line if there is one */
+ if (!done)
+ {
+ if (linget(ctx->tokcxlin)) break;
+ }
+ }
+
+ /* null-terminate the buffer, and use it for input */
+ *p = '\0';
+ ctx->tokcxptr = ctx->tokcxbuf;
+ }
+ else
+ {
+ ctx->tokcxptr = ctx->tokcxlin->linbuf;
+ ctx->tokcxlen = ctx->tokcxlin->linlen;
+ }
+
+ /* check for preprocessor directives */
+ if (dopound && ctx->tokcxlen != 0 && ctx->tokcxptr[0] == '#'
+ && !(ctx->tokcxlin->linflg & LINFNOINC))
+ {
+ char *p;
+ int len;
+ static struct
+ {
+ char *nm;
+ int len;
+ int ok_in_if;
+ void (*fn)(tokcxdef *, char *, int);
+ }
+ *dirp, dir[] =
+ {
+ { "include", 7, FALSE, tokinclude },
+ { "pragma", 6, FALSE, tokpragma },
+ { "define", 6, FALSE, tokdefine },
+ { "ifdef", 5, TRUE, tokifdef },
+ { "ifndef", 6, TRUE, tokifndef },
+ { "if", 2, TRUE, tokif },
+ { "else", 4, TRUE, tokelse },
+ { "elif", 4, TRUE, tokelif },
+ { "endif", 5, TRUE, tokendif },
+ { "undef", 5, FALSE, tokundef },
+ { "error", 5, FALSE, tok_p_error }
+ };
+ int i;
+
+ /* scan off spaces between '#' and directive */
+ for (len = ctx->tokcxlen - 1, p = &ctx->tokcxptr[1] ;
+ len && t_isspace(*p) ; --len, ++p) ;
+
+ /* find and process the directive */
+ for (dirp = dir, i = sizeof(dir)/sizeof(dir[0]) ; i ; --i, ++dirp)
+ {
+ /* compare this directive; if it wins, call its function */
+ if (len >= dirp->len && !memcmp(p, dirp->nm, (size_t)dirp->len)
+ && (len == dirp->len || t_isspace(*(p + dirp->len))))
+ {
+ int cnt;
+ int stat;
+
+ /*
+ * if we're not in a #if's false part, or if the
+ * directive is processed even in #if false parts,
+ * process the line, otherwise skip it
+ */
+ cnt = ctx->tokcxifcnt;
+ if (dirp->ok_in_if || cnt == 0
+ || ((stat = ctx->tokcxifcur) == TOKIF_IF_YES
+ || stat == TOKIF_ELSE_YES))
+ {
+ /* skip whitespace following the directive */
+ for (p += dirp->len, len -= dirp->len ;
+ len && t_isspace(*p) ;
+ --len, ++p) ;
+
+ /* invoke the function to process this directive */
+ (*dirp->fn)(ctx, p, len);
+ }
+
+ /* there's no need to look at more directives */
+ break;
+ }
+ }
+
+ /* if we didn't find anything, flag the error */
+ if (i == 0)
+ errlog(ctx->tokcxerr, ERR_PRPDIR);
+
+ /* ignore this line */
+ continue;
+ }
+ else
+ {
+ /*
+ * Check the #if level. If we're in an #if, and we're to
+ * ignore lines (because of a false condition or an #else
+ * part for a true condition), skip this line.
+ */
+ if (ctx->tokcxifcnt != 0)
+ {
+ switch(ctx->tokcxifcur)
+ {
+ case TOKIF_IF_NO:
+ case TOKIF_ELSE_NO:
+ /* ignore this line */
+ continue;
+
+ default:
+ /* we're in a true part - keep the line */
+ break;
+ }
+ }
+
+ ctx->tokcxlin->linflg &= ~LINFDBG; /* no debug record yet */
+ return(FALSE); /* return the line we found */
+ }
+ }
+}
+
+/* get the next token, removing it from the input stream */
+int toknext(tokcxdef *ctx)
+{
+ char *p;
+ tokdef *tok = &ctx->tokcxcur;
+ int len;
+
+ /*
+ * Check for the special case that we pushed an open paren prior to
+ * a string containing an embedded expression. If this is the case,
+ * immediately return the string we previously parsed.
+ */
+ if ((ctx->tokcxflg & TOKCXF_EMBED_PAREN_PRE) != 0)
+ {
+ /*
+ * convert the token to a string - note that the offset
+ * information for the string is already in the current token
+ * structure, since we set everything up for it on the previous
+ * call where we actually parsed the beginning of the string
+ */
+ tok->toktyp = TOKTDSTRING;
+
+ /* clear the special flag - we've now consumed the pushed string */
+ ctx->tokcxflg &= ~TOKCXF_EMBED_PAREN_PRE;
+
+ /* immediately return the string */
+ return tok->toktyp;
+ }
+
+ /* set up at the current scanning position */
+ p = ctx->tokcxptr;
+ len = ctx->tokcxlen;
+
+ /* scan off whitespace and comments until we find something */
+ do
+ {
+ skipblanks:
+ /* if there's nothing on this line, get the next one */
+ if (len == 0)
+ {
+ /* if we're in a macro expansion, continue after it */
+ if (ctx->tokcxmlvl)
+ {
+ ctx->tokcxmlvl--;
+ p = ctx->tokcxmsav[ctx->tokcxmlvl];
+ len = ctx->tokcxmsvl[ctx->tokcxmlvl];
+ }
+ else
+ {
+ if (tokgetlin(ctx, TRUE))
+ {
+ tok->toktyp = TOKTEOF;
+ goto done;
+ }
+ p = ctx->tokcxptr;
+ len = ctx->tokcxlen;
+ }
+ }
+ while (len && t_isspace(*p)) ++p, --len; /* scan off whitespace */
+
+ /* check for comments, and remove if present */
+ if (len >= 2 && *p == '/' && *(p+1) == '/')
+ len = 0;
+ else if (len >= 2 && *p == '/' && *(p+1) == '*')
+ {
+ while (len < 2 || *p != '*' || *(p+1) != '/')
+ {
+ if (len != 0)
+ ++p, --len;
+
+ if (len == 0)
+ {
+ if (ctx->tokcxmlvl != 0)
+ {
+ ctx->tokcxmlvl--;
+ p = ctx->tokcxmsav[ctx->tokcxmlvl];
+ len = ctx->tokcxmsvl[ctx->tokcxmlvl];
+ }
+ else
+ {
+ if (tokgetlin(ctx, FALSE))
+ {
+ ctx->tokcxptr = p;
+ tok->toktyp = TOKTEOF;
+ goto done;
+ }
+ p = ctx->tokcxptr;
+ len = ctx->tokcxlen;
+ }
+ }
+ }
+ p += 2;
+ len -= 2;
+ goto skipblanks;
+ }
+ } while (len == 0);
+
+nexttoken:
+ if (Common::isAlpha((uchar)*p) || *p == '_' || *p == '$')
+ {
+ int l;
+ int hash;
+ char *q;
+ toktdef *tab;
+ int found = FALSE;
+ uchar thischar;
+ tokdfdef *df;
+
+ for (hash = 0, l = 0, q = tok->toknam ;
+ len != 0 && TOKISSYM(*p) && l < TOKNAMMAX ;
+ (thischar = ((Common::isUpper((uchar)*p)
+ && (ctx->tokcxflg & TOKCXCASEFOLD))
+ ? Common::isLower((uchar)*p) : *p)),
+ (hash = ((hash + thischar) & (TOKHASHSIZE - 1))),
+ (*q++ = thischar), ++p, --len, ++l) ;
+ *q = '\0';
+ if (len != 0 && TOKISSYM(*p))
+ {
+ while (len != 0 && TOKISSYM(*p)) ++p, --len;
+ errlog1(ctx->tokcxerr, ERR_TRUNC, ERRTSTR,
+ errstr(ctx->tokcxerr, tok->toknam, tok->toklen));
+ }
+ tok->toklen = l;
+ tok->tokhash = hash;
+
+ /*
+ * check for the special defined() preprocessor operator
+ */
+ if (l == 9 && !memcmp(tok->toknam,
+ ((ctx->tokcxflg & TOKCXCASEFOLD)
+ ? "__defined" : "__DEFINED"),
+ (size_t)9)
+ && len > 2 && *p == '(' && TOKISSYM(*(p+1))
+ && !Common::isDigit((uchar)*(p+1)))
+ {
+ int symlen;
+ char mysym[TOKNAMMAX];
+
+ /* find the matching ')', allowing only symbolic characters */
+ ++p, --len;
+ for (symlen = 0, q = p ; len && *p != ')' && TOKISSYM(*p) ;
+ ++p, --len, ++symlen) ;
+
+ /* make sure we found the closing paren */
+ if (!len || *p != ')')
+ errsig(ctx->tokcxerr, ERR_BADISDEF);
+ ++p, --len;
+
+ /* if we're folding case, convert the symbol to lower case */
+ q = tok_casefold_defsym(ctx, mysym, q, symlen);
+
+ /* check to see if it's defined */
+ tok->toktyp = TOKTNUMBER;
+ tok->tokval = (tok_find_define(ctx, q, symlen) != 0);
+ goto done;
+ }
+
+ /* substitute the preprocessor #define, if any */
+ if ((df = tok_find_define(ctx, tok->toknam, l)) != 0)
+ {
+ /* save the current parsing position */
+ if (ctx->tokcxmlvl >= TOKMACNEST)
+ errsig(ctx->tokcxerr, ERR_MACNEST);
+ ctx->tokcxmsav[ctx->tokcxmlvl] = p;
+ ctx->tokcxmsvl[ctx->tokcxmlvl] = len;
+ ctx->tokcxmlvl++;
+
+ /* point to the token's expansion and keep going */
+ p = df->expan;
+ len = df->explen;
+ goto nexttoken;
+ }
+
+ /* look up in symbol table(s), if any */
+ for (tab = ctx->tokcxstab ; tab ; tab = tab->toktnxt)
+ {
+ if ((found = (*tab->toktfsea)(tab, tok->toknam, l, hash,
+ &tok->toksym)) != 0)
+ break;
+ }
+
+ if (found && tok->toksym.tokstyp == TOKSTKW)
+ tok->toktyp = tok->toksym.toksval;
+ else
+ {
+ tok->toktyp = TOKTSYMBOL;
+ if (!found) tok->toksym.tokstyp = TOKSTUNK;
+ }
+ goto done;
+ }
+ else if (Common::isDigit((uchar)*p))
+ {
+ long acc = 0;
+
+ /* check for octal/hex */
+ if (*p == '0')
+ {
+ ++p, --len;
+ if (len && (*p == 'x' || *p == 'X'))
+ {
+ /* hex */
+ ++p, --len;
+ while (len && TOKISHEX(*p))
+ {
+ acc = (acc << 4) + TOKHEX2INT(*p);
+ ++p, --len;
+ }
+ }
+ else
+ {
+ /* octal */
+ while (len && TOKISOCT(*p))
+ {
+ acc = (acc << 3) + TOKOCT2INT(*p);
+ ++p, --len;
+ }
+ }
+ }
+ else
+ {
+ /* decimal */
+ while (len && Common::isDigit((uchar)*p))
+ {
+ acc = (acc << 1) + (acc << 3) + TOKDEC2INT(*p);
+ ++p, --len;
+ }
+ }
+ tok->tokval = acc;
+ tok->toktyp = TOKTNUMBER;
+ goto done;
+ }
+ else if (*p == '"' || *p == '\'')
+ {
+ char delim; /* closing delimiter we're looking for */
+ char *strstart; /* pointer to start of string */
+ int warned;
+
+ delim = *p;
+ --len;
+ strstart = ++p;
+
+ if (delim == '"' && len >= 2 && *p == '<' && *(p+1) == '<')
+ {
+ /* save the current parsing position */
+ if (ctx->tokcxmlvl >= TOKMACNEST)
+ errsig(ctx->tokcxerr, ERR_MACNEST);
+ ctx->tokcxmsav[ctx->tokcxmlvl] = p + 2;
+ ctx->tokcxmsvl[ctx->tokcxmlvl] = len - 2;
+ ctx->tokcxmlvl++;
+
+ /*
+ * read from the special "<<" expansion string - use the
+ * version for a "<<" at the very beginning of the string
+ */
+ p = tokmac1s;
+ len = strlen(p);
+ ctx->tokcxflg |= TOKCXFINMAC;
+ goto nexttoken;
+ }
+ tok->toktyp = (delim == '"' ? TOKTDSTRING : TOKTSSTRING);
+
+ tok->tokofs = (*ctx->tokcxsst)(ctx->tokcxscx); /* start the string */
+ for (warned = FALSE ;; )
+ {
+ if (len >= 2 && *p == '\\')
+ {
+ if (*(p+1) == '"' || *(p+1) == '\'')
+ {
+ (*ctx->tokcxsad)(ctx->tokcxscx, strstart,
+ (ushort)(p - strstart));
+ strstart = p + 1;
+ }
+ p += 2;
+ len -= 2;
+ }
+ else if (len == 0 || *p == delim ||
+ (delim == '"' && len >= 2 && *p == '<' && *(p+1) == '<'
+ && !(ctx->tokcxflg & TOKCXFINMAC)))
+ {
+ (*ctx->tokcxsad)(ctx->tokcxscx, strstart,
+ (ushort)(p - strstart));
+ if (len == 0)
+ {
+ if (ctx->tokcxmlvl)
+ {
+ ctx->tokcxmlvl--;
+ p = ctx->tokcxmsav[ctx->tokcxmlvl];
+ len = ctx->tokcxmsvl[ctx->tokcxmlvl];
+ }
+ else
+ (*ctx->tokcxsad)(ctx->tokcxscx, " ", (ushort)1);
+
+ while (len == 0)
+ {
+ if (tokgetlin(ctx, FALSE))
+ errsig(ctx->tokcxerr, ERR_STREOF);
+ p = ctx->tokcxptr;
+ len = ctx->tokcxlen;
+
+ /* warn if it looks like the end of an object */
+ if (!warned && len && (*p == ';' || *p == '}'))
+ {
+ errlog(ctx->tokcxerr, ERR_STREND);
+ warned = TRUE; /* warn only once per string */
+ }
+
+ /* scan past whitespace at start of line */
+ while (len && t_isspace(*p)) ++p, --len;
+ }
+ strstart = p;
+ }
+ else break;
+ }
+ else
+ ++p, --len;
+ }
+
+ /* end the string */
+ (*ctx->tokcxsend)(ctx->tokcxscx);
+
+ /* check to see how it ended */
+ if (len != 0 && *p == delim)
+ {
+ /*
+ * We ended with the matching delimiter. Move past the
+ * closing delimiter.
+ */
+ ++p;
+ --len;
+
+ /*
+ * If we have a pending close paren we need to put in
+ * because of an embedded expression that occurred earlier
+ * in the string, parse the macro to provide the paren.
+ */
+ if ((ctx->tokcxflg & TOKCXF_EMBED_PAREN_AFT) != 0
+ && !(ctx->tokcxflg & TOKCXFINMAC))
+ {
+ /* clear the flag */
+ ctx->tokcxflg &= ~TOKCXF_EMBED_PAREN_AFT;
+
+ /* push the current parsing position */
+ if (ctx->tokcxmlvl >= TOKMACNEST)
+ errsig(ctx->tokcxerr, ERR_MACNEST);
+ ctx->tokcxmsav[ctx->tokcxmlvl] = p;
+ ctx->tokcxmsvl[ctx->tokcxmlvl] = len;
+ ctx->tokcxmlvl++;
+
+ /* parse the macro */
+ p = tokmac4;
+ len = strlen(p);
+ }
+ }
+ else if (len != 0 && *p == '<')
+ {
+ /* save the current parsing position */
+ if (ctx->tokcxmlvl >= TOKMACNEST)
+ errsig(ctx->tokcxerr, ERR_MACNEST);
+ ctx->tokcxmsav[ctx->tokcxmlvl] = p + 2;
+ ctx->tokcxmsvl[ctx->tokcxmlvl] = len - 2;
+ ctx->tokcxmlvl++;
+
+ /* read from the "<<" expansion */
+ p = tokmac1;
+ len = strlen(p);
+ ctx->tokcxflg |= TOKCXFINMAC;
+
+ /*
+ * Set the special push-a-paren flag: we'll return an open
+ * paren now, so that we have an open paren before the
+ * string, and then on the next call to toknext() we'll
+ * immediately return the string we've already parsed here.
+ * This will ensure that everything in the string is
+ * properly grouped together as a single indivisible
+ * expression.
+ *
+ * Note that we only need to do this for the first embedded
+ * expression in a string. Once we have a close paren
+ * pending, we don't need more open parens.
+ */
+ if (!(ctx->tokcxflg & TOKCXF_EMBED_PAREN_AFT))
+ {
+ ctx->tokcxflg |= TOKCXF_EMBED_PAREN_PRE;
+ tok->toktyp = TOKTLPAR;
+ }
+ }
+ goto done;
+ }
+ else if (len >= 2 && *p == '>' && *(p+1) == '>'
+ && (ctx->tokcxflg & TOKCXFINMAC) != 0)
+ {
+ /* skip the ">>" */
+ ctx->tokcxflg &= ~TOKCXFINMAC;
+ p += 2;
+ len -= 2;
+
+ /* save the current parsing position */
+ if (ctx->tokcxmlvl >= TOKMACNEST)
+ errsig(ctx->tokcxerr, ERR_MACNEST);
+ ctx->tokcxmsav[ctx->tokcxmlvl] = p;
+ ctx->tokcxmsvl[ctx->tokcxmlvl] = len;
+ ctx->tokcxmlvl++;
+
+ if (*p == '"')
+ {
+ ++(ctx->tokcxmsav[ctx->tokcxmlvl - 1]);
+ --(ctx->tokcxmsvl[ctx->tokcxmlvl - 1]);
+ p = tokmac3;
+
+ /*
+ * we won't need an extra closing paren now, since tokmac3
+ * provides it
+ */
+ ctx->tokcxflg &= ~TOKCXF_EMBED_PAREN_AFT;
+ }
+ else
+ {
+ /*
+ * The string is continuing. Set a flag to note that we
+ * need to provide a close paren after the end of the
+ * string, and parse the glue (tokmac2) that goes between
+ * the expression and the resumption of the string.
+ */
+ ctx->tokcxflg |= TOKCXF_EMBED_PAREN_AFT;
+ p = tokmac2;
+ }
+
+ len = strlen(p);
+ goto nexttoken;
+ }
+ else
+ {
+ tokscdef *sc;
+
+ for (sc = ctx->tokcxsc[ctx->tokcxinx[(uchar)*p]] ; sc ;
+ sc = sc->tokscnxt)
+ {
+ if (toksceq(sc->tokscstr, p, sc->toksclen, len))
+ {
+ tok->toktyp = sc->toksctyp;
+ p += sc->toksclen;
+ len -= sc->toksclen;
+ goto done;
+ }
+ }
+ errsig(ctx->tokcxerr, ERR_INVTOK);
+ }
+
+done:
+ ctx->tokcxptr = p;
+ ctx->tokcxlen = len;
+ return(tok->toktyp);
+}
+
+/* initialize a linear symbol table */
+void toktlini(errcxdef *errctx, toktldef *toktab, uchar *mem, uint siz)
+{
+ CLRSTRUCT(*toktab);
+
+ /* initialize superclass data */
+ toktab->toktlsc.toktfadd = toktladd; /* set add-symbol method */
+ toktab->toktlsc.toktfsea = toktlsea; /* set search-table method */
+ toktab->toktlsc.toktfeach = toktleach; /* set 'each' method */
+ toktab->toktlsc.toktfset = toktlset; /* set 'update' method */
+ toktab->toktlsc.tokterr = errctx; /* set error handling context */
+
+ /* initialize class data */
+ toktab->toktlptr = mem;
+ toktab->toktlnxt = mem;
+ toktab->toktlsiz = siz;
+}
+
+/* add a symbol to a linear symbol table */
+void toktladd(toktdef *toktab1, char *name, int namel,
+ int typ, int val, int hash)
+{
+ uint siz = sizeof(toks1def) + namel;
+ toksdef *sym;
+ toktldef *toktab = (toktldef *)toktab1;
+
+ VARUSED(hash);
+
+ if (toktab->toktlsiz < siz)
+ errsig(toktab->toktlsc.tokterr, ERR_NOLCLSY);
+
+ sym = (toksdef *)toktab->toktlnxt;
+ siz = osrndsz(siz);
+ toktab->toktlnxt += siz;
+ if (siz > toktab->toktlsiz) toktab->toktlsiz = 0;
+ else toktab->toktlsiz -= siz;
+
+ /* set up symbol */
+ sym->toksval = val;
+ sym->tokslen = namel;
+ sym->tokstyp = typ;
+ sym->toksfr = 0;
+ memcpy(sym->toksnam, name, (size_t)(namel + 1));
+
+ /* indicate there's one more symbol in the table */
+ ++(toktab->toktlcnt);
+}
+
+/* delete all symbols from a linear symbol table */
+void toktldel(toktldef *tab)
+{
+ tab->toktlcnt = 0;
+ tab->toktlsiz += tab->toktlnxt - tab->toktlptr;
+ tab->toktlnxt = tab->toktlptr;
+}
+
+/* call a function for every symbol in a linear symbol table */
+void toktleach(toktdef *tab1,
+ void (*cb)(void *ctx, toksdef *sym), void *ctx)
+{
+ toksdef *p;
+ uint cnt;
+ toktldef *tab = (toktldef *)tab1;
+
+ for (p = (toksdef *)tab->toktlptr, cnt = tab->toktlcnt ; cnt ; --cnt )
+ {
+ (*cb)(ctx, p);
+ p = (toksdef *)(((uchar *)p)
+ + osrndsz(p->tokslen + sizeof(toks1def)));
+ }
+}
+
+/* search a linear symbol table */
+int toktlsea(toktdef *tab1, char *name, int namel, int hash, toksdef *ret)
+{
+ toksdef *p;
+ uint cnt;
+ toktldef *tab = (toktldef *)tab1;
+
+ VARUSED(hash);
+
+ for (p = (toksdef *)tab->toktlptr, cnt = tab->toktlcnt ; cnt ; --cnt )
+ {
+ if (p->tokslen == namel && !memcmp(p->toksnam, name, (size_t)namel))
+ {
+ memcpy(ret, p, (size_t)(sizeof(toks1def) + namel));
+ return(TRUE);
+ }
+
+ p = (toksdef *)(((uchar *)p)
+ + osrndsz(p->tokslen + sizeof(toks1def)));
+ }
+
+ /* nothing found - indicate by returning FALSE */
+ return(FALSE);
+}
+
+/* update a symbol in a linear symbol table */
+void toktlset(toktdef *tab1, toksdef *newsym)
+{
+ toksdef *p;
+ uint cnt;
+ toktldef *tab = (toktldef *)tab1;
+
+ for (p = (toksdef *)tab->toktlptr, cnt = tab->toktlcnt ; cnt ; --cnt )
+ {
+ if (p->tokslen == newsym->tokslen
+ && !memcmp(p->toksnam, newsym->toksnam, (size_t)newsym->tokslen))
+ {
+ p->toksval = newsym->toksval;
+ p->tokstyp = newsym->tokstyp;
+ return;
+ }
+
+ p = (toksdef *)(((uchar *)p)
+ + osrndsz(p->tokslen + sizeof(toks1def)));
+ }
+}
+
+tokcxdef *tokcxini(errcxdef *errctx, mcmcxdef *mcmctx, tokldef *sctab)
+{
+ int i;
+ int cnt;
+ tokldef *p;
+ uchar c;
+ uchar index[256];
+ tokcxdef *ret;
+ tokscdef *sc;
+ ushort siz;
+
+ /* set up index table: finds tokcxsc entry from character value */
+ memset(index, 0, (size_t)sizeof(index));
+ for (i = cnt = 0, p = sctab ; (c = p->toklstr[0]) != 0 ; ++cnt, ++p)
+ if (!index[c]) index[c] = ++i;
+
+ /* allocate memory for table plus the tokscdef's */
+ siz = sizeof(tokcxdef) + (i * sizeof(tokscdef *))
+ + ((cnt + 1) * sizeof(tokscdef));
+ ret = (tokcxdef *)mchalo(errctx, siz, "tokcxini");
+ memset(ret, 0, (size_t)siz);
+
+ /* copy the index, set up fixed part */
+ memcpy(ret->tokcxinx, index, sizeof(ret->tokcxinx));
+ ret->tokcxerr = errctx;
+ ret->tokcxmem = mcmctx;
+
+ /* start out without an #if */
+ ret->tokcxifcur = TOKIF_IF_YES;
+
+ /* force the first toknext() to read a line */
+ ret->tokcxptr = "\000";
+
+ /* figure where the tokscdef's go (right after sc pointer array) */
+ sc = (tokscdef *)&ret->tokcxsc[i+1];
+
+ /* set up the individual tokscdef entries, and link into lists */
+ for (p = sctab ; (c = p->toklstr[0]) != 0 ; ++p, ++sc)
+ {
+ size_t len;
+
+ sc->toksctyp = p->tokltyp;
+ len = sc->toksclen = strlen(p->toklstr);
+ memcpy(sc->tokscstr, p->toklstr, len);
+ sc->tokscnxt = ret->tokcxsc[index[c]];
+ ret->tokcxsc[index[c]] = sc;
+ }
+
+ return(ret);
+}
+
+/* add an include path to a tokdef */
+void tokaddinc(tokcxdef *ctx, char *path, int pathlen)
+{
+ tokpdef *newpath;
+ tokpdef *last;
+
+ /* find the tail of the include path list, if any */
+ for (last = ctx->tokcxinc ; last && last->tokpnxt ;
+ last = last->tokpnxt) ;
+
+ /* allocate storage for and set up a new path structure */
+ newpath = (tokpdef *)mchalo(ctx->tokcxerr,
+ (sizeof(tokpdef) + pathlen - 1),
+ "tokaddinc");
+ newpath->tokplen = pathlen;
+ newpath->tokpnxt = (tokpdef *)0;
+ memcpy(newpath->tokpdir, path, (size_t)pathlen);
+
+ /* link in at end of list (if no list yet, newpath becomes first entry) */
+ if (last)
+ last->tokpnxt = newpath;
+ else
+ ctx->tokcxinc = newpath;
+}
} // End of namespace TADS2
} // End of namespace TADS
diff --git a/engines/glk/tads/tads2/tokenizer.h b/engines/glk/tads/tads2/tokenizer.h
index 50a8492cd3..11d76c6657 100644
--- a/engines/glk/tads/tads2/tokenizer.h
+++ b/engines/glk/tads/tads2/tokenizer.h
@@ -26,6 +26,7 @@
#include "glk/tads/tads2/lib.h"
#include "glk/tads/tads2/error_handling.h"
#include "glk/tads/tads2/line_source.h"
+#include "glk/tads/tads2/line_source_file.h"
#include "glk/tads/tads2/memory_cache.h"
namespace Glk {
@@ -356,7 +357,7 @@ struct tokcxdef {
int tokcxifcnt; /* number of #endif's we expect to find */
char tokcxif[TOKIFNEST]; /* #if state for each nesting level */
int tokcxifcur; /* current #if state, obeying nesting */
- struct linfdef *tokcxhdr; /* list of previously included headers */
+ linfdef *tokcxhdr; /* list of previously included headers */
tokscdef *tokcxsc[1]; /* special character table */
};
@@ -453,16 +454,16 @@ void tok_write_defines(tokcxdef *ctx, osfildef *fp, errcxdef *ec);
/* determine if a char is a valid non-initial character in a symbol name */
#define TOKISSYM(c) \
- (isalpha((uchar)(c)) || isdigit((uchar)(c)) || (c)=='_' || (c)=='$')
+ (Common::isAlpha((uchar)(c)) || Common::isDigit((uchar)(c)) || (c)=='_' || (c)=='$')
/* numeric conversion and checking macros */
#define TOKISHEX(c) \
- (isdigit((uchar)(c))||((c)>='a'&&(c)<='f')||((c)>='A'&&(c)<='F'))
+ (Common::isDigit((uchar)(c))||((c)>='a'&&(c)<='f')||((c)>='A'&&(c)<='F'))
#define TOKISOCT(c) \
- (isdigit((uchar)(c))&&!((c)=='8'||(c)=='9'))
+ (Common::isDigit((uchar)(c))&&!((c)=='8'||(c)=='9'))
#define TOKHEX2INT(c) \
- (isdigit((uchar)c)?(c)-'0':((c)>='a'?(c)-'a'+10:(c)-'A'+10))
+ (Common::isDigit((uchar)c)?(c)-'0':((c)>='a'?(c)-'a'+10:(c)-'A'+10))
#define TOKOCT2INT(c) ((c)-'0')
#define TOKDEC2INT(c) ((c)-'0')
diff --git a/engines/glk/tads/tads2/vocabulary.cpp b/engines/glk/tads/tads2/vocabulary.cpp
index e20bbe3de8..516e9df3f9 100644
--- a/engines/glk/tads/tads2/vocabulary.cpp
+++ b/engines/glk/tads/tads2/vocabulary.cpp
@@ -22,12 +22,912 @@
#include "glk/tads/tads2/run.h"
#include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/memory_cache_heap.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
-// TODO: Rest of vocabulary stuff
+
+/*
+ * Main vocabulary context. This can be saved globally if desired, so
+ * that routines that don't have any other access to it (such as
+ * Unix-style signal handlers) can reach it.
+ */
+voccxdef *main_voc_ctx = 0;
+
+#ifdef VOCW_IN_CACHE
+vocwdef *vocwget(voccxdef *ctx, uint idx)
+{
+ uint pg;
+
+ if (idx == VOCCXW_NONE)
+ return 0;
+
+ /* get the page we need */
+ pg = idx/VOCWPGSIZ;
+
+ /* if it's not locked, lock it */
+ if (pg != ctx->voccxwplck)
+ {
+ /* unlock the old page */
+ if (ctx->voccxwplck != MCMONINV)
+ mcmunlck(ctx->voccxmem, ctx->voccxwp[ctx->voccxwplck]);
+
+ /* lock the new page */
+ ctx->voccxwpgptr = (vocwdef *)mcmlck(ctx->voccxmem,
+ ctx->voccxwp[pg]);
+ ctx->voccxwplck = pg;
+ }
+
+ /* return the entry on that page */
+ return ctx->voccxwpgptr + (idx % VOCWPGSIZ);
+}
+#endif /*VOCW_IN_CACHE */
+
+/* hash value is based on first 6 characters only to allow match-in-6 */
+uint vochsh(uchar *t, int len)
+{
+ uint ret = 0;
+
+ if (len > 6) len = 6;
+ for ( ; len ; --len, ++t)
+ ret = (ret + (uint)(vocisupper(*t) ? tolower(*t) : *t))
+ & (VOCHASHSIZ - 1);
+ return(ret);
+}
+
+/* copy vocabulary word, and convert to lower case */
+static void voccpy(uchar *dst, uchar *src, int len)
+{
+ for ( ; len ; --len, ++dst, ++src)
+ *dst = vocisupper(*src) ? tolower(*src) : *src;
+}
+
+/* allocate and set up a new vocwdef record, linking into a vocdef's list */
+static void vocwset(voccxdef *ctx, vocdef *v, prpnum p, objnum objn,
+ int classflg)
+{
+ vocwdef *vw;
+ uint inx;
+ vocwdef *vw2;
+
+ /*
+ * look through the vocdef list to see if there's an existing entry
+ * with the DELETED marker -- if so, simply undelete it
+ */
+ for (inx = v->vocwlst, vw = vocwget(ctx, inx) ; vw ;
+ inx = vw->vocwnxt, vw = vocwget(ctx, inx))
+ {
+ /* if this entry was deleted, and otherwise matches, undelete it */
+ if ((vw->vocwflg & VOCFDEL)
+ && vw->vocwobj == objn && vw->vocwtyp == p)
+ {
+ /*
+ * Remove the deleted flag. We will otherwise leave the
+ * flags unchanged, since the VOCFDEL flag applies only to
+ * statically allocated objects, and hence the original
+ * flags should take precedence over any run-time flags.
+ */
+ vw->vocwflg &= ~VOCFDEL;
+
+ /* we're done */
+ return;
+ }
+ }
+
+ /* make sure the word+object+type record isn't already defined */
+ for (inx = v->vocwlst, vw = vocwget(ctx, inx) ; vw ;
+ inx = vw->vocwnxt, vw = vocwget(ctx, inx))
+ {
+ if (vw->vocwobj == objn && vw->vocwtyp == p
+ && (vw->vocwflg & VOCFCLASS) == (classflg & VOCFCLASS))
+ {
+ /* it matches - don't add a redundant record */
+ return;
+ }
+ }
+
+ /* look in the free list for an available vocwdef */
+ if (ctx->voccxwfre != VOCCXW_NONE)
+ {
+ inx = ctx->voccxwfre;
+ vw = vocwget(ctx, inx); /* get the free vocwdef */
+ ctx->voccxwfre = vw->vocwnxt; /* unlink from free list */
+ }
+ else
+ {
+ /* allocate another page of vocwdef's if necssary */
+ if ((ctx->voccxwalocnt % VOCWPGSIZ) == 0)
+ {
+ int pg = ctx->voccxwalocnt / VOCWPGSIZ;
+
+ /* make sure we haven't exceeded the available page count */
+ if (pg >= VOCWPGMAX) errsig(ctx->voccxerr, ERR_VOCMNPG);
+
+ /* allocate on the new page */
+#ifdef VOCW_IN_CACHE
+ mcmalo(ctx->voccxmem, (ushort)(VOCWPGSIZ * sizeof(vocwdef)),
+ &ctx->voccxwp[pg]);
+ mcmunlck(ctx->voccxmem, ctx->voccxwp[pg]);
+#else
+ ctx->voccxwp[pg] =
+ (vocwdef *)mchalo(ctx->voccxerr,
+ (VOCWPGSIZ * sizeof(vocwdef)),
+ "vocwset");
+#endif
+ }
+
+ /* get the next entry, and increment count of used entries */
+ inx = ctx->voccxwalocnt++;
+ vw = vocwget(ctx, inx);
+ }
+
+ /* link the new vocwdef into the vocdef's relation list */
+ vw->vocwnxt = v->vocwlst;
+ v->vocwlst = inx;
+
+ /* set up the new vocwdef */
+ vw->vocwtyp = (uchar)p;
+ vw->vocwobj = objn;
+ vw->vocwflg = classflg;
+
+ /*
+ * Scan the list and make sure we're not adding a redundant verb.
+ * Don't bother with the warning if this is a class.
+ */
+ if (p == PRP_VERB && (ctx->voccxflg & VOCCXFVWARN)
+ && (vw->vocwflg & VOCFCLASS) == 0)
+ {
+ for (vw2 = vocwget(ctx, v->vocwlst) ; vw2 ;
+ vw2 = vocwget(ctx, vw2->vocwnxt))
+ {
+ /*
+ * if this is a different object, and it's not a class, and
+ * it's defined as a verb, warn about it
+ */
+ if (vw2 != vw
+ && (vw2->vocwflg & VOCFCLASS) == 0
+ && vw2->vocwtyp == PRP_VERB)
+ {
+ if (v->vocln2 != 0)
+ errlog2(ctx->voccxerr, ERR_VOCREVB,
+ ERRTSTR,
+ errstr(ctx->voccxerr,
+ (char *)v->voctxt, v->voclen),
+ ERRTSTR,
+ errstr(ctx->voccxerr,
+ (char *)v->voctxt + v->voclen, v->vocln2));
+ else
+ errlog1(ctx->voccxerr, ERR_VOCREVB,
+ ERRTSTR,
+ errstr(ctx->voccxerr,
+ (char *)v->voctxt, v->voclen));
+ break;
+ }
+ }
+ }
+}
+
+/* set up a vocdef record, and link into hash table */
+static void vocset(voccxdef *ctx, vocdef *v, prpnum p, objnum objn,
+ int classflg, uchar *wrdtxt, int len,
+ uchar *wrd2, int len2)
+{
+ uint hshval = vochsh(wrdtxt, len);
+
+ v->vocnxt = ctx->voccxhsh[hshval];
+ ctx->voccxhsh[hshval] = v;
+
+ v->voclen = len;
+ v->vocln2 = len2;
+ voccpy(v->voctxt, wrdtxt, len);
+ if (wrd2) voccpy(v->voctxt + len, wrd2, len2);
+
+ /* allocate and initialize a vocwdef for the object */
+ vocwset(ctx, v, p, objn, classflg);
+}
+
+/* internal addword - already parsed into two words and have lengths */
+void vocadd2(voccxdef *ctx, prpnum p, objnum objn, int classflg,
+ uchar *wrdtxt, int len, uchar *wrd2, int len2)
+{
+ vocdef *v;
+ vocdef *prv;
+ uint need;
+ uint hshval;
+
+ /* if the word is null, ignore it entirely */
+ if (len == 0 && len2 == 0)
+ return;
+
+ /* look for a vocdef entry with the same word text */
+ hshval = vochsh(wrdtxt, len);
+ for (v = ctx->voccxhsh[hshval] ; v ; v = v->vocnxt)
+ {
+ /* if it matches on both words, use this entry */
+ if (v->voclen == len && !memcmp(wrdtxt, v->voctxt, (size_t)len)
+ && ((!wrd2 && v->vocln2 == 0)
+ || (v->vocln2 == len2 &&
+ !memcmp(wrd2, v->voctxt + len, (size_t)len2))))
+ {
+ vocwset(ctx, v, p, objn, classflg);
+ return;
+ }
+ }
+
+ /* look for a free vocdef entry of the same size */
+ for (prv = (vocdef *)0, v = ctx->voccxfre ; v ; prv = v, v = v->vocnxt)
+ if (v->voclen == len + len2) break;
+
+ if (v)
+ {
+ /* we found something - unlink from free list */
+ if (prv) prv->vocnxt = v->vocnxt;
+ else ctx->voccxfre = v->vocnxt;
+
+ /* reuse the entry */
+ v->vocwlst = VOCCXW_NONE;
+ vocset(ctx, v, p, objn, classflg, wrdtxt, len, wrd2, len2);
+ return;
+ }
+
+ /* didn't find an existing vocdef; allocate a new one */
+ need = sizeof(vocdef) + len + len2 - 1;
+ if (ctx->voccxrem < need)
+ {
+ /* not enough space in current page; allocate a new one */
+ ctx->voccxpool = mchalo(ctx->voccxerr, VOCPGSIZ, "vocadd2");
+ ctx->voccxrem = VOCPGSIZ;
+ }
+
+ /* use top of current pool, and update pool pointer and size */
+ v = (vocdef *)ctx->voccxpool;
+ need = osrndsz(need);
+ ctx->voccxpool += need;
+ if (ctx->voccxrem > need) ctx->voccxrem -= need;
+ else ctx->voccxrem = 0;
+
+ /* set up new vocdef */
+ v->vocwlst = VOCCXW_NONE;
+ vocset(ctx, v, p, objn, classflg, wrdtxt, len, wrd2, len2);
+}
+
+static void voc_parse_words(char **wrdtxt, int *len, char **wrd2, int *len2)
+{
+ /* get length and pointer to actual text */
+ *len = osrp2(*wrdtxt) - 2;
+ *wrdtxt += 2;
+
+ /* see if there's a second word - look for a space */
+ for (*wrd2 = *wrdtxt, *len2 = *len ; *len2 && !vocisspace(**wrd2) ;
+ ++*wrd2, --*len2) ;
+ if (*len2)
+ {
+ *len -= *len2;
+ while (*len2 && vocisspace(**wrd2)) ++*wrd2, --*len2;
+ }
+ else
+ {
+ /* no space ==> no second word */
+ *wrd2 = (char *)0;
+ }
+}
+
+void vocadd(voccxdef *ctx, prpnum p, objnum objn, int classflg, char *wrdtxt)
+{
+ int len;
+ char *wrd2;
+ int len2;
+
+ voc_parse_words(&wrdtxt, &len, &wrd2, &len2);
+ vocadd2(ctx, p, objn, classflg, (uchar *)wrdtxt, len, (uchar *)wrd2, len2);
+}
+
+/* make sure we have a page table entry for an object, allocating one if not */
+void vocialo(voccxdef *ctx, objnum obj)
+{
+ if (!ctx->voccxinh[obj >> 8])
+ {
+ ctx->voccxinh[obj >> 8] =
+ (vocidef **)mchalo(ctx->voccxerr,
+ (256 * sizeof(vocidef *)), "vocialo");
+ memset(ctx->voccxinh[obj >> 8], 0, (size_t)(256 * sizeof(vocidef *)));
+ }
+}
+
+/* add an inheritance/location record */
+void vociadd(voccxdef *ctx, objnum obj, objnum loc,
+ int numsc, objnum *sc, int flags)
+{
+ vocidef *v;
+ vocidef *min;
+ vocidef *prv;
+ vocidef *minprv = nullptr;
+
+ /* make sure we have a page table entry for this object */
+ vocialo(ctx, obj);
+
+ /* look in free list for an entry that's big enough */
+ for (prv = (vocidef *)0, min = (vocidef *)0, v = ctx->voccxifr ; v ;
+ prv = v, v = v->vocinxt)
+ {
+ if (v->vocinsc == numsc)
+ {
+ min = v;
+ minprv = prv;
+ break;
+ }
+ else if (v->vocinsc > numsc)
+ {
+ if (!min || v->vocinsc < min->vocinsc)
+ {
+ min = v;
+ minprv = prv;
+ }
+ }
+ }
+
+ if (!min)
+ {
+ uint need;
+
+ /* nothing in free list; allocate a new entry */
+ need = osrndsz(sizeof(vocidef) + (numsc - 1)*sizeof(objnum));
+ if (ctx->voccxilst + need >= VOCISIZ)
+ {
+ /* nothing left on current page; allocate a new page */
+ ctx->voccxip[++(ctx->voccxiplst)] =
+ mchalo(ctx->voccxerr, VOCISIZ, "vociadd");
+ ctx->voccxilst = 0;
+ }
+
+ /* allocate space out of current page */
+ v = (vocidef *)(ctx->voccxip[ctx->voccxiplst] + ctx->voccxilst);
+ ctx->voccxilst += need;
+ }
+ else
+ {
+ /* unlink from chain and use */
+ v = min;
+ if (minprv)
+ minprv->vocinxt = v->vocinxt;
+ else
+ ctx->voccxifr = v->vocinxt;
+ }
+
+ /* set up the entry */
+ if (vocinh(ctx, obj) != (vocidef *)0) errsig(ctx->voccxerr, ERR_VOCINUS);
+ v->vociloc = loc;
+ v->vociilc = MCMONINV;
+ v->vociflg = (flags & ~VOCIFXLAT);
+ v->vocinsc = numsc;
+ if (numsc)
+ {
+ if (flags & VOCIFXLAT)
+ {
+ int i;
+
+ for (i = 0 ; i < numsc ; ++i)
+ v->vocisc[i] = osrp2(&sc[i]);
+ }
+ else
+ memcpy(v->vocisc, sc, (size_t)(numsc * sizeof(objnum)));
+ }
+ vocinh(ctx, obj) = v; /* set page table entry */
+}
+
+/* revert all objects to original state, using inheritance records */
+void vocrevert(voccxdef *vctx)
+{
+ vocidef ***vpg;
+ vocidef **v;
+ int i;
+ int j;
+ objnum obj;
+
+ /*
+ * Go through the inheritance records. Delete each dynamically
+ * allocated object, and revert each static object to its original
+ * load state.
+ */
+ for (vpg = vctx->voccxinh, i = 0 ; i < VOCINHMAX ; ++vpg, ++i)
+ {
+ if (!*vpg) continue;
+ for (v = *vpg, obj = (i << 8), j = 0 ; j < 256 ; ++v, ++obj, ++j)
+ {
+ if (*v)
+ {
+ /* if the object was dynamically allocated, delete it */
+ if ((*v)->vociflg & VOCIFNEW)
+ {
+ /* delete vocabulary and inheritance data for the object */
+ vocidel(vctx, obj);
+ vocdel(vctx, obj);
+
+ /* delete the object */
+ mcmfre(vctx->voccxmem, (mcmon)obj);
+ }
+ else
+ {
+ /* revert the object */
+ mcmrevert(vctx->voccxmem, (mcmon)obj);
+ }
+ }
+ }
+ }
+
+ /*
+ * Revert the vocabulary list: delete all newly added words, and
+ * undelete all original words marked as deleted.
+ */
+ vocdel1(vctx, MCMONINV, (char *)0, 0, TRUE, TRUE, FALSE);
+}
+
+/* initialize voc context */
+void vocini(voccxdef *vocctx, errcxdef *errctx, mcmcxdef *memctx,
+ runcxdef *runctx, objucxdef *undoctx,
+ int fuses, int daemons, int notifiers)
+{
+ CLRSTRUCT(*vocctx);
+ vocctx->voccxerr = errctx;
+ vocctx->voccxiplst = (uint)-1;
+ vocctx->voccxilst = VOCISIZ;
+ vocctx->voccxmem = memctx;
+ vocctx->voccxrun = runctx;
+ vocctx->voccxundo = undoctx;
+
+ vocctx->voccxme =
+ vocctx->voccxme_init =
+ vocctx->voccxvtk =
+ vocctx->voccxstr =
+ vocctx->voccxnum =
+ vocctx->voccxit =
+ vocctx->voccxhim =
+ vocctx->voccxprd =
+ vocctx->voccxpre =
+ vocctx->voccxpre2 =
+ vocctx->voccxppc =
+ vocctx->voccxlsv =
+ vocctx->voccxpreinit =
+ vocctx->voccxper =
+ vocctx->voccxprom =
+ vocctx->voccxpostprom =
+ vocctx->voccxpdis =
+ vocctx->voccxper2 =
+ vocctx->voccxperp =
+ vocctx->voccxpdef =
+ vocctx->voccxpdef2 =
+ vocctx->voccxpask =
+ vocctx->voccxpask2 =
+ vocctx->voccxpask3 =
+ vocctx->voccxinitrestore =
+ vocctx->voccxpuv =
+ vocctx->voccxpnp =
+ vocctx->voccxpostact =
+ vocctx->voccxendcmd =
+ vocctx->voccxher = MCMONINV;
+ vocctx->voccxthc = 0;
+#ifdef VOCW_IN_CACHE
+ vocctx->voccxwplck = MCMONINV;
+#endif
+
+ vocctx->voccxactor = MCMONINV;
+ vocctx->voccxverb = MCMONINV;
+ vocctx->voccxprep = MCMONINV;
+ vocctx->voccxdobj = 0;
+ vocctx->voccxiobj = 0;
+
+ vocctx->voccxunknown = 0;
+ vocctx->voccxlastunk = 0;
+
+ vocctx->voc_stk_ptr = 0;
+ vocctx->voc_stk_cur = 0;
+ vocctx->voc_stk_end = 0;
+
+ /* allocate fuses, daemons, notifiers */
+ vocinialo(vocctx, &vocctx->voccxfus, (vocctx->voccxfuc = fuses));
+ vocinialo(vocctx, &vocctx->voccxdmn, (vocctx->voccxdmc = daemons));
+ vocinialo(vocctx, &vocctx->voccxalm, (vocctx->voccxalc = notifiers));
+
+ /* no entries in vocwdef free list yet */
+ vocctx->voccxwfre = VOCCXW_NONE;
+}
+
+/* uninitialize the voc context */
+void vocterm(voccxdef *ctx)
+{
+ /* delete the fuses, daemons, and notifiers */
+ voctermfree(ctx->voccxfus);
+ voctermfree(ctx->voccxdmn);
+ voctermfree(ctx->voccxalm);
+
+ /* delete the private stack */
+ if (ctx->voc_stk_ptr != 0)
+ mchfre(ctx->voc_stk_ptr);
+}
+
+/* clean up the vocab context */
+void voctermfree(vocddef *what)
+{
+ if (what != 0)
+ mchfre(what);
+}
+
+/*
+ * Iterate through all words for a particular object, calling a
+ * function with each vocwdef found. If objn == MCMONINV, we'll call
+ * the callback for every word.
+ */
+void voc_iterate(voccxdef *ctx, objnum objn,
+ void (*fn)(void *, vocdef *, vocwdef *), void *fnctx)
+{
+ int i;
+ vocdef *v;
+ vocdef **vp;
+ vocwdef *vw;
+ uint idx;
+
+ /* go through each hash value looking for matching words */
+ for (i = VOCHASHSIZ, vp = ctx->voccxhsh ; i ; ++vp, --i)
+ {
+ /* go through all words in this hash chain */
+ for (v = *vp ; v ; v = v->vocnxt)
+ {
+ /* go through each object relation for this word */
+ for (idx = v->vocwlst, vw = vocwget(ctx, idx) ; vw ;
+ idx = vw->vocwnxt, vw = vocwget(ctx, idx))
+ {
+ /*
+ * if this word is for this object, call the callback
+ */
+ if (objn == MCMONINV || vw->vocwobj == objn)
+ (*fn)(fnctx, v, vw);
+ }
+ }
+ }
+}
+
+/* callback context for voc_count */
+struct voc_count_ctx
+{
+ int cnt;
+ int siz;
+ prpnum prp;
+};
+
+/* callback for voc_count */
+static void voc_count_cb(void *ctx0, vocdef *voc, vocwdef *vocw)
+{
+ struct voc_count_ctx *ctx = (struct voc_count_ctx *)ctx0;
+
+ VARUSED(vocw);
+
+ /*
+ * If it matches the property (or we want all properties), count
+ * it. Don't count deleted objects.
+ */
+ if ((ctx->prp == 0 || ctx->prp == vocw->vocwtyp)
+ && !(vocw->vocwflg & VOCFDEL))
+ {
+ /* count the word */
+ ctx->cnt++;
+
+ /* count the size */
+ ctx->siz += voc->voclen + voc->vocln2;
+ }
+}
+
+/*
+ * Get the number and size of words defined for an object. The size
+ * returns the total byte count from all the words involved. Do not
+ * include deleted words in the count.
+ */
+void voc_count(voccxdef *ctx, objnum objn, prpnum prp, int *cnt, int *siz)
+{
+ struct voc_count_ctx fnctx;
+
+ /* set up the context with zero initial counts */
+ fnctx.cnt = 0;
+ fnctx.siz = 0;
+ fnctx.prp = prp;
+
+ /* iterate over all words for the object with our callback */
+ voc_iterate(ctx, objn, voc_count_cb, &fnctx);
+
+ /* return the data */
+ if (cnt) *cnt = fnctx.cnt;
+ if (siz) *siz = fnctx.siz;
+}
+
+
+/*
+ * Delete a particular word associated with an object, or all words if
+ * the word pointer is null. If the word isn't marked as added at
+ * runtime (i.e., the VOCFNEW flag is not set for the word), the word is
+ * simply marked as deleted, rather than being actually deleted.
+ * However, if the 'really_delete' flag is set, the word is actually
+ * deleted. If the 'revert' flag is true, this routine deletes _every_
+ * dynamically created word, and undeletes all dynamically deleted words
+ * that were in the original vocabulary.
+ */
+void vocdel1(voccxdef *ctx, objnum objn, char *wrd1, prpnum prp,
+ int really_delete, int revert, int keep_undo)
+{
+ int i;
+ vocdef *v;
+ vocdef *prv;
+ vocdef *nxt;
+ vocdef **vp;
+ vocwdef *vw;
+ vocwdef *prvw;
+ vocwdef *nxtw;
+ uint nxtidx;
+ uint idx;
+ int deleted_vocdef;
+ char *wrd2 = nullptr;
+ int len1 = 0, len2 = 0;
+ int do_del;
+ char *orgwrd;
+
+ /* parse the word if provided */
+ orgwrd = wrd1;
+ if (wrd1)
+ voc_parse_words(&wrd1, &len1, &wrd2, &len2);
+
+ /* go through each hash value looking for matching words */
+ for (i = VOCHASHSIZ, vp = ctx->voccxhsh ; i ; ++vp, --i)
+ {
+ /* go through all words in this hash chain */
+ for (prv = (vocdef *)0, v = *vp ; v ; v = nxt)
+ {
+ /* remember next word in hash chain */
+ nxt = v->vocnxt;
+
+ /* if this word doesn't match, skip it */
+ if (wrd1)
+ {
+ /* compare the first word */
+ if (v->voclen != len1
+ || memicmp((char *)v->voctxt, wrd1, (size_t)len1))
+ {
+ prv = v;
+ continue;
+ }
+
+ /* if there's a second word, compare it as well */
+ if (wrd2 && (v->vocln2 != len2
+ || memicmp((char *)v->voctxt + len1,
+ wrd2, (size_t)len2)))
+ {
+ prv = v;
+ continue;
+ }
+ }
+
+ /* presume we're not going to delete this vocdef */
+ deleted_vocdef = FALSE;
+
+ /* go through all object relations for this word */
+ for (prvw = 0, idx = v->vocwlst, vw = vocwget(ctx, idx) ; vw ;
+ vw = nxtw, idx = nxtidx)
+ {
+ /* remember next word in relation list */
+ nxtidx = vw->vocwnxt;
+ nxtw = vocwget(ctx, nxtidx);
+
+ /*
+ * figure out whether to delete this word, based on the
+ * caller's specified operating mode
+ */
+ if (revert)
+ {
+ /* if reverting, delete all new words */
+ do_del = (vw->vocwflg & VOCFNEW);
+
+ /* also, remove the DELETED flag if present */
+ vw->vocwflg &= ~VOCFDEL;
+ }
+ else
+ {
+ /*
+ * delete the word if the object number matches,
+ * AND either we're not searching for a specific
+ * vocabulary word (in which case wrd1 will be null)
+ * or the word matches the vocabulary word we're
+ * seeking (in which case the part of speech must
+ * match)
+ */
+ do_del = (vw->vocwobj == objn
+ && (wrd1 == 0 || vw->vocwtyp == prp));
+
+ /*
+ * if we're not in really_delete mode, and the word
+ * matches and it wasn't dynamically added at
+ * run-time, simply mark it as deleted -- this will
+ * allow it to be undeleted if the game is reverted
+ * to RESTART conditions
+ */
+ if (do_del && !really_delete && !(vw->vocwflg & VOCFNEW))
+ {
+ /* geneate undo for the operation */
+ if (keep_undo && orgwrd)
+ vocdusave_delwrd(ctx, objn, prp,
+ vw->vocwflg, orgwrd);
+
+ /* just mark the word for deletion, but keep vw */
+ vw->vocwflg |= VOCFDEL;
+ do_del = FALSE;
+ }
+ }
+
+ /* now delete the structure if we decided we should */
+ if (do_del)
+ {
+ /* geneate undo for the operation */
+ if (keep_undo && orgwrd)
+ vocdusave_delwrd(ctx, objn, prp, vw->vocwflg, orgwrd);
+
+ /* unlink this vocwdef from the vocdef's list */
+ if (prvw)
+ prvw->vocwnxt = vw->vocwnxt;
+ else
+ v->vocwlst = vw->vocwnxt;
+
+ /* link the vocwdef into the vocwdef free list */
+ vw->vocwnxt = ctx->voccxwfre;
+ ctx->voccxwfre = idx;
+
+ /*
+ * if there's nothing left in the vocdef's list,
+ * delete the entire vocdef as well
+ */
+ if (v->vocwlst == VOCCXW_NONE)
+ {
+ if (prv) prv->vocnxt = v->vocnxt;
+ else *vp = v->vocnxt;
+
+ /* link into free chain */
+ v->vocnxt = ctx->voccxfre;
+ ctx->voccxfre = v;
+
+ /* note that it's been deleted */
+ deleted_vocdef = TRUE;
+ }
+ }
+ else
+ {
+ /* we're not deleting the word, so move prvw forward */
+ prvw = vw;
+ }
+ }
+
+ /* if we didn't delete this vocdef, move prv forward onto it */
+ if (!deleted_vocdef)
+ prv = v;
+ }
+ }
+}
+
+/* delete all vocabulary for an object */
+void vocdel(voccxdef *ctx, objnum objn)
+{
+ vocdel1(ctx, objn, (char *)0, (prpnum)0, TRUE, FALSE, FALSE);
+}
+
+/* delete object inheritance records for a particular object */
+void vocidel(voccxdef *ctx, objnum obj)
+{
+ vocidef *v;
+
+ /* get entry out of page table, and clear page table slot */
+ v = vocinh(ctx, obj);
+ vocinh(ctx, obj) = (vocidef *)0;
+
+ /* link into free list */
+ if (v)
+ {
+ v->vocinxt = ctx->voccxifr;
+ ctx->voccxifr = v;
+ }
+}
+
+/*
+ * Find template matching a verb+object+prep combination; return TRUE
+ * if a suitable template is found, FALSE otherwise.
+ */
+int voctplfnd(voccxdef *ctx, objnum verb_in, objnum prep,
+ uchar *tplout, int *newstyle)
+{
+ uchar *tplptr;
+ uchar *thistpl;
+ int found;
+ int tplcnt;
+ uint tplofs;
+ objnum verb;
+
+ /* look for a new-style template first */
+ tplofs = objgetap(ctx->voccxmem, verb_in, PRP_TPL2, &verb, FALSE);
+ if (tplofs)
+ {
+ /* flag the presence of the new-style template */
+ *newstyle = TRUE;
+ }
+ else
+ {
+ /* no new-style template - look for old version */
+ tplofs = objgetap(ctx->voccxmem, verb_in, PRP_TPL, &verb, FALSE);
+ *newstyle = FALSE;
+ }
+
+ /* inherit templates until we run out of them */
+ for (;;)
+ {
+ /* if we found something already, use it */
+ if (tplofs)
+ {
+ size_t siz;
+
+ /* figure the size of this template style */
+ siz = (*newstyle ? VOCTPL2SIZ : VOCTPLSIZ);
+
+ /* lock the verb object, and get the property value pointer */
+ tplptr = mcmlck(ctx->voccxmem, verb);
+ thistpl = prpvalp(tplptr + tplofs);
+
+ /* first byte is number of templates in array */
+ tplcnt = *thistpl++;
+
+ /* look for a template that matches the preposition object */
+ for (found = FALSE ; tplcnt ; thistpl += siz, --tplcnt)
+ {
+ if (voctplpr(thistpl) == prep)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ /* unlock the object and return the value if we found one */
+ mcmunlck(ctx->voccxmem, verb);
+ if (found)
+ {
+ memcpy(tplout, thistpl, siz);
+ return(TRUE);
+ }
+ }
+
+ /* try inheriting a template (new-style, then old-style) */
+ tplofs = objgetap(ctx->voccxmem, verb_in, PRP_TPL2, &verb, TRUE);
+ if (tplofs)
+ *newstyle = TRUE;
+ else
+ {
+ tplofs = objgetap(ctx->voccxmem, verb_in, PRP_TPL, &verb, TRUE);
+ *newstyle = FALSE;
+ }
+
+ /* return not-found if we couldn't inherit it */
+ if (!tplofs)
+ return FALSE;
+
+ /* use the newly found verb */
+ verb_in = verb;
+ }
+}
+
+/*
+ * Set the "Me" object
+ */
+void voc_set_me(voccxdef *ctx, objnum new_me)
+{
+ /* save undo for the change */
+ vocdusave_me(ctx, ctx->voccxme);
+
+ /* set the new "Me" object */
+ ctx->voccxme = new_me;
+}
} // End of namespace TADS2
} // End of namespace TADS
diff --git a/engines/glk/tads/tads2/vocabulary.h b/engines/glk/tads/tads2/vocabulary.h
index 5574675aa2..f4c058edaf 100644
--- a/engines/glk/tads/tads2/vocabulary.h
+++ b/engines/glk/tads/tads2/vocabulary.h
@@ -27,6 +27,7 @@
#ifndef GLK_TADS_TADS2_VOCABULARY
#define GLK_TADS_TADS2_VOCABULARY
+#include "common/util.h"
#include "glk/tads/tads2/lib.h"
#include "glk/tads/tads2/object.h"
#include "glk/tads/tads2/property.h"
@@ -645,11 +646,11 @@ void vocdusave_me(voccxdef *ctx, objnum old_me);
uint vochsh(uchar *t, int len);
/* TADS versions of isalpha, isspace, isdigit, etc */
-#define vocisupper(c) ((uchar)(c) <= 127 && isupper((uchar)(c)))
-#define vocislower(c) ((uchar)(c) <= 127 && islower((uchar)(c)))
-#define vocisalpha(c) ((uchar)(c) > 127 || isalpha((uchar)(c)))
-#define vocisspace(c) ((uchar)(c) <= 127 && isspace((uchar)(c)))
-#define vocisdigit(c) ((uchar)(c) <= 127 && isdigit((uchar)(c)))
+#define vocisupper(c) ((uchar)(c) <= 127 && Common::isUpper((uchar)(c)))
+#define vocislower(c) ((uchar)(c) <= 127 && Common::isLower((uchar)(c)))
+#define vocisalpha(c) ((uchar)(c) > 127 || Common::isAlpha((uchar)(c)))
+#define vocisspace(c) ((uchar)(c) <= 127 && Common::isSpace((uchar)(c)))
+#define vocisdigit(c) ((uchar)(c) <= 127 && Common::isDigit((uchar)(c)))
/*