From e83972f502142b4e6191b962a7be89829bd8d708 Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Fri, 17 May 2019 11:02:01 -1000 Subject: GLK: TADS2: Added code for tokenizer & vocabulary --- engines/glk/module.mk | 1 + engines/glk/tads/osfrobtads.cpp | 14 +- engines/glk/tads/osfrobtads.h | 24 +- engines/glk/tads/tads2/file_io.h | 19 +- engines/glk/tads/tads2/lib.h | 2 +- engines/glk/tads/tads2/line_source_file.cpp | 33 + engines/glk/tads/tads2/line_source_file.h | 170 +++ engines/glk/tads/tads2/tads2.cpp | 3 +- engines/glk/tads/tads2/tokenizer.cpp | 1526 ++++++++++++++++++++++++++- engines/glk/tads/tads2/tokenizer.h | 11 +- engines/glk/tads/tads2/vocabulary.cpp | 902 +++++++++++++++- engines/glk/tads/tads2/vocabulary.h | 11 +- 12 files changed, 2685 insertions(+), 31 deletions(-) create mode 100644 engines/glk/tads/tads2/line_source_file.cpp create mode 100644 engines/glk/tads/tads2/line_source_file.h 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(fp)->read(buf, count); +} + +bool osfwb(osfildef *fp, void *buf, size_t count) { + return dynamic_cast(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 <> 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))) /* -- cgit v1.2.3