aboutsummaryrefslogtreecommitdiff
path: root/engines/glk/tads
diff options
context:
space:
mode:
authorPaul Gilbert2019-05-17 15:52:13 -1000
committerPaul Gilbert2019-05-24 18:21:06 -0700
commitfcb2592ec24f983c49f1e2e1b4f41cc9659a9229 (patch)
tree6f90f0a47a637d4fcb52ff437e47c94855ab2f91 /engines/glk/tads
parentf607792fa4e1f024dd8265034ac84425bde4aee7 (diff)
downloadscummvm-rg350-fcb2592ec24f983c49f1e2e1b4f41cc9659a9229.tar.gz
scummvm-rg350-fcb2592ec24f983c49f1e2e1b4f41cc9659a9229.tar.bz2
scummvm-rg350-fcb2592ec24f983c49f1e2e1b4f41cc9659a9229.zip
GLK: TADS2: Added built in functions
Diffstat (limited to 'engines/glk/tads')
-rw-r--r--engines/glk/tads/os_glk.h3
-rw-r--r--engines/glk/tads/osfrobtads.cpp4
-rw-r--r--engines/glk/tads/osfrobtads.h4
-rw-r--r--engines/glk/tads/tads2/built_in.cpp4333
-rw-r--r--engines/glk/tads/tads2/debug.cpp522
-rw-r--r--engines/glk/tads/tads2/error.cpp174
-rw-r--r--engines/glk/tads/tads2/error_handling.cpp179
-rw-r--r--engines/glk/tads/tads2/os.cpp323
-rw-r--r--engines/glk/tads/tads2/string_resources.h69
9 files changed, 5288 insertions, 323 deletions
diff --git a/engines/glk/tads/os_glk.h b/engines/glk/tads/os_glk.h
index e58868b8b7..75354eacef 100644
--- a/engines/glk/tads/os_glk.h
+++ b/engines/glk/tads/os_glk.h
@@ -31,6 +31,9 @@
namespace Glk {
namespace TADS {
+#define TADS_RUNTIME_VERSION "2.5.17"
+#define OS_SYSTEM_NAME "ScummGlk"
+
#define OSPATHCHAR '/'
#define OSPATHALT ""
#define OSPATHURL "/"
diff --git a/engines/glk/tads/osfrobtads.cpp b/engines/glk/tads/osfrobtads.cpp
index eabd297e6e..6dcaadb356 100644
--- a/engines/glk/tads/osfrobtads.cpp
+++ b/engines/glk/tads/osfrobtads.cpp
@@ -81,6 +81,10 @@ char *osfgets(char *buf, size_t count, osfildef *fp) {
return buf;
}
+int osfputs(const char *str, osfildef *fp) {
+ return dynamic_cast<Common::WriteStream *>(fp)->write(str, strlen(str)) == strlen(str) ? 0 : -1;
+}
+
bool os_locate(const char *fname, int flen, const char *arg0, char *buf, size_t bufsiz) {
Common::String name = !flen ? Common::String(fname) : Common::String(fname, fname + flen);
diff --git a/engines/glk/tads/osfrobtads.h b/engines/glk/tads/osfrobtads.h
index 5c9bcd8bd0..d503a7b023 100644
--- a/engines/glk/tads/osfrobtads.h
+++ b/engines/glk/tads/osfrobtads.h
@@ -168,7 +168,7 @@ int osfmode( const char* fname, int follow_links, unsigned long* mode,
#define osrealloc realloc
/* Open text file for reading. */
-#define osfoprt(fname,typ) (fopen((fname),"r"))
+#define osfoprt(fname,typ) osfoprb(fname,typ)
/* Open text file for writing. */
osfildef *osfopwt(const char *fname, os_filetype_t typ);
@@ -208,7 +208,7 @@ inline osfildef *osfoprwtb(const char *fname, os_filetype_t typ);
char *osfgets(char *buf, size_t count, osfildef *fp);
/* Write a line of text to a text file. */
-#define osfputs fputs
+int osfputs(const char *str, osfildef *fp);
/* Write bytes to file. */
inline bool osfwb(osfildef *fp, const void *buf, size_t count);
diff --git a/engines/glk/tads/tads2/built_in.cpp b/engines/glk/tads/tads2/built_in.cpp
new file mode 100644
index 0000000000..30c2772b78
--- /dev/null
+++ b/engines/glk/tads/tads2/built_in.cpp
@@ -0,0 +1,4333 @@
+/* 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/built_in.h"
+#include "glk/tads/tads2/appctx.h"
+#include "glk/tads/tads2/character_map.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/file_io.h"
+#include "glk/tads/tads2/list.h"
+#include "glk/tads/tads2/os.h"
+#include "glk/tads/tads2/run.h"
+#include "glk/tads/tads2/string_resources.h"
+#include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/os_glk.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+/* yorn - yes or no */
+void bifyon(bifcxdef *ctx, int argc)
+{
+ char rsp[128];
+ char *p;
+ runsdef val;
+ char yesbuf[64];
+ char nobuf[64];
+ re_context rectx;
+ int match_yes;
+ int match_no;
+
+ bifcntargs(ctx, 0, argc); /* check for proper argument count */
+
+ /* load the "yes" and "no" reply patterns */
+ if (os_get_str_rsc(RESID_YORN_YES, yesbuf, sizeof(yesbuf)))
+ strcpy(yesbuf, "[Yy].*");
+ if (os_get_str_rsc(RESID_YORN_NO, nobuf, sizeof(nobuf)))
+ strcpy(nobuf, "[Nn].*");
+
+ /* if we're in HTML mode, switch to input font */
+ if (tio_is_html_mode())
+ tioputs(ctx->bifcxtio, "<font face='TADS-Input'>");
+
+ /* ensure the prompt is displayed */
+ tioflushn(ctx->bifcxtio, 0);
+
+ /* reset count of lines since the last keyboard input */
+ tioreset(ctx->bifcxtio);
+
+ /* read a line of text */
+ if (tiogets(ctx->bifcxtio, (char *)0, rsp, (int)sizeof(rsp)))
+ runsig(ctx->bifcxrun, ERR_RUNQUIT);
+
+ /* if we're in HTML mode, close the input font tag */
+ if (tio_is_html_mode())
+ tioputs(ctx->bifcxtio, "</font>");
+
+ /* scan off leading spaces */
+ for (p = rsp ; t_isspace(*p) ; ++p) ;
+
+ /* set up our regex context */
+ re_init(&rectx, ctx->bifcxerr);
+
+ /* check for a "yes" response */
+ match_yes = re_compile_and_match(&rectx, yesbuf, strlen(yesbuf),
+ p, strlen(p));
+
+ /* check for a "no" response */
+ match_no = re_compile_and_match(&rectx, nobuf, strlen(nobuf),
+ p, strlen(p));
+
+ /* check the result */
+ if (match_yes == (int)strlen(p))
+ val.runsv.runsvnum = 1;
+ else if (match_no == (int)strlen(p))
+ val.runsv.runsvnum = 0;
+ else
+ val.runsv.runsvnum = -1;
+
+ /* delete our regex context */
+ re_delete(&rectx);
+
+ /* push the result */
+ runpush(ctx->bifcxrun, DAT_NUMBER, &val);
+}
+
+/* setfuse */
+void bifsfs(bifcxdef *ctx, int argc)
+{
+ objnum func;
+ uint tm;
+ runsdef val;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ bifcntargs(ctx, 3, argc); /* check for proper argument count */
+ func = runpopfn(ctx->bifcxrun);
+ tm = runpopnum(ctx->bifcxrun);
+ runpop(ctx->bifcxrun, &val);
+
+ /* limitation: don't allow string or list for value */
+ if (val.runstyp == DAT_LIST || val.runstyp == DAT_SSTRING)
+ runsig(ctx->bifcxrun, ERR_FUSEVAL);
+
+ vocsetfd(voc, voc->voccxfus, func, (prpnum)0,
+ tm, &val, ERR_MANYFUS);
+}
+
+/* remfuse */
+void bifrfs(bifcxdef *ctx, int argc)
+{
+ objnum func;
+ runsdef val;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ bifcntargs(ctx, 2, argc);
+ func = runpopfn(ctx->bifcxrun);
+ runpop(ctx->bifcxrun, &val);
+ vocremfd(voc, voc->voccxfus, func, (prpnum)0,
+ &val, ERR_NOFUSE);
+}
+
+/* setdaemon */
+void bifsdm(bifcxdef *ctx, int argc)
+{
+ objnum func;
+ runsdef val;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ bifcntargs(ctx, 2, argc); /* check for proper argument count */
+ func = runpopfn(ctx->bifcxrun);
+ runpop(ctx->bifcxrun, &val);
+
+ /* limitation: don't allow string or list for value */
+ if (val.runstyp == DAT_LIST || val.runstyp == DAT_SSTRING)
+ runsig(ctx->bifcxrun, ERR_FUSEVAL);
+
+ vocsetfd(voc, voc->voccxdmn, func, (prpnum)0, 0,
+ &val, ERR_MANYDMN);
+}
+
+/* remdaemon */
+void bifrdm(bifcxdef *ctx, int argc)
+{
+ objnum func;
+ runsdef val;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ bifcntargs(ctx, 2, argc);
+ func = runpopfn(ctx->bifcxrun);
+ runpop(ctx->bifcxrun, &val);
+ vocremfd(voc, voc->voccxdmn, func, (prpnum)0,
+ &val, ERR_NODMN);
+}
+
+/* incturn */
+void bifinc(bifcxdef *ctx, int argc)
+{
+ int turncnt;
+
+ if (argc == 1)
+ {
+ /* get the number of turns to skip */
+ turncnt = runpopnum(ctx->bifcxrun);
+ if (turncnt < 1)
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "incturn");
+ }
+ else
+ {
+ /* no arguments -> increment by one turn */
+ bifcntargs(ctx, 0, argc);
+ turncnt = 1;
+ }
+
+ /* skip the given number of turns */
+ vocturn(ctx->bifcxrun->runcxvoc, turncnt, TRUE);
+}
+
+/* skipturn */
+void bifskt(bifcxdef *ctx, int argc)
+{
+ int turncnt;
+
+ bifcntargs(ctx, 1, argc);
+ turncnt = runpopnum(ctx->bifcxrun);
+ if (turncnt < 1)
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "skipturn");
+ vocturn(ctx->bifcxrun->runcxvoc, turncnt, FALSE);
+}
+
+/* quit */
+void bifqui(bifcxdef *ctx, int argc)
+{
+ /* check for proper argument count */
+ bifcntargs(ctx, 0, argc);
+
+ /* flush output buffer, and signal the end of the game */
+ tioflush(ctx->bifcxtio);
+ errsig(ctx->bifcxerr, ERR_RUNQUIT);
+}
+
+/* internal function to convert a TADS string into a C-string */
+static void bifcstr(bifcxdef *ctx, char *buf, size_t bufsiz, uchar *str)
+{
+ size_t srcrem;
+ size_t dstrem;
+ uchar *src;
+ char *dst;
+
+ /* get the length and text portion of the string */
+ srcrem = osrp2(str) - 2;
+ str += 2;
+
+ /* scan the string, and convert escapes */
+ for (src = str, dst = buf, dstrem = bufsiz ;
+ srcrem != 0 && dstrem != 0 ; ++src, --srcrem)
+ {
+ /* if we have an escape sequence, convert it */
+ if (*src == '\\')
+ {
+ /* skip the backslash in the input */
+ ++src;
+ --srcrem;
+
+ /* if there's nothing left, store the backslash */
+ if (srcrem == 0)
+ {
+ /* store the backslash */
+ *dst++ = '\\';
+ --dstrem;
+
+ /* there's nothing left to scan */
+ break;
+ }
+
+ /* see what the second half of the escape sequence is */
+ switch(*src)
+ {
+ case 'n':
+ /* store a C-style newline character */
+ *dst++ = '\n';
+ --dstrem;
+ break;
+
+ case 't':
+ /* store a C-style tab */
+ *dst++ = '\t';
+ --dstrem;
+ break;
+
+ case '(':
+ case ')':
+ /* entirely omit the highlighting sequences */
+ break;
+
+ default:
+ /* store everything else unchanged */
+ *dst++ = *src;
+ --dstrem;
+ break;
+ }
+ }
+ else
+ {
+ /* copy this character unchanged */
+ *dst++ = *src;
+ --dstrem;
+ }
+ }
+
+ /* if the buffer wasn't big enough, signal an error */
+ if (dstrem == 0)
+ runsig(ctx->bifcxrun, ERR_BIFCSTR);
+
+ /* null-terminate the result string */
+ *dst = '\0';
+}
+
+/* save */
+void bifsav(bifcxdef *ctx, int argc)
+{
+ uchar *fn;
+ char buf[OSFNMAX];
+ int err;
+ runsdef val;
+
+ bifcntargs(ctx, 1, argc);
+ fn = runpopstr(ctx->bifcxrun);
+ bifcstr(ctx, buf, (size_t)sizeof(buf), fn);
+ os_defext(buf, ctx->bifcxsavext != 0 ? ctx->bifcxsavext : "sav");
+ err = fiosav(ctx->bifcxrun->runcxvoc, buf, ctx->bifcxrun->runcxgamename);
+ runpush(ctx->bifcxrun, runclog(err), &val);
+}
+
+/* restore */
+void bifrso(bifcxdef *ctx, int argc)
+{
+ uchar *fn;
+ char buf[OSFNMAX];
+ int err;
+ voccxdef *vctx = ctx->bifcxrun->runcxvoc;
+
+ bifcntargs(ctx, 1, argc);
+
+ /* check for special restore(nil) - restore game given as parameter */
+ if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+ {
+ /* get filename from startup parameter, if any */
+ if (!os_paramfile(buf))
+ {
+ /* no startup parameter */
+ runpnum(ctx->bifcxrun, FIORSO_NO_PARAM_FILE);
+ return;
+ }
+ }
+ else
+ {
+ /* get string parameter - it's the filename */
+ fn = runpopstr(ctx->bifcxrun);
+ bifcstr(ctx, buf, (size_t)sizeof(buf), fn);
+ os_defext(buf, ctx->bifcxsavext != 0 ? ctx->bifcxsavext : "sav");
+ }
+
+ /* try restoring the file */
+ err = fiorso(vctx, buf);
+
+ /* blow away all undo records */
+ objulose(vctx->voccxundo);
+
+ /* return the result code from fiorso */
+ runpnum(ctx->bifcxrun, err);
+
+ /* note that the rest of the command line is to be ignored */
+ vctx->voccxflg |= VOCCXFCLEAR;
+}
+
+/* logging */
+void biflog(bifcxdef *ctx, int argc)
+{
+ char buf[OSFNMAX];
+ uchar *str;
+
+ bifcntargs(ctx, 1, argc);
+ if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+ {
+ rundisc(ctx->bifcxrun);
+ tiologcls(ctx->bifcxtio);
+ }
+ else
+ {
+ str = runpopstr(ctx->bifcxrun);
+ bifcstr(ctx, buf, (size_t)sizeof(buf), str);
+ tiologopn(ctx->bifcxtio, buf);
+ }
+}
+
+/* restart */
+void bifres(bifcxdef *ctx, int argc)
+{
+ voccxdef *vctx = ctx->bifcxrun->runcxvoc;
+ objnum fn;
+
+ if (argc == 2)
+ fn = runpopfn(ctx->bifcxrun); /* get function if present */
+ else
+ {
+ bifcntargs(ctx, 0, argc); /* check for proper argument count */
+ fn = MCMONINV; /* no function was specified */
+ }
+
+ objulose(vctx->voccxundo); /* blow away all undo records */
+ vocrevert(vctx); /* revert all objects to original state */
+ vocdmnclr(vctx); /* clear out fuses/deamons/notifiers */
+
+ /* restore the original "Me" object */
+ vctx->voccxme = vctx->voccxme_init;
+
+ /* call preinit if necessary (call it before invoking the user callback) */
+ if (vctx->voccxpreinit != MCMONINV)
+ runfn(ctx->bifcxrun, vctx->voccxpreinit, 0);
+
+ /*
+ * If a restart function was provided, call it. Note that we left
+ * the argument for the function on the stack, so there's no need to
+ * re-push it!
+ */
+ if (fn != MCMONINV) runfn(ctx->bifcxrun, fn, 1);
+
+ /* restart the game */
+ errsig(ctx->bifcxerr, ERR_RUNRESTART);
+}
+
+/* input - get a line of input from the keyboard */
+void bifinp(bifcxdef *ctx, int argc)
+{
+ char inbuf[128];
+
+ /* check for proper argument count */
+ bifcntargs(ctx, 0, argc);
+
+ /* make sure the prompt is displayed */
+ tioflushn(ctx->bifcxtio, 0);
+
+ /* reset count of lines since the last keyboard input */
+ tioreset(ctx->bifcxtio);
+
+ /* read a line of text */
+ if (tiogets(ctx->bifcxtio, (char *)0, inbuf, (int)sizeof(inbuf)))
+ runsig(ctx->bifcxrun, ERR_RUNQUIT);
+
+ /* push the string, converting escapes */
+ runpushcstr(ctx->bifcxrun, inbuf, strlen(inbuf), 0);
+}
+
+/* notify */
+void bifnfy(bifcxdef *ctx, int argc)
+{
+ objnum objn;
+ prpnum prp;
+ uint tm;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ bifcntargs(ctx, 3, argc); /* check for proper argument count */
+ objn = runpopobj(ctx->bifcxrun);
+ prp = runpopprp(ctx->bifcxrun);
+ tm = runpopnum(ctx->bifcxrun);
+
+ /* a time of zero means every turn */
+ if (tm == 0)
+ tm = VOCDTIM_EACH_TURN;
+
+ vocsetfd(voc, voc->voccxalm, objn, prp, tm,
+ (runsdef *)0, ERR_MANYNFY);
+}
+
+
+/* unnotify */
+void bifunn(bifcxdef *ctx, int argc)
+{
+ objnum objn;
+ prpnum prop;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ bifcntargs(ctx, 2, argc);
+ objn = runpopobj(ctx->bifcxrun);
+ prop = runpopprp(ctx->bifcxrun);
+ vocremfd(voc, voc->voccxalm, objn, prop,
+ (runsdef *)0, ERR_NONFY);
+}
+
+/* trace on/off */
+void biftrc(bifcxdef *ctx, int argc)
+{
+ runsdef val;
+ int n;
+ int flag;
+
+ if (argc == 2)
+ {
+ /* get the type indicator and the on/off status */
+ n = runpopnum(ctx->bifcxrun);
+ flag = runpoplog(ctx->bifcxrun);
+
+ /* see what type of debugging they want to turn on or off */
+ switch(n)
+ {
+ case 1:
+ /* turn on parser tracing */
+ if (flag)
+ ctx->bifcxrun->runcxvoc->voccxflg |= VOCCXFDBG;
+ else
+ ctx->bifcxrun->runcxvoc->voccxflg &= ~VOCCXFDBG;
+ break;
+
+ default:
+ /* ignore other requests */
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "debugTrace");
+ }
+ }
+ else
+ {
+ /* break into debugger; return whether debugger is present */
+ bifcntargs(ctx, 0, argc);
+ runpush(ctx->bifcxrun, runclog(dbgstart(ctx->bifcxrun->runcxdbg)),
+ &val);
+ }
+}
+
+/* say */
+void bifsay(bifcxdef *ctx, int argc)
+{
+ uchar *str;
+ long num;
+ char numbuf[30];
+
+ if (argc != 2) bifcntargs(ctx, 1, argc);
+
+ switch(runtostyp(ctx->bifcxrun))
+ {
+ case DAT_NUMBER:
+ num = runpopnum(ctx->bifcxrun);
+ sprintf(numbuf, "%ld", num);
+ tioputs(ctx->bifcxtio, numbuf);
+ break;
+
+ case DAT_SSTRING:
+ str = runpopstr(ctx->bifcxrun);
+ outfmt(ctx->bifcxtio, str);
+ break;
+
+ case DAT_NIL:
+ (void)runpoplog(ctx->bifcxrun);
+ break;
+
+ default:
+ runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "say");
+ }
+}
+
+/* car */
+void bifcar(bifcxdef *ctx, int argc)
+{
+ uchar *lstp;
+ uint lstsiz;
+ runsdef val;
+
+ bifcntargs(ctx, 1, argc);
+ bifchkarg(ctx, DAT_LIST);
+
+ lstp = runpoplst(ctx->bifcxrun);
+
+ /* get list's size, and point to its data string */
+ lstsiz = osrp2(lstp) - 2;
+ lstp += 2;
+
+ /* push first element if one is present, otherwise push nil */
+ if (lstsiz)
+ runpbuf(ctx->bifcxrun, *lstp, lstp+1);
+ else
+ runpush(ctx->bifcxrun, DAT_NIL, &val);
+}
+
+/* cdr */
+void bifcdr(bifcxdef *ctx, int argc)
+{
+ uchar *lstp;
+ uint siz;
+ uint lstsiz;
+ runsdef val;
+ runsdef stkval;
+
+ bifcntargs(ctx, 1, argc);
+ bifchkarg(ctx, DAT_LIST);
+
+ lstp = runpoplst(ctx->bifcxrun);
+ stkval.runstyp = DAT_LIST;
+ stkval.runsv.runsvstr = lstp;
+
+ /* get list's size, and point to its data string */
+ lstsiz = osrp2(lstp) - 2;
+ lstp += 2;
+
+ if (lstsiz != 0)
+ {
+ /* deduct size of first element from size of list */
+ siz = datsiz(*lstp, lstp+1) + 1;
+ lstsiz -= siz;
+
+ /* add in the size prefix for our new list size */
+ lstsiz += 2;
+
+ /* allocate space for new list containing rest of list */
+ runhres1(ctx->bifcxrun, lstsiz, 1, &stkval);
+ lstp = stkval.runsv.runsvstr + siz + 2;
+
+ /* write out size followed by list value string */
+ oswp2(ctx->bifcxrun->runcxhp, lstsiz);
+ memcpy(ctx->bifcxrun->runcxhp+2, lstp, (size_t)(lstsiz-2));
+
+ val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+ val.runstyp = DAT_LIST;
+ ctx->bifcxrun->runcxhp += lstsiz;
+ runrepush(ctx->bifcxrun, &val);
+ }
+ else
+ runpush(ctx->bifcxrun, DAT_NIL, &val); /* empty list - cdr is nil */
+}
+
+/* caps */
+void bifcap(bifcxdef *ctx, int argc)
+{
+ bifcntargs(ctx, 0, argc);
+ tiocaps(ctx->bifxtio); /* set output driver next-char-capitalized flag */
+}
+
+/* nocaps */
+void bifnoc(bifcxdef *ctx, int argc)
+{
+ bifcntargs(ctx, 0, argc);
+ tionocaps(ctx->bifxtio); /* set next-not-capitalized flag */
+}
+
+/* length */
+void biflen(bifcxdef *ctx, int argc)
+{
+ uchar *p;
+ runsdef val;
+ long len = 0;
+ int l;
+
+ bifcntargs(ctx, 1, argc);
+ switch(runtostyp(ctx->bifcxrun))
+ {
+ case DAT_SSTRING:
+ p = (uchar *)runpopstr(ctx->bifcxrun);
+ len = osrp2(p) - 2;
+ break;
+
+ case DAT_LIST:
+ p = runpoplst(ctx->bifcxrun);
+ l = osrp2(p) - 2;
+ p += 2;
+
+ /* count all elements in list */
+ for (len = 0 ; l ; ++len)
+ {
+ int cursiz;
+
+ /* get size of this element, and move past it */
+ cursiz = datsiz(*p, p+1) + 1;
+ l -= cursiz;
+ p += cursiz;
+ }
+ break;
+
+ default:
+ runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "length");
+ }
+
+ val.runsv.runsvnum = len;
+ runpush(ctx->bifcxrun, DAT_NUMBER, &val);
+}
+
+/* find */
+void biffnd(bifcxdef *ctx, int argc)
+{
+ uchar *p1, *p2;
+ int len1, len2;
+ int outv = 0;
+ runsdef val;
+ int typ = 0;
+ int siz;
+
+ bifcntargs(ctx, 2, argc);
+ switch(runtostyp(ctx->bifcxrun))
+ {
+ case DAT_SSTRING:
+ p1 = runpopstr(ctx->bifcxrun);
+ len1 = osrp2(p1) - 2;
+ p1 += 2;
+
+ p2 = runpopstr(ctx->bifcxrun);
+ len2 = osrp2(p2) - 2;
+ p2 += 2;
+
+ /* look for p2 within p1 */
+ for (typ = DAT_NIL, outv = 1 ; len1 >= len2 ; ++p1, --len1, ++outv)
+ {
+ if (!memcmp(p1, p2, (size_t)len2))
+ {
+ typ = DAT_NUMBER; /* use number in outv after all */
+ break; /* that's it - we've found it */
+ }
+ }
+ break;
+
+ case DAT_LIST:
+ p1 = runpoplst(ctx->bifcxrun);
+ len1 = osrp2(p1) - 2;
+ p1 += 2;
+
+ /* get second item: any old datatype */
+ runpop(ctx->bifcxrun, &val);
+
+ for (typ = DAT_NIL, outv = 1 ; len1 ; ++outv, p1 += siz, len1 -= siz)
+ {
+ siz = datsiz(*p1, p1 + 1) + 1; /* get size of this element */
+ if (val.runstyp != *p1) continue; /* types don't match */
+
+ switch(val.runstyp)
+ {
+ case DAT_NUMBER:
+ if (val.runsv.runsvnum != osrp4s(p1 + 1)) continue;
+ break;
+
+ case DAT_SSTRING:
+ case DAT_LIST:
+ if (osrp2(p1 + 1) != osrp2(val.runsv.runsvstr) ||
+ memcmp(p1 + 3, val.runsv.runsvstr + 2,
+ (size_t)(osrp2(p1 + 1) - 2)))
+ continue;
+ break;
+
+ case DAT_PROPNUM:
+ if (osrp2(p1 + 1) != val.runsv.runsvprp) continue;
+ break;
+
+ case DAT_OBJECT:
+ case DAT_FNADDR:
+ if (osrp2(p1 + 1) != val.runsv.runsvobj) continue;
+ break;
+
+ default:
+ break;
+ }
+
+ /* if we got here, it means we found a match */
+ typ = DAT_NUMBER; /* use the value in outv */
+ break; /* that's it - we've found it */
+ }
+ break;
+
+ default:
+ runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "find");
+ }
+
+ /* push the value given by typ and outv */
+ val.runsv.runsvnum = outv;
+ runpush(ctx->bifcxrun, typ, &val);
+}
+
+/* setit - set current 'it' */
+void bifsit(bifcxdef *ctx, int argc)
+{
+ objnum obj;
+ int typ;
+ voccxdef *vcx = ctx->bifcxrun->runcxvoc;
+
+ /* check for extended version that allows setting him/her */
+ if (argc == 2)
+ {
+ if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+ {
+ rundisc(ctx->bifcxrun); /* discard the nil */
+ obj = MCMONINV; /* use invalid object */
+ }
+ else
+ obj = runpopobj(ctx->bifcxrun); /* get the object */
+
+ typ = runpopnum(ctx->bifcxrun); /* get the code */
+ vcx->voccxthc = 0; /* clear the 'them' list */
+
+ switch(typ)
+ {
+ case 0: /* set "it" */
+ vcx->voccxit = obj;
+ break;
+
+ case 1: /* set "him" */
+ vcx->voccxhim = obj;
+ break;
+
+ case 2: /* set "her" */
+ vcx->voccxher = obj;
+ break;
+ }
+ return;
+ }
+
+ /* "setit classic" has one argument only */
+ bifcntargs(ctx, 1, argc);
+
+ /* check to see if we're setting 'it' or 'them' */
+ if (runtostyp(ctx->bifcxrun) == DAT_LIST)
+ {
+ uchar *lst;
+ uint siz;
+ int cnt;
+
+ lst = runpoplst(ctx->bifcxrun);
+ siz = osrp2(lst);
+ lst += 2;
+ siz -= 2;
+
+ for (cnt = 0 ; siz ; )
+ {
+ /* if this is an object, add to 'them' list (otherwise ignore) */
+ if (*lst == DAT_OBJECT)
+ vcx->voccxthm[cnt++] = osrp2(lst+1);
+
+ lstadv(&lst, &siz);
+ }
+ vcx->voccxthc = cnt;
+ vcx->voccxit = MCMONINV;
+ }
+ else
+ {
+ /* set 'it', and delete 'them' list */
+ if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+ {
+ vcx->voccxit = MCMONINV;
+ rundisc(ctx->bifcxrun);
+ }
+ else
+ vcx->voccxit = runpopobj(ctx->bifcxrun);
+ vcx->voccxthc = 0;
+ }
+}
+
+/* randomize - seed random number generator */
+void bifsrn(bifcxdef *ctx, int argc)
+{
+ bifcntargs(ctx, 0, argc);
+ os_rand(&ctx->bifcxrnd);
+ ctx->bifcxrndset = TRUE;
+}
+
+/* rand - get a random number */
+void bifrnd(bifcxdef *ctx, int argc)
+{
+ unsigned long result, max, randseed;
+ int tmp;
+ runsdef val;
+
+ /* get argument - number giving upper bound of generated number */
+ bifcntargs(ctx, 1, argc);
+ bifchkarg(ctx, DAT_NUMBER);
+ max = runpopnum(ctx->bifcxrun);
+
+ /* if the max is zero, just return zero */
+ if (max == 0)
+ {
+ runpnum(ctx->bifcxrun, 0);
+ return;
+ }
+
+ /*
+ * If the random number generator has been seeded by a call to
+ * randomize(), use the new, improved random number generator. If
+ * not, use the old random number generator to ensure that the same
+ * sequence of numbers is generated as always (to prevent breaking
+ * existing test scripts based on the old sequence).
+ */
+ if (!ctx->bifcxrndset)
+ {
+ /* compute the next number in sequence, using old cheesy generator */
+ randseed = ctx->bifcxrnd;
+ randseed *= 1033;
+ randseed += 5;
+ tmp = randseed / 16384;
+ randseed %= 16384;
+ result = tmp / 7;
+
+ /* adjust the result to be in the requested range */
+ result = ( randseed % max ) + 1;
+
+ /* save the new seed value, and return the value */
+ ctx->bifcxrnd = randseed;
+ val.runsv.runsvnum = result;
+ runpush(ctx->bifcxrun, DAT_NUMBER, &val);
+ }
+ else
+ {
+#define BIF_RAND_M ((ulong)2147483647)
+#define BIF_RAND_Q ((ulong)127773)
+#define BIF_RAND_A ((ulong)16807)
+#define BIF_RAND_R ((ulong)2836)
+
+ long lo, hi, test;
+
+ lo = ctx->bifcxrnd / BIF_RAND_Q;
+ hi = ctx->bifcxrnd % BIF_RAND_Q;
+ test = BIF_RAND_A*lo - BIF_RAND_R*hi;
+ ctx->bifcxrnd = test;
+ if (test > 0)
+ ctx->bifcxrnd = test;
+ else
+ ctx->bifcxrnd = test + BIF_RAND_M;
+ runpnum(ctx->bifcxrun, (((ulong)ctx->bifcxrnd) % max) + 1);
+ }
+}
+
+/*
+ * case-insensitive substring matching
+ */
+static char *bif_stristr(const char *s1, const char *s2)
+{
+ size_t s1len;
+ size_t s2len;
+
+ /* scan for a match */
+ for (s1len = strlen(s1), s2len = strlen(s2) ; s1len >= s2len ;
+ ++s1, --s1len)
+ {
+ /* if this is a match, return this substring */
+ if (memicmp(s1, s2, s2len) == 0)
+ return (char *)s1;
+ }
+
+ return 0;
+}
+
+/*
+ * askfile flags
+ */
+#define BIF_ASKF_EXT_RET 1 /* extended return codes */
+
+/*
+ * askfile
+ */
+void bifask(bifcxdef *ctx, int argc)
+{
+ uchar *prompt;
+ char buf[OSFNMAX + 2];
+ char pbuf[128];
+ int err;
+ int prompt_type;
+ int file_type;
+ ulong flags;
+
+ /* make sure we have an acceptable number of arguments */
+ if (argc != 1 && argc != 3 && argc != 4)
+ runsig(ctx->bifcxrun, ERR_BIFARGC);
+
+ /* get the first argument - the prompt string */
+ prompt = runpopstr(ctx->bifcxrun);
+ bifcstr(ctx, pbuf, (size_t)sizeof(pbuf), prompt);
+
+ /* presume we will have no flags */
+ flags = 0;
+
+ /* if we have the prompt type and file type parameters, get them */
+ if (argc >= 3)
+ {
+ /* get the prompt-type and the file-type arguments */
+ prompt_type = (int)runpopnum(ctx->bifcxrun);
+ file_type = (int)runpopnum(ctx->bifcxrun);
+
+ /* if we have a fourth argument, it's the flags */
+ if (argc == 4)
+ flags = runpopnum(ctx->bifcxrun);
+ }
+ else
+ {
+ static const char *save_strs[] =
+ {
+ "save",
+ "write",
+ 0
+ };
+ static const char *game_strs[] =
+ {
+ "restore",
+ "game",
+ 0
+ };
+ const char **sp;
+
+ /*
+ * No prompt type or file type were specified. Try to infer the
+ * dialog type and file type from the text of the prompt. (This
+ * is mostly to support older games, in particular those based
+ * on older versions of adv.t, since newer games should always
+ * provide explicit values for the file type and dialog type.
+ * We are thus inferring the types based on the prompt strings
+ * that older adv.t's used when calling askfile.)
+ *
+ * If the prompt contains any substring such as "save" or
+ * "write", specify that we're saving; otherwise, assume that
+ * we're opening an existing file for reading.
+ *
+ * If the prompt contains the substrings "restore" AND "game",
+ * assume that we're opening a game file; otherwise, don't make
+ * any assumptions, and use the "unknown" file type.
+ */
+
+ /* presume we're going to open a saved-game file */
+ prompt_type = OS_AFP_OPEN;
+ file_type = OSFTSAVE;
+
+ /* look for any one of the "save" substrings */
+ for (sp = save_strs ; *sp != 0 ; ++sp)
+ {
+ /* check to see if this substring matches */
+ if (bif_stristr(pbuf, *sp))
+ {
+ /* found it - use the "save" prompt */
+ prompt_type = OS_AFP_SAVE;
+
+ /* no need to look any further */
+ break;
+ }
+ }
+
+ /*
+ * look for *all* of the "restore game" strings - if we fail to
+ * find any of them, be conservative and make no assumptions
+ * about the file type
+ */
+ for (sp = game_strs ; *sp != 0 ; ++sp)
+ {
+ if (bif_stristr(pbuf, *sp) == 0)
+ {
+ /*
+ * this one doesn't match - don't make assumptions about
+ * the file type
+ */
+ file_type = OSFTUNK;
+
+ /* no need to look any further */
+ break;
+ }
+ }
+
+ /* check for a transcript */
+ if (file_type == OSFTUNK
+ && prompt_type == OS_AFP_SAVE
+ && bif_stristr(pbuf, "script") != 0)
+ {
+ /* looks like a log file */
+ file_type = OSFTLOG;
+ }
+ }
+
+ /* ask for a file */
+ err = tio_askfile(pbuf, buf, (int)sizeof(buf), prompt_type, (os_filetype_t)file_type);
+
+ /*
+ * if the caller requested extended return codes, return a list
+ * containing the return code as the first element and, if
+ * successful, the string as the second element
+ */
+ if ((flags & BIF_ASKF_EXT_RET) != 0)
+ {
+ ushort len;
+ runsdef val;
+ uchar *p;
+
+ /*
+ * Allocate space for the starter list - if we have a string to
+ * return, just allocate space for the number element for now;
+ * otherwise, allocate space for the number plus a nil second
+ * element (one byte).
+ */
+ len = 2 + (1 + 4);
+ if (err != OS_AFE_SUCCESS)
+ ++len;
+
+ /* allocate the space */
+ runhres(ctx->bifcxrun, len, 0);
+
+ /* set up our list pointer */
+ val.runstyp = DAT_LIST;
+ val.runsv.runsvstr = p = ctx->bifcxrun->runcxhp;
+
+ /* write the length prefix */
+ oswp2(p, len);
+ p += 2;
+
+ /* write the return code as the first element */
+ *p++ = DAT_NUMBER;
+ oswp4s(p, err);
+ p += 4;
+
+ /* write the 'nil' second element if there's an error */
+ if (err != OS_AFE_SUCCESS)
+ *p++ = DAT_NIL;
+
+ /* commit the list's memory */
+ ctx->bifcxrun->runcxhp = p;
+
+ /* push the list */
+ runrepush(ctx->bifcxrun, &val);
+
+ /* if we were successful, add the string to the list */
+ if (err == OS_AFE_SUCCESS)
+ {
+ runsdef val2;
+
+ /* push the string value, converting to our string format */
+ runpushcstr(ctx->bifcxrun, buf, strlen(buf), 1);
+
+ /* add it to the list already on the stack */
+ runpop(ctx->bifcxrun, &val2);
+ runpop(ctx->bifcxrun, &val);
+ runadd(ctx->bifcxrun, &val, &val2, 2);
+
+ /* re-push the result */
+ runrepush(ctx->bifcxrun, &val);
+ }
+ }
+ else
+ {
+ /*
+ * use the traditional return codes - if askfile failed, return
+ * nil; otherwise, return the filename
+ */
+ if (err)
+ runpnil(ctx->bifcxrun);
+ else
+ runpushcstr(ctx->bifcxrun, buf, strlen(buf), 0);
+ }
+}
+
+/* setscore */
+void bifssc(bifcxdef *ctx, int argc)
+{
+ int s1, s2;
+
+ /* optional new way - string argument */
+ if (argc == 1 && runtostyp(ctx->bifcxrun) == DAT_SSTRING)
+ {
+ char buf[80];
+ uchar *p;
+
+ p = runpopstr(ctx->bifcxrun);
+ bifcstr(ctx, buf, (size_t)sizeof(buf), p);
+ tiostrsc(ctx->bifcxtio, buf);
+ }
+ else
+ {
+ /* old way - two numeric arguments (displays: x/y) */
+ bifcntargs(ctx, 2, argc);
+ s1 = runpopnum(ctx->bifcxrun);
+ s2 = runpopnum(ctx->bifcxrun);
+ tioscore(ctx->bifcxtio, s1, s2);
+ }
+}
+
+/* substr */
+void bifsub(bifcxdef *ctx, int argc)
+{
+ uchar *p;
+ int ofs;
+ int asklen;
+ int outlen;
+ int len;
+
+ bifcntargs(ctx, 3, argc);
+
+ /* get the string argument */
+ bifchkarg(ctx, DAT_SSTRING);
+ p = runpopstr(ctx->bifcxrun);
+ len = osrp2(p) - 2;
+ p += 2;
+
+ /* get the offset argument */
+ bifchkarg(ctx, DAT_NUMBER);
+ ofs = runpopnum(ctx->bifcxrun);
+ if (ofs < 1) runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "substr");
+
+ /* get the length argument */
+ bifchkarg(ctx, DAT_NUMBER);
+ asklen = runpopnum(ctx->bifcxrun);
+ if (asklen < 0) runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "substr");
+
+ --ofs; /* convert offset to a zero bias (user provided 1-bias) */
+ p += ofs; /* advance string pointer by offset */
+
+ if (ofs >= len)
+ outlen = 0; /* offset is past end of string */
+ else if (asklen > len - ofs)
+ outlen = len - ofs; /* just use rest of string */
+ else
+ outlen = asklen; /* requested length can be provided */
+
+ runpstr(ctx->bifcxrun, (char *)p, outlen, 3);
+}
+
+/* cvtstr - convert value to a string */
+void bifcvs(bifcxdef *ctx, int argc)
+{
+ char *p;
+ int len;
+ char buf[30];
+
+ bifcntargs(ctx, 1, argc);
+ switch(runtostyp(ctx->bifcxrun))
+ {
+ case DAT_NIL:
+ p = "nil";
+ len = 3;
+ (void)runpoplog(ctx->bifcxrun);
+ break;
+
+ case DAT_TRUE:
+ p = "true";
+ len = 4;
+ (void)runpoplog(ctx->bifcxrun);
+ break;
+
+ case DAT_NUMBER:
+ sprintf(buf, "%ld", runpopnum(ctx->bifcxrun));
+ p = buf;
+ len = strlen(buf);
+ break;
+
+ case DAT_SSTRING:
+ /* leave the string value on the stack unchanged */
+ return;
+
+ default:
+ /* throw the RUNEXITOBJ error */
+ runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "cvtstr");
+ }
+
+ runpstr(ctx->bifcxrun, p, len, 0);
+}
+
+/* cvtnum - convert a value to a number */
+void bifcvn(bifcxdef *ctx, int argc)
+{
+ runsdef val;
+ uchar *p;
+ int len;
+ int typ;
+ long acc;
+ int neg;
+
+ bifcntargs(ctx, 1, argc);
+ p = runpopstr(ctx->bifcxrun);
+ len = osrp2(p) - 2;
+ p += 2;
+
+ if (len == 3 && !memcmp(p, "nil", (size_t)3))
+ typ = DAT_NIL;
+ else if (len == 4 && !memcmp(p, "true", (size_t)4))
+ typ = DAT_TRUE;
+ else
+ {
+ typ = DAT_NUMBER;
+ for ( ; len != 0 && t_isspace(*p) ; ++p, --len) ;
+ if (len != 0 && *p == '-')
+ {
+ neg = TRUE;
+ for (++p, --len ; len != 0 && t_isspace(*p) ; ++p, --len) ;
+ }
+ else neg = FALSE;
+
+ /* accumulate the number digit by digit */
+ for (acc = 0 ; len != 0 && Common::isDigit(*p) ; ++p, --len)
+ acc = (acc << 3) + (acc << 1) + ((*p) - '0');
+
+ if (neg) acc = -acc;
+ val.runsv.runsvnum = acc;
+ }
+
+ runpush(ctx->bifcxrun, typ, &val);
+}
+
+/* general string conversion function */
+static void bifcvtstr(bifcxdef *ctx, void (*cvtfn)(uchar *, int), int argc)
+{
+ uchar *p;
+ int len;
+ runsdef val;
+ runsdef stkval;
+
+ bifcntargs(ctx, 1, argc);
+ bifchkarg(ctx, DAT_SSTRING);
+
+ p = runpopstr(ctx->bifcxrun);
+ stkval.runstyp = DAT_SSTRING;
+ stkval.runsv.runsvstr = p;
+ len = osrp2(p);
+
+ /* allocate space in heap for the string and convert */
+ runhres1(ctx->bifcxrun, len, 1, &stkval);
+ p = stkval.runsv.runsvstr;
+ memcpy(ctx->bifcxrun->runcxhp, p, (size_t)len);
+ (*cvtfn)(ctx->bifcxrun->runcxhp + 2, len - 2);
+
+ val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+ val.runstyp = DAT_SSTRING;
+ ctx->bifcxrun->runcxhp += len;
+ runrepush(ctx->bifcxrun, &val);
+}
+
+/* routine to convert a counted-length string to uppercase */
+static void bifstrupr(uchar *str, int len)
+{
+ for ( ; len ; --len, ++str)
+ {
+ if (*str == '\\' && len > 1)
+ --len, ++str;
+ else if (Common::isLower(*str))
+ *str = toupper(*str);
+ }
+}
+
+/* upper */
+void bifupr(bifcxdef *ctx, int argc)
+{
+ bifcvtstr(ctx, bifstrupr, argc);
+}
+
+/* convert a counted-length string to lowercase */
+static void bifstrlwr(uchar *str, int len)
+{
+ for ( ; len ; --len, ++str)
+ {
+ if (*str == '\\' && len > 1)
+ --len, ++str;
+ else if (Common::isUpper(*str))
+ *str = tolower(*str);
+ }
+}
+
+/* lower */
+void biflwr(bifcxdef *ctx, int argc)
+{
+ bifcvtstr(ctx, bifstrlwr, argc);
+}
+
+/* internal check to determine if object is of a class */
+int bifinh(voccxdef *voc, vocidef *v, objnum cls)
+{
+ int i;
+ objnum *sc;
+
+ if (!v) return(FALSE);
+ for (i = v->vocinsc, sc = v->vocisc ; i ; ++sc, --i)
+ {
+ if (*sc == cls
+ || bifinh(voc, vocinh(voc, *sc), cls))
+ return(TRUE);
+ }
+ return(FALSE);
+}
+
+/* isclass(obj, cls) */
+void bifisc(bifcxdef *ctx, int argc)
+{
+ objnum obj;
+ objnum cls;
+ runsdef val;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ bifcntargs(ctx, 2, argc);
+
+ /* if checking for nil, return nil */
+ if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+ {
+ rundisc(ctx->bifcxrun);
+ rundisc(ctx->bifcxrun);
+ runpnil(ctx->bifcxrun);
+ return;
+ }
+
+ /* get the arguments: object, class */
+ obj = runpopobj(ctx->bifcxrun);
+ cls = runpopobj(ctx->bifcxrun);
+
+ /* return the result from bifinh() */
+ runpush(ctx->bifcxrun, runclog(bifinh(voc, vocinh(voc, obj), cls)), &val);
+}
+
+/* firstsc(obj) - get the first superclass of an object */
+void bif1sc(bifcxdef *ctx, int argc)
+{
+ objnum obj;
+ objnum sc;
+
+ bifcntargs(ctx, 1, argc);
+ obj = runpopobj(ctx->bifcxrun);
+ sc = objget1sc(ctx->bifcxrun->runcxmem, obj);
+ runpobj(ctx->bifcxrun, sc);
+}
+
+/* firstobj */
+void biffob(bifcxdef *ctx, int argc)
+{
+ vocidef ***vpg;
+ vocidef **v;
+ objnum obj;
+ int i;
+ int j;
+ objnum cls = 0;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ /* get class to search for, if one is specified */
+ if (argc == 0)
+ cls = MCMONINV;
+ else if (argc == 1)
+ cls = runpopobj(ctx->bifcxrun);
+ else
+ runsig(ctx->bifcxrun, ERR_BIFARGC);
+
+ for (vpg = voc->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 || ((*v)->vociflg & VOCIFCLASS)
+ || (cls != MCMONINV && !bifinh(voc, *v, cls)))
+ continue;
+
+ /* this is an object we can use - push it */
+ runpobj(ctx->bifcxrun, obj);
+ return;
+ }
+ }
+
+ /* no objects found at all - return nil */
+ runpnil(ctx->bifcxrun);
+}
+
+/* nextobj */
+void bifnob(bifcxdef *ctx, int argc)
+{
+ objnum prv;
+ vocidef ***vpg;
+ vocidef **v;
+ objnum obj;
+ int i;
+ int j;
+ objnum cls = 0;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ /* get last position in search */
+ prv = runpopobj(ctx->bifcxrun);
+
+ /* get class to search for, if one is specified */
+ if (argc == 1)
+ cls = MCMONINV;
+ else if (argc == 2)
+ cls = runpopobj(ctx->bifcxrun);
+ else
+ runsig(ctx->bifcxrun, ERR_BIFARGC);
+
+ /* start at previous object plus 1 */
+ i = (prv >> 8);
+ vpg = voc->voccxinh + i;
+ j = (prv & 255);
+ obj = prv;
+ v = (*vpg) + j;
+
+ for (;;)
+ {
+ ++j;
+ ++obj;
+ ++v;
+ if (j == 256)
+ {
+ j = 0;
+ ++i;
+ ++vpg;
+ if (!*vpg)
+ {
+ obj += 255;
+ j += 255;
+ continue;
+ }
+ v = (*vpg);
+ }
+ if (i >= VOCINHMAX)
+ {
+ runpnil(ctx->bifcxrun);
+ return;
+ }
+
+ if (!*v || ((*v)->vociflg & VOCIFCLASS)
+ || (cls != MCMONINV && !bifinh(voc, *v, cls)))
+ continue;
+
+ /* this is an object we can use - push it */
+ runpobj(ctx->bifcxrun, obj);
+ return;
+ }
+}
+
+/* setversion */
+void bifsvn(bifcxdef *ctx, int argc)
+{
+ bifcntargs(ctx, 1, argc);
+ (void)runpopstr(ctx->bifcxrun);
+ /* note - setversion doesn't do anything in v2; uses timestamp instead */
+}
+
+/* getarg */
+void bifarg(bifcxdef *ctx, int argc)
+{
+ int argnum;
+
+ bifcntargs(ctx, 1, argc);
+ bifchkarg(ctx, DAT_NUMBER);
+
+ /* get and verify argument number */
+ argnum = runpopnum(ctx->bifcxrun);
+ if (argnum < 1) runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "getarg");
+
+ runrepush(ctx->bifcxrun, ctx->bifcxrun->runcxbp - argnum - 1);
+}
+
+/* datatype */
+void biftyp(bifcxdef *ctx, int argc)
+{
+ runsdef val;
+
+ bifcntargs(ctx, 1, argc);
+
+ /* get whatever it is, and push the type */
+ runpop(ctx->bifcxrun, &val);
+ val.runsv.runsvnum = val.runstyp; /* new value is the datatype */
+ runpush(ctx->bifcxrun, DAT_NUMBER, &val);
+}
+
+/* undo */
+void bifund(bifcxdef *ctx, int argc)
+{
+ objucxdef *ucx = ctx->bifcxrun->runcxvoc->voccxundo;
+ mcmcxdef *mcx = ctx->bifcxrun->runcxmem;
+ errcxdef *ec = ctx->bifcxerr;
+ int err;
+ int undone;
+ runsdef val;
+
+ bifcntargs(ctx, 0, argc); /* no arguments */
+
+ ERRBEGIN(ec)
+ if (ucx)
+ {
+ objundo(mcx, ucx); /* try to undo to previous savepoint */
+ undone = TRUE; /* looks like we succeeded */
+ }
+ else
+ undone = FALSE; /* no undo context; can't undo */
+ ERRCATCH(ec, err)
+ if (err == ERR_NOUNDO || err == ERR_ICUNDO)
+ undone = FALSE;
+ else
+ errrse(ec); /* don't know how to handle other errors */
+ ERREND(ec)
+
+ /* return a value indicating whether the undo operation succeeded */
+ runpush(ctx->bifcxrun, runclog(undone), &val);
+
+ /* note that the rest of the command line is to be ignored */
+ ctx->bifcxrun->runcxvoc->voccxflg |= VOCCXFCLEAR;
+}
+
+/* flags for defined() function */
+#define BIFDEF_DEFINED_ANY 1
+#define BIFDEF_DEFINED_DIRECTLY 2
+#define BIFDEF_DEFINED_INHERITS 3
+#define BIFDEF_DEFINED_GET_CLASS 4
+
+/* defined */
+void bifdef(bifcxdef *ctx, int argc)
+{
+ prpnum prpn;
+ objnum objn;
+ uint ofs;
+ runsdef val;
+ objnum def_objn;
+ int flag;
+
+ /* get object and property arguments */
+ objn = runpopobj(ctx->bifcxrun);
+ prpn = runpopprp(ctx->bifcxrun);
+
+ /* if there's a flag argument, get it as well */
+ if (argc == 3)
+ {
+ /* get the flag */
+ flag = (int)runpopnum(ctx->bifcxrun);
+ }
+ else
+ {
+ /* check the argument count */
+ bifcntargs(ctx, 2, argc);
+
+ /* use the default flag value (DEFINES_OR_INHERITS) */
+ flag = BIFDEF_DEFINED_ANY;
+ }
+
+ /* get the offset of the property and the defining object */
+ ofs = objgetap(ctx->bifcxrun->runcxmem, objn, prpn, &def_objn, FALSE);
+
+ /* determine the type of information they want */
+ switch(flag)
+ {
+ case BIFDEF_DEFINED_ANY:
+ /* if the property is defined, return true, else return nil */
+ runpush(ctx->bifcxrun, runclog(ofs != 0), &val);
+ break;
+
+ case BIFDEF_DEFINED_DIRECTLY:
+ /* if the property is defined directly by the object, return true */
+ runpush(ctx->bifcxrun, runclog(ofs != 0 && def_objn == objn), &val);
+ break;
+
+ case BIFDEF_DEFINED_INHERITS:
+ /* if the property is inherited, return true */
+ runpush(ctx->bifcxrun, runclog(ofs != 0 && def_objn != objn), &val);
+ break;
+
+ case BIFDEF_DEFINED_GET_CLASS:
+ /* if it's defined, return the defining object, otherwise nil */
+ if (ofs == 0)
+ runpnil(ctx->bifcxrun);
+ else
+ runpobj(ctx->bifcxrun, def_objn);
+ break;
+
+ default:
+ /* invalid flag value */
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "defined");
+ }
+}
+
+/* proptype */
+void bifpty(bifcxdef *ctx, int argc)
+{
+ prpnum prpn;
+ objnum objn;
+ uint ofs;
+ runsdef val;
+ objnum orn;
+ objdef *objptr;
+ prpdef *propptr;
+
+ bifcntargs(ctx, 2, argc);
+
+ /* get offset of obj.prop */
+ objn = runpopobj(ctx->bifcxrun);
+ prpn = runpopprp(ctx->bifcxrun);
+ ofs = objgetap(ctx->bifcxrun->runcxmem, objn, prpn, &orn, FALSE);
+
+ if (ofs)
+ {
+ /* lock the object, read the prpdef, and unlock it */
+ objptr = (objdef *)mcmlck(ctx->bifcxrun->runcxmem, (mcmon)orn);
+ propptr = objofsp(objptr, ofs);
+ val.runsv.runsvnum = prptype(propptr);
+ mcmunlck(ctx->bifcxrun->runcxmem, (mcmon)orn);
+ }
+ else
+ {
+ /* property is not defined by object - indicate that type is nil */
+ val.runsv.runsvnum = DAT_NIL;
+ }
+
+ /* special case: DAT_DEMAND -> DAT_LIST (for contents properties) */
+ if (val.runsv.runsvnum == DAT_DEMAND)
+ val.runsv.runsvnum = DAT_LIST;
+
+ /* return the property type as a number */
+ runpush(ctx->bifcxrun, DAT_NUMBER, &val);
+}
+
+/* outhide */
+void bifoph(bifcxdef *ctx, int argc)
+{
+ runsdef val;
+ int hidden, output_occurred;
+
+ bifcntargs(ctx, 1, argc);
+ outstat(&hidden, &output_occurred);
+ if (runtostyp(ctx->bifcxrun) == DAT_TRUE)
+ {
+ /* throw away the flag */
+ rundisc(ctx->bifcxrun);
+
+ /* figure out appropriate return value */
+ if (!hidden)
+ val.runsv.runsvnum = 0;
+ else if (!output_occurred)
+ val.runsv.runsvnum = 1;
+ else
+ val.runsv.runsvnum = 2;
+ runpush(ctx->bifcxrun, DAT_NUMBER, &val);
+
+ /* actually hide the output, resetting count flag */
+ outhide();
+ }
+ else if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+ {
+ /* throw away the flag */
+ rundisc(ctx->bifcxrun);
+
+ /* show output, returning status */
+ runpush(ctx->bifcxrun, runclog(outshow()), &val);
+ }
+ else if (runtostyp(ctx->bifcxrun) == DAT_NUMBER)
+ {
+ int n = runpopnum(ctx->bifcxrun);
+
+ if (n == 0)
+ {
+ /* output was not hidden - show output and return status */
+ runpush(ctx->bifcxrun, runclog(outshow()), &val);
+ }
+ else if (n == 1)
+ {
+ /*
+ * Output was hidden, but no output had occurred yet.
+ * Leave output hidden and return whether any output has
+ * occurred.
+ */
+ runpush(ctx->bifcxrun, runclog(output_occurred), &val);
+ }
+ else if (n == 2)
+ {
+ /*
+ * Output was hidden, and output had already occurred. If
+ * more output has occurred, return true, else return nil.
+ * In either case, set the output_occurred flag back to
+ * true, since it was true before the outhide(true).
+ */
+ runpush(ctx->bifcxrun, runclog(output_occurred), &val);
+ outsethidden();
+ }
+ else
+ errsig1(ctx->bifcxerr, ERR_INVVBIF, ERRTSTR, "outhide");
+ }
+ else
+ errsig(ctx->bifcxerr, ERR_REQNUM);
+}
+
+/* put a numeric value in a list */
+static uchar *bifputnum(uchar *lstp, uint val)
+{
+ *lstp++ = DAT_NUMBER;
+ oswp4s(lstp, (long)val);
+ return(lstp + 4);
+}
+
+/* gettime */
+void biftim(bifcxdef *ctx, int argc) {
+ TimeDate tm;
+ uint timer;
+ uchar ret[80];
+ uchar *p;
+ runsdef val;
+ int typ;
+ int tm_yday;
+ const int MONTH_DAYS[11] = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30 };
+
+ if (argc == 1)
+ {
+ /* get the time type */
+ typ = (int)runpopnum(ctx->bifcxrun);
+ }
+ else
+ {
+ /* make sure no arguments are specified */
+ bifcntargs(ctx, 0, argc);
+
+ /* use the default time type */
+ typ = 1;
+ }
+
+ switch(typ)
+ {
+ case 1:
+ /*
+ * default information format - list format with current system
+ * time and date
+ */
+
+ /* make sure the time zone is set up properly */
+ os_tzset();
+
+ /* get the local time information */
+ g_system->getTimeAndDate(tm);
+
+ /* adjust values for return format */
+ tm.tm_wday++;
+
+ // Get number of days since start of year
+ tm_yday = tm.tm_mday;
+ for (int monthNum = 1; monthNum < tm.tm_mon; ++monthNum) {
+ int daysInMonth = MONTH_DAYS[monthNum - 1];
+ if (monthNum == 2)
+ daysInMonth = (tm.tm_year % 4) == 0 && (((tm.tm_year % 100) != 0) || ((tm.tm_year % 400) == 0)) ? 29 : 28;
+
+ tm_yday += daysInMonth;
+ }
+
+ // TODO: Convert dae/tme to Unix style local time
+ timer = 0;
+
+ /* build return list value */
+ oswp2(ret, 47);
+ p = ret + 2;
+ p = bifputnum(p, tm.tm_year);
+ p = bifputnum(p, tm.tm_mon);
+ p = bifputnum(p, tm.tm_mday);
+ p = bifputnum(p, tm.tm_wday);
+ p = bifputnum(p, tm_yday);
+ p = bifputnum(p, tm.tm_hour);
+ p = bifputnum(p, tm.tm_min);
+ p = bifputnum(p, tm.tm_sec);
+ *p++ = DAT_NUMBER;
+ oswp4s(p, (long)timer);
+
+ val.runstyp = DAT_LIST;
+ val.runsv.runsvstr = ret;
+ runpush(ctx->bifcxrun, DAT_LIST, &val);
+ break;
+
+ case 2:
+ /*
+ * High-precision system timer value - returns the system time
+ * in milliseconds, relative to an arbitrary zero point
+ */
+ runpnum(ctx->bifcxrun, os_get_sys_clock_ms());
+ break;
+
+ default:
+ /* other types are invalid */
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "gettime");
+ break;
+ }
+}
+
+/* getfuse */
+void bifgfu(bifcxdef *ctx, int argc)
+{
+ vocddef *daem;
+ objnum func;
+ runsdef val;
+ runcxdef *rcx = ctx->bifcxrun;
+ int slots;
+ prpnum prop;
+ voccxdef *vcx = ctx->bifcxrun->runcxvoc;
+
+ bifcntargs(ctx, 2, argc);
+
+ if (runtostyp(rcx) == DAT_FNADDR)
+ {
+ /* check on a setfuse()-style fuse: get fnaddr, parm */
+ func = runpopfn(rcx);
+ runpop(rcx, &val);
+
+ for (slots = vcx->voccxfuc, daem = vcx->voccxfus ;
+ slots ; ++daem, --slots)
+ {
+ if (daem->vocdfn == func
+ && daem->vocdarg.runstyp == val.runstyp
+ && !memcmp(&val.runsv, &daem->vocdarg.runsv,
+ (size_t)datsiz(val.runstyp, &val.runsv)))
+ goto ret_num;
+ }
+ }
+ else
+ {
+ /* check on a notify()-style fuse: get object, &message */
+ func = runpopobj(rcx);
+ prop = runpopprp(rcx);
+
+ for (slots = vcx->voccxalc, daem = vcx->voccxalm ;
+ slots ; ++daem, --slots)
+ {
+ if (daem->vocdfn == func && daem->vocdprp == prop)
+ goto ret_num;
+ }
+ }
+
+ /* didn't find anything - return nil */
+ runpush(rcx, DAT_NIL, &val);
+ return;
+
+ret_num:
+ /* return current daem->vocdtim */
+ runpnum(rcx, (long)daem->vocdtim);
+ return;
+}
+
+/* runfuses */
+void bifruf(bifcxdef *ctx, int argc)
+{
+ int ret;
+ runsdef val;
+
+ bifcntargs(ctx, 0, argc);
+ ret = exefuse(ctx->bifcxrun->runcxvoc, TRUE);
+ runpush(ctx->bifcxrun, runclog(ret), &val);
+}
+
+/* rundaemons */
+void bifrud(bifcxdef *ctx, int argc)
+{
+ bifcntargs(ctx, 0, argc);
+ exedaem(ctx->bifcxrun->runcxvoc);
+}
+
+/* intersect */
+void bifsct(bifcxdef *bifctx, int argc)
+{
+ runcxdef *ctx = bifctx->bifcxrun;
+ uchar *l1;
+ uchar *l2;
+ uchar *l3;
+ uint siz1;
+ uint siz2;
+ uint siz3;
+ uchar *p;
+ uint l;
+ uint dsz1;
+ uint dsz2;
+ runsdef val;
+ runsdef stk1, stk2;
+
+ bifcntargs(bifctx, 2, argc);
+ l1 = runpoplst(ctx);
+ siz1 = osrp2(l1);
+ l2 = runpoplst(ctx);
+ siz2 = osrp2(l2);
+
+ /* make sure the first list is smaller - if not, switch them */
+ if (siz1 > siz2)
+ l3 = l1, l1 = l2, l2 = l3, siz3 = siz1, siz1 = siz2, siz2 = siz3;
+
+ /* size of result is at most size of smaller list (which is now siz1) */
+ stk1.runstyp = stk2.runstyp = DAT_LIST;
+ stk1.runsv.runsvstr = l1;
+ stk2.runsv.runsvstr = l2;
+ runhres2(ctx, siz1, 2, &stk1, &stk2);
+ l1 = stk1.runsv.runsvstr;
+ l2 = stk2.runsv.runsvstr;
+ l3 = ctx->runcxhp + 2;
+
+ /* go through list1, and copy each element that is found in list2 */
+ for (l1 += 2, l2 += 2, siz1 -= 2, siz2 -= 2 ; siz1 ; lstadv(&l1, &siz1))
+ {
+ dsz1 = datsiz(*l1, l1 + 1) + 1;
+ for (l = siz2, p = l2 ; l ; lstadv(&p, &l))
+ {
+ dsz2 = datsiz(*p, p + 1) + 1;
+#ifndef AMIGA
+ if (dsz1 == dsz2 && !memcmp(l1, p, (size_t)dsz1))
+#else /* AMIGA */
+ if (!memcmp(l1, p, (size_t)dsz1) && (dsz1 == dsz2) )
+#endif /* AMIGA */
+ {
+ memcpy(l3, p, (size_t)dsz1);
+ l3 += dsz1;
+ break;
+ }
+ }
+ }
+
+ /* set up return value, take it out of the heap, and push value */
+ val.runsv.runsvstr = ctx->runcxhp;
+ val.runstyp = DAT_LIST;
+ oswp2(ctx->runcxhp, (uint)(l3 - ctx->runcxhp));
+ ctx->runcxhp = l3;
+ runrepush(ctx, &val);
+}
+
+/*
+ * Portable keystroke mappings. We map the extended key codes to these
+ * strings, so that the TADS code can access arrow keys and the like.
+ */
+static char *ext_key_names[] =
+{
+ "[up]", /* CMD_UP - 1 */
+ "[down]", /* CMD_DOWN - 2 */
+ "[right]", /* CMD_RIGHT - 3 */
+ "[left]", /* CMD_LEFT - 4 */
+ "[end]", /* CMD_END - 5 */
+ "[home]", /* CMD_HOME - 6 */
+ "[del-eol]", /* CMD_DEOL - 7 */
+ "[del-line]", /* CMD_KILL - 8 */
+ "[del]", /* CMD_DEL - 9 */
+ "[scroll]", /* CMD_SCR - 10 */
+ "[page up]", /* CMD_PGUP - 11 */
+ "[page down]", /* CMD_PGDN - 12 */
+ "[top]", /* CMD_TOP - 13 */
+ "[bottom]", /* CMD_BOT - 14 */
+ "[f1]", /* CMD_F1 - 15 */
+ "[f2]", /* CMD_F2 - 16 */
+ "[f3]", /* CMD_F3 - 17 */
+ "[f4]", /* CMD_F4 - 18 */
+ "[f5]", /* CMD_F5 - 19 */
+ "[f6]", /* CMD_F6 - 20 */
+ "[f7]", /* CMD_F7 - 21 */
+ "[f8]", /* CMD_F8 - 22 */
+ "[f9]", /* CMD_F9 - 23 */
+ "[f10]", /* CMD_F10 - 24 */
+ "[?]", /* invalid key - CMD_CHOME - 25 */
+ "[tab]", /* CMD_TAB - 26 */
+ "[?]", /* invalid key - shift-F2 - 27 */
+ "[?]", /* not used (obsolete) - 28 */
+ "[word-left]", /* CMD_WORD_LEFT - 29 */
+ "[word-right]", /* CMD_WORD_RIGHT - 30 */
+ "[del-word]", /* CMD_WORDKILL - 31 */
+ "[eof]", /* CMD_EOF - 32 */
+ "[break]" /* CMD_BREAK - 33 */
+};
+
+/*
+ * Get the name of a keystroke. Pass in the one or two characters
+ * returned by os_getc(), and we'll fill in the buffer with the
+ * inputkey() name of the keystroke. Returns true if the key was valid,
+ * false if not. 'c' is the first character returned by os_getc() for
+ * the keystroke; if 'c' is zero, then 'extc' is the character returned
+ * by the second call to os_getc() to get the CMD_xxx code for the
+ * keystroke.
+ *
+ * The name buffer should be 20 characters long - this will ensure that
+ * any name will fit.
+ *
+ * For ordinary, printable characters, we'll simply return the
+ * character; the letter 'a', for example, is returned as the string "a".
+ *
+ * For extended keys, we'll look up the CMD_xxx code and return the name
+ * of the command, enclosed in square brackets; see the ext_key_names
+ * table for the mappings. The left-arrow cursor key, for example,
+ * returns "[left]".
+ *
+ * For control characters, we'll generate a name like "[ctrl-a]", except
+ * for the following characters:
+ *
+ *. ascii 10 returns "\n"
+ *. ascii 13 returns "\n"
+ *. ascii 9 returns "\t"
+ *. ascii 8 returns "[bksp]"
+ */
+static int get_ext_key_name(char *namebuf, int c, int extc)
+{
+ /* if it's a control character, translate it */
+ if (c >= 1 && c <= 27)
+ {
+ switch(c)
+ {
+ case 10:
+ case 13:
+ /* return '\n' for LF and CR characters */
+ strcpy(namebuf, "\\n");
+ return TRUE;
+
+ case 9:
+ /* return '\t' for TAB characters */
+ strcpy(namebuf, "\\t");
+ return TRUE;
+
+ case 8:
+ /* return '[bksp]' for backspace characters */
+ strcpy(namebuf, "[bksp]");
+ return TRUE;
+
+ case 27:
+ /* return '[esc]' for the escape key */
+ strcpy(namebuf, "[esc]");
+ return TRUE;
+
+ default:
+ /* return '[ctrl-X]' for other control characters */
+ strcpy(namebuf, "[ctrl-X]");
+ namebuf[6] = (char)(c + 'a' - 1);
+ return TRUE;
+ }
+ }
+
+ /* if it's any other non-extended key, return it as-is */
+ if (c != 0)
+ {
+ namebuf[0] = c;
+ namebuf[1] = '\0';
+ return TRUE;
+ }
+
+ /* if it's in the key name array, use the array entry */
+ if (extc >= 1
+ && extc <= (int)(sizeof(ext_key_names)/sizeof(ext_key_names[0])))
+ {
+ /* use the array name */
+ strcpy(namebuf, ext_key_names[extc - 1]);
+ return TRUE;
+ }
+
+ /* if it's in the ALT key range, generate an ALT key name */
+ if (extc >= CMD_ALT && extc <= CMD_ALT + 25)
+ {
+ /* generate an ALT key name */
+ strcpy(namebuf, "[alt-X]");
+ namebuf[5] = (char)(extc - CMD_ALT + 'a');
+ return TRUE;
+ }
+
+ /* it's not a valid key - use '[?]' as the name */
+ strcpy(namebuf, "[?]");
+ return FALSE;
+}
+
+
+/* inputkey */
+void bifink(bifcxdef *ctx, int argc)
+{
+ int c;
+ int extc;
+ char str[20];
+ size_t len;
+
+ bifcntargs(ctx, 0, argc);
+ tioflushn(ctx->bifcxtio, 0);
+
+ /* get a key */
+ c = os_getc_raw();
+
+ /* if it's extended, get the second part of the extended sequence */
+ extc = (c == 0 ? os_getc_raw() : 0);
+
+ /* map the extended key name */
+ get_ext_key_name(str, c, extc);
+
+ /* get the length of the name */
+ len = strlen(str);
+
+ /* reset the [more] counter */
+ outreset();
+
+ /* return the string, translating escapes */
+ runpstr(ctx->bifcxrun, str, len, 0);
+}
+
+/* get direct/indirect object word list */
+void bifwrd(bifcxdef *ctx, int argc)
+{
+ int ob;
+ vocoldef *v;
+ uchar buf[128];
+ uchar *dst;
+ uchar *src;
+ uint len;
+ runsdef val;
+
+ bifcntargs(ctx, 1, argc);
+
+ /* figure out what word list to get */
+ ob = runpopnum(ctx->bifcxrun);
+ switch(ob)
+ {
+ case 1:
+ v = ctx->bifcxrun->runcxvoc->voccxdobj;
+ break;
+
+ case 2:
+ v = ctx->bifcxrun->runcxvoc->voccxiobj;
+ break;
+
+ default:
+ runpnil(ctx->bifcxrun);
+ return;
+ }
+
+ /* now build a list of strings from the words, if there are any */
+ if (v != 0 && voclistlen(v) != 0 && v->vocolfst != 0 && v->vocollst != 0)
+ {
+ for (dst = buf + 2, src = (uchar *)v->vocolfst ;
+ src <= (uchar *)v->vocollst ; src += len + 1)
+ {
+ *dst++ = DAT_SSTRING;
+ len = strlen((char *)src);
+ oswp2(dst, len + 2);
+ strcpy((char *)dst + 2, (char *)src);
+ dst += len + 2;
+ }
+ }
+ else
+ dst = buf + 2;
+
+ /* finish setting up the list length and return it */
+ len = dst - buf;
+ oswp2(buf, len);
+ val.runsv.runsvstr = buf;
+ val.runstyp = DAT_LIST;
+ runpush(ctx->bifcxrun, DAT_LIST, &val);
+}
+
+/* add a vocabulary word to an object */
+void bifadw(bifcxdef *ctx, int argc)
+{
+ uchar *wrd;
+ objnum objn;
+ prpnum prpn;
+ vocidef *voci;
+ int classflg;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ bifcntargs(ctx, 3, argc);
+
+ /* get the arguments */
+ objn = runpopobj(ctx->bifcxrun);
+ prpn = runpopprp(ctx->bifcxrun);
+ wrd = runpopstr(ctx->bifcxrun);
+
+ /* make sure the property is a valid part of speech property */
+ if (!prpisvoc(prpn))
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "addword");
+
+ /* get the vocidef for the object, and see if it's a class object */
+ voci = vocinh(voc, objn);
+
+ classflg = VOCFNEW;
+ if (voci->vociflg & VOCIFCLASS) classflg |= VOCFCLASS;
+
+ /* add the word */
+ vocadd(voc, prpn, objn, classflg, (char *)wrd);
+
+ /* generate undo for the operation */
+ vocdusave_addwrd(voc, objn, prpn, classflg, (char *)wrd);
+}
+
+/* delete a vocabulary word from an object */
+void bifdlw(bifcxdef *ctx, int argc)
+{
+ uchar *wrd;
+ objnum objn;
+ prpnum prpn;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ bifcntargs(ctx, 3, argc);
+
+ /* get the arguments */
+ objn = runpopobj(ctx->bifcxrun);
+ prpn = runpopprp(ctx->bifcxrun);
+ wrd = runpopstr(ctx->bifcxrun);
+
+ /* make sure the property is a valid part of speech property */
+ if (!prpisvoc(prpn))
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "delword");
+
+ /* delete the word */
+ vocdel1(voc, objn, (char *)wrd, prpn, FALSE, FALSE, TRUE);
+}
+
+/* callback context for word list builder */
+struct bifgtw_cb_ctx
+{
+ uchar *p;
+ int typ;
+};
+
+/* callback for word list builder */
+static void bifgtw_cb(void *ctx0, vocdef *voc, vocwdef *vocw)
+{
+ struct bifgtw_cb_ctx *ctx = (struct bifgtw_cb_ctx *)ctx0;
+
+ /* ignore deleted objects */
+ if (vocw->vocwflg & VOCFDEL)
+ return;
+
+ /* ignore objects of the inappropriate type */
+ if (vocw->vocwtyp != ctx->typ)
+ return;
+
+ /* the datatype is string */
+ *ctx->p = DAT_SSTRING;
+
+ /* copy the first word */
+ memcpy(ctx->p + 3, voc->voctxt, (size_t)voc->voclen);
+
+ /* if there are two words, add a space and the second word */
+ if (voc->vocln2)
+ {
+ *(ctx->p + 3 + voc->voclen) = ' ';
+ memcpy(ctx->p + 4 + voc->voclen, voc->voctxt + voc->voclen,
+ (size_t)voc->vocln2);
+ oswp2(ctx->p + 1, voc->voclen + voc->vocln2 + 3);
+ ctx->p += voc->voclen + voc->vocln2 + 4;
+ }
+ else
+ {
+ oswp2(ctx->p + 1, voc->voclen+2);
+ ctx->p += voc->voclen + 3;
+ }
+}
+
+/* get the list of words for an object for a particular part of speech */
+void bifgtw(bifcxdef *ctx, int argc)
+{
+ objnum objn;
+ prpnum prpn;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+ int cnt;
+ int siz;
+ runsdef val;
+ struct bifgtw_cb_ctx fnctx;
+
+ bifcntargs(ctx, 2, argc);
+
+ /* get the arguments */
+ objn = runpopobj(ctx->bifcxrun);
+ prpn = runpopprp(ctx->bifcxrun);
+
+ /* make sure the property is a valid part of speech property */
+ if (!prpisvoc(prpn))
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "delword");
+
+ /* get the size of the list we'll need to build */
+ voc_count(voc, objn, prpn, &cnt, &siz);
+
+ /*
+ * calculate how much space it will take to make a list out of all
+ * these words: 2 bytes for the list length header; plus, for each
+ * entry, 1 byte for the type header, 2 bytes for the string size
+ * header, and possibly one extra byte for the two-word separator --
+ * a total of 4 bytes extra per word.
+ */
+ siz += 2 + 4*cnt;
+
+ /* reserve the space */
+ runhres(ctx->bifcxrun, siz, 0);
+
+ /* set up our callback context, and build the list */
+ fnctx.p = ctx->bifcxrun->runcxhp + 2;
+ fnctx.typ = prpn;
+ voc_iterate(voc, objn, bifgtw_cb, &fnctx);
+
+ /* set up the return value */
+ val.runstyp = DAT_LIST;
+ val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+
+ /* write the list length, and advance past the space we used */
+ oswp2(ctx->bifcxrun->runcxhp, fnctx.p - ctx->bifcxrun->runcxhp);
+ ctx->bifcxrun->runcxhp = fnctx.p;
+
+ /* return the list */
+ runrepush(ctx->bifcxrun, &val);
+}
+
+/* verbinfo service routine - add an object to the output list */
+static uchar *bifvin_putprpn(uchar *p, prpnum prpn)
+{
+ *p++ = DAT_PROPNUM;
+ oswp2(p, prpn);
+ return p + 2;
+}
+
+/* verbinfo */
+void bifvin(bifcxdef *ctx, int argc)
+{
+ objnum verb;
+ objnum prep;
+ uchar tplbuf[VOCTPL2SIZ];
+ int newstyle;
+
+ /* get the verb */
+ verb = runpopobj(ctx->bifcxrun);
+
+ /* check for the presence of a preposition */
+ if (argc == 1)
+ {
+ /* no preposition */
+ prep = MCMONINV;
+ }
+ else
+ {
+ /* the second argument is the preposition */
+ bifcntargs(ctx, 2, argc);
+ prep = runpopobj(ctx->bifcxrun);
+ }
+
+ /* look up the template */
+ if (voctplfnd(ctx->bifcxrun->runcxvoc, verb, prep, tplbuf, &newstyle))
+ {
+ prpnum prp_do, prp_verdo, prp_io, prp_verio;
+ int flg_dis_do;
+ ushort siz;
+ uchar *p;
+ runsdef val;
+
+ /* get the information from the template */
+ prp_do = voctpldo(tplbuf);
+ prp_verdo = voctplvd(tplbuf);
+ prp_io = voctplio(tplbuf);
+ prp_verio = voctplvi(tplbuf);
+ flg_dis_do = (voctplflg(tplbuf) & VOCTPLFLG_DOBJ_FIRST) != 0;
+
+ /*
+ * figure space for the return value: if there's a prep, three
+ * property pointers plus a boolean, otherwise just two property
+ * pointers
+ */
+ siz = 2 + 2*(2+1);
+ if (prep != MCMONINV)
+ siz += (2+1) + 1;
+
+ /* reserve the space */
+ runhres(ctx->bifcxrun, siz, 0);
+
+ /* build the output list */
+ p = ctx->bifcxrun->runcxhp;
+ oswp2(p, siz);
+ p += 2;
+
+ p = bifvin_putprpn(p, prp_verdo);
+ if (prep == MCMONINV)
+ {
+ p = bifvin_putprpn(p, prp_do);
+ }
+ else
+ {
+ p = bifvin_putprpn(p, prp_verio);
+ p = bifvin_putprpn(p, prp_io);
+ *p++ = runclog(flg_dis_do);
+ }
+
+ /* build the return value */
+ val.runstyp = DAT_LIST;
+ val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+
+ /* consume the space */
+ ctx->bifcxrun->runcxhp += siz;
+
+ /* return the list */
+ runrepush(ctx->bifcxrun, &val);
+ }
+ else
+ {
+ /* no template for this verb - return nil */
+ runpnil(ctx->bifcxrun);
+ }
+}
+
+
+/* clearscreen */
+void bifcls(bifcxdef *ctx, int argc)
+{
+ /* this takes no arguments */
+ bifcntargs(ctx, 0, argc);
+
+ /* flush any pending output */
+ tioflushn(ctx->bifcxtio, 0);
+
+ /* clear the screen */
+ oscls();
+}
+
+/*
+ * File operations
+ */
+
+/*
+ * fopen(file, mode).
+ *
+ * Operations are allowed only if they conform to the current I/O safety
+ * level. The safety level can be set by the user on the command line
+ * when running the game, and some implementations may allow the setting
+ * to be saved as a preference. The possible levels are:
+ *
+ *. 0 - minimum safety - read and write in any directory
+ *. 1 - read in any directory, write in current directory
+ *. 2 - read/write access in current directory only
+ *. 3 - read-only access in current directory only
+ *. 4 - maximum safety - no file I/O allowed
+ *
+ * When operations are allowed only in the current directory, the
+ * operations will fail if the filename contains any sort of path
+ * specifier (for example, on Unix, any file that contains a '/' is
+ * considered to have a path specifier, and will always fail if
+ * operations are only allowed in the current directory).
+ */
+void biffopen(bifcxdef *ctx, int argc)
+{
+ char fname[OSFNMAX];
+ uchar *p;
+ uchar *mode;
+ int modelen;
+ int fnum;
+ osfildef *fp;
+ int bin_mode = TRUE; /* flag: mode is binary (rather than text) */
+ int rw_mode = FALSE; /* flag: both read and write are allowed */
+ char main_mode; /* 'r' for read, 'w' for write */
+ int in_same_dir; /* flag: file is in current directory */
+ appctxdef *appctx;
+
+ bifcntargs(ctx, 2, argc);
+
+ /* get the filename */
+ p = runpopstr(ctx->bifcxrun);
+ bifcstr(ctx, fname, (size_t)sizeof(fname), p);
+
+ /*
+ * If it's a relative path, combine it with the game file path to form
+ * the absolute path. This ensures that relative paths are always
+ * relative to the original working directory if the OS-level working
+ * directory has changed.
+ */
+ if (!os_is_file_absolute(fname))
+ {
+ /* combine the game file path with the relative filename */
+ char newname[OSFNMAX];
+ os_build_full_path(newname, sizeof(newname),
+ ctx->bifcxrun->runcxgamepath, fname);
+
+ /* replace the original filename with the full path */
+ strcpy(fname, newname);
+ }
+
+ /* get the mode string */
+ mode = runpopstr(ctx->bifcxrun);
+ modelen = osrp2(mode) - 2;
+ mode += 2;
+ if (modelen < 1)
+ goto bad_mode;
+
+ /* allocate a filenum for the file */
+ for (fnum = 0 ; fnum < BIFFILMAX ; ++fnum)
+ {
+ if (ctx->bifcxfile[fnum].fp == 0)
+ break;
+ }
+ if (fnum == BIFFILMAX)
+ {
+ /* return nil to indicate failure */
+ runpnil(ctx->bifcxrun);
+ return;
+ }
+
+ /* parse the main mode */
+ switch(*mode)
+ {
+ case 'w':
+ case 'W':
+ main_mode = 'w';
+ break;
+
+ case 'r':
+ case 'R':
+ main_mode = 'r';
+ break;
+
+ default:
+ goto bad_mode;
+ }
+
+ /* skip the main mode, and check for a '+' flag */
+ ++mode;
+ --modelen;
+ if (modelen > 0 && *mode == '+')
+ {
+ /* note the read/write mode */
+ rw_mode = TRUE;
+
+ /* skip the speciifer */
+ ++mode;
+ --modelen;
+ }
+
+ /* check for a binary/text specifier */
+ if (modelen > 0)
+ {
+ switch(*mode)
+ {
+ case 'b':
+ case 'B':
+ bin_mode = TRUE;
+ break;
+
+ case 't':
+ case 'T':
+ bin_mode = FALSE;
+ break;
+
+ default:
+ goto bad_mode;
+ }
+
+ /* skip the binary/text specifier */
+ ++mode;
+ --modelen;
+ }
+
+ /* it's an error if there's anything left unparsed */
+ if (modelen > 0)
+ goto bad_mode;
+
+ /*
+ * If we have a host application context, and it provides a file
+ * safety level callback function, ask the host system for its
+ * current file safety level, which overrides our current setting.
+ */
+ appctx = ctx->bifcxappctx;
+ if (appctx != 0 && appctx->get_io_safety_level != 0)
+ {
+ /*
+ * ask the host system for the current level, and override any
+ * setting we previously had
+ */
+ (*appctx->get_io_safety_level)(
+ appctx->io_safety_level_ctx,
+ &ctx->bifcxsafetyr, &ctx->bifcxsafetyw);
+ }
+
+ /*
+ * Check to see if the file is in the current working directory - if
+ * not, we may have to disallow the operation based on safety level
+ * settings.
+ */
+ in_same_dir = os_is_file_in_dir(
+ fname, ctx->bifcxrun->runcxgamepath, TRUE, FALSE);
+
+ /* check file safety settings */
+ switch(main_mode)
+ {
+ case 'w':
+ /*
+ * writing - we must be at a safety level no higher than 2
+ * (read/write current directory) to write at all, and we must be
+ * level 0 to write a file that's not in the current directory
+ */
+ if (ctx->bifcxsafetyw > 2
+ || (!in_same_dir && ctx->bifcxsafetyw > 0))
+ {
+ /* this operation is not allowed - return failure */
+ runpnil(ctx->bifcxrun);
+ return;
+ }
+ break;
+
+ case 'r':
+ /*
+ * reading - we must be at a safety level no higher than 3 (read
+ * current directory) to read at all, and we must be at safety
+ * level 1 (read any directory) or lower to read a file that's not
+ * in the current directory
+ */
+ if (ctx->bifcxsafetyr > 3
+ || (!in_same_dir && ctx->bifcxsafetyr > 1))
+ {
+ /* this operation is not allowed - return failure */
+ runpnil(ctx->bifcxrun);
+ return;
+ }
+ break;
+
+ default:
+ /*
+ * fail the operation, as a code maintenance measure to make
+ * sure that we add appropriate cases to this switch (even if
+ * merely to allow the operation unconditionally) in the event
+ * that more modes are added in the future
+ */
+ goto bad_mode;
+ }
+
+ /* try opening the file */
+ switch(main_mode)
+ {
+ case 'w':
+ /* check for binary vs text mode */
+ if (bin_mode)
+ {
+ /*
+ * binary mode -- allow read/write or just writing, but in
+ * either case truncate the file if it already exists, and
+ * create a new file if it doesn't exist
+ */
+ if (rw_mode)
+ fp = osfoprwtb(fname, OSFTDATA);
+ else
+ fp = osfopwb(fname, OSFTDATA);
+ }
+ else
+ {
+ /* text mode - don't allow read/write on a text file */
+ if (rw_mode)
+ goto bad_mode;
+
+ /* open the file */
+ fp = osfopwt(fname, OSFTTEXT);
+ }
+ break;
+
+ case 'r':
+ /* check for binary vs text mode */
+ if (bin_mode)
+ {
+ /*
+ * Binary mode -- allow read/write or just reading; leave
+ * any existing file intact.
+ */
+ if (rw_mode)
+ {
+ /* open for reading and writing, keeping existing data */
+ fp = osfoprwb(fname, OSFTDATA);
+ }
+ else
+ {
+ /* open for read-only */
+ fp = osfoprb(fname, OSFTDATA);
+ }
+ }
+ else
+ {
+ /* text mode -- only allow reading */
+ if (rw_mode)
+ goto bad_mode;
+
+ /* open the file */
+ fp = osfoprt(fname, OSFTTEXT);
+ }
+ break;
+
+ default:
+ goto bad_mode;
+ }
+
+ /* if we couldn't open it, return nil */
+ if (fp == 0)
+ {
+ runpnil(ctx->bifcxrun);
+ return;
+ }
+
+ /* store the flags */
+ ctx->bifcxfile[fnum].flags = 0;
+ if (bin_mode)
+ ctx->bifcxfile[fnum].flags |= BIFFIL_F_BINARY;
+
+ /* remember the file handle */
+ ctx->bifcxfile[fnum].fp = fp;
+
+ /* return the file number (i.e., the slot number) */
+ runpnum(ctx->bifcxrun, (long)fnum);
+ return;
+
+
+ /* come here on a mode error */
+bad_mode:
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "fopen");
+}
+
+/* service routine for file routines - get and validate a file number */
+static osfildef *bif_get_file(bifcxdef *ctx, int *fnump, int *bin_modep)
+{
+ long fnum;
+
+ /* get the file number and make sure it's valid */
+ fnum = runpopnum(ctx->bifcxrun);
+ if (fnum < 0 || fnum >= BIFFILMAX || ctx->bifcxfile[fnum].fp == 0)
+ runsig(ctx->bifcxrun, ERR_BADFILE);
+
+ /* put the validated file number, if the caller wants it */
+ if (fnump != 0)
+ *fnump = (int)fnum;
+
+ /* set the binary-mode flag, if the caller wants it */
+ if (bin_modep != 0)
+ *bin_modep = ((ctx->bifcxfile[fnum].flags & BIFFIL_F_BINARY) != 0);
+
+ /* return the file array pointer */
+ return ctx->bifcxfile[fnum].fp;
+}
+
+void biffclose(bifcxdef *ctx, int argc)
+{
+ int fnum;
+ osfildef *fp;
+
+ /* get the file number */
+ bifcntargs(ctx, 1, argc);
+ fp = bif_get_file(ctx, &fnum, 0);
+
+ /* close the file and release the slot */
+ osfcls(fp);
+ ctx->bifcxfile[fnum].fp = 0;
+}
+
+void bifftell(bifcxdef *ctx, int argc)
+{
+ osfildef *fp;
+
+ /* get the file number */
+ bifcntargs(ctx, 1, argc);
+ fp = bif_get_file(ctx, (int *)0, 0);
+
+ /* return the seek position */
+ runpnum(ctx->bifcxrun, osfpos(fp));
+}
+
+void biffseek(bifcxdef *ctx, int argc)
+{
+ osfildef *fp;
+ long pos;
+
+ /* get the file pointer */
+ bifcntargs(ctx, 2, argc);
+ fp = bif_get_file(ctx, (int *)0, 0);
+
+ /* get the seek position, and seek there */
+ pos = runpopnum(ctx->bifcxrun);
+ osfseek(fp, pos, OSFSK_SET);
+}
+
+void biffseekeof(bifcxdef *ctx, int argc)
+{
+ osfildef *fp;
+
+ /* get the file pointer */
+ bifcntargs(ctx, 1, argc);
+ fp = bif_get_file(ctx, (int *)0, 0);
+
+ /* seek to the end */
+ osfseek(fp, 0L, OSFSK_END);
+}
+
+void biffwrite(bifcxdef *ctx, int argc)
+{
+ osfildef *fp;
+ char typ;
+ char buf[32];
+ runsdef val;
+ int bin_mode;
+
+ /* get the file */
+ bifcntargs(ctx, 2, argc);
+ fp = bif_get_file(ctx, (int *)0, &bin_mode);
+
+ /* get the value to write */
+ runpop(ctx->bifcxrun, &val);
+ typ = val.runstyp;
+
+ if (bin_mode)
+ {
+ /* put a byte indicating the type */
+ if (osfwb(fp, &typ, 1))
+ goto ret_error;
+
+ /* see what type of data we want to put */
+ switch(typ)
+ {
+ case DAT_NUMBER:
+ oswp4s(buf, val.runsv.runsvnum);
+ if (osfwb(fp, buf, 4))
+ goto ret_error;
+ break;
+
+ case DAT_SSTRING:
+ /* write the string, including the length prefix */
+ if (osfwb(fp, val.runsv.runsvstr, osrp2(val.runsv.runsvstr)))
+ goto ret_error;
+ break;
+
+ case DAT_TRUE:
+ /* all we need for this is the type prefix */
+ break;
+
+ default:
+ /* other types are not acceptable */
+ runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "fwrite");
+ }
+ }
+ else
+ {
+ uint rem;
+ uchar *p;
+
+ switch(typ)
+ {
+ case DAT_SSTRING:
+ /*
+ * Copy and translate the string to our buffer, in pieces if
+ * the size of the string exceeds that of our buffer. If we
+ * encounter any escape codes, translate them.
+ */
+ rem = osrp2(val.runsv.runsvstr) - 2;
+ p = val.runsv.runsvstr + 2;
+ while (rem > 0)
+ {
+ uchar *dst;
+ uchar dbuf[256];
+
+ /* fill up the buffer */
+ for (dst = dbuf ;
+ rem != 0 && (size_t)(dst - dbuf) < sizeof(dbuf) - 1 ;
+ ++p, --rem)
+ {
+ /* if we have an escape character, translate it */
+ if (*p == '\\' && rem > 1)
+ {
+ /* skip the opening slash */
+ ++p;
+ --rem;
+
+ /* translate it */
+ switch(*p)
+ {
+ case 'n':
+ *dst++ = '\n';
+ break;
+
+ case 't':
+ *dst++ = '\t';
+ break;
+
+ default:
+ *dst++ = *p;
+ break;
+ }
+ }
+ else
+ {
+ /* copy this character directly */
+ *dst++ = *p;
+ }
+ }
+
+ /* null-terminate the buffer */
+ *dst = '\0';
+
+ /* write it out */
+ if (osfputs((char *)dbuf, fp) == EOF)
+ goto ret_error;
+ }
+
+ /* done */
+ break;
+
+ default:
+ /* other types are not allowed */
+ runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "fwrite");
+ }
+ }
+
+ /* success */
+ runpnil(ctx->bifcxrun);
+ return;
+
+ret_error:
+ val.runstyp = DAT_TRUE;
+ runpush(ctx->bifcxrun, DAT_TRUE, &val);
+}
+
+void biffread(bifcxdef *ctx, int argc)
+{
+ osfildef *fp;
+ char typ;
+ char buf[32];
+ runsdef val;
+ ushort len;
+ int bin_mode;
+
+ /* get the file pointer */
+ bifcntargs(ctx, 1, argc);
+ fp = bif_get_file(ctx, (int *)0, &bin_mode);
+
+ if (bin_mode)
+ {
+ /* binary file - read the type byte */
+ if (osfrb(fp, &typ, 1))
+ goto ret_error;
+
+ /* read the data according to the type */
+ switch(typ)
+ {
+ case DAT_NUMBER:
+ if (osfrb(fp, buf, 4))
+ goto ret_error;
+ runpnum(ctx->bifcxrun, osrp4s(buf));
+ break;
+
+ case DAT_SSTRING:
+ /* get the size */
+ if (osfrb(fp, buf, 2))
+ goto ret_error;
+ len = osrp2(buf);
+
+ /* reserve space */
+ runhres(ctx->bifcxrun, len, 0);
+
+ /* read the string into the reserved space */
+ if (osfrb(fp, ctx->bifcxrun->runcxhp + 2, len - 2))
+ goto ret_error;
+
+ /* set up the string */
+ oswp2(ctx->bifcxrun->runcxhp, len);
+ val.runstyp = DAT_SSTRING;
+ val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+
+ /* consume the space */
+ ctx->bifcxrun->runcxhp += len;
+
+ /* push the value */
+ runrepush(ctx->bifcxrun, &val);
+ break;
+
+ case DAT_TRUE:
+ val.runstyp = DAT_TRUE;
+ runpush(ctx->bifcxrun, DAT_TRUE, &val);
+ break;
+
+ default:
+ goto ret_error;
+ }
+ }
+ else
+ {
+ uchar dbuf[257];
+ uchar *dst;
+ uchar *src;
+ uint dlen;
+ uint res_total;
+ int found_nl;
+
+ /*
+ * reserve some space in the heap - we'll initially reserve
+ * space for twice our buffer, in case every single character
+ * needs to be expanded into an escape sequence
+ */
+ res_total = sizeof(dbuf) * 2;
+ runhres(ctx->bifcxrun, res_total, 0);
+
+ /* set up our output value */
+ val.runstyp = DAT_SSTRING;
+ val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+ dst = ctx->bifcxrun->runcxhp + 2;
+
+ /* keep going until we find a newline or run out of data */
+ for (found_nl = FALSE ; !found_nl ; )
+ {
+ /* text-mode - read the result into our buffer */
+ if (!osfgets((char *)dbuf, sizeof(dbuf) - 1, fp))
+ {
+ /*
+ * if we found a newline, return what we have;
+ * otherwise, return an error
+ */
+ if (found_nl)
+ break;
+ else
+ goto ret_error;
+ }
+
+ /*
+ * make sure it's null-terminated, in case the buffer was
+ * full
+ */
+ dbuf[256] = '\0';
+
+ /* translate into the heap area we've reserved */
+ for (src = dbuf ; *src != '\0' ; ++src, ++dst)
+ {
+ /* determine if we need translations */
+ switch(*src)
+ {
+ case '\n':
+ case '\r':
+ /* translate to a newline sequence */
+ *dst++ = '\\';
+ *dst = 'n';
+
+ /* note that we've found our newline */
+ found_nl = TRUE;
+ break;
+
+ case '\t':
+ /* translate to a tab sequence */
+ *dst++ = '\\';
+ *dst = 't';
+ break;
+
+ case '\\':
+ /* expand to a double-backslash sequence */
+ *dst++ = '\\';
+ *dst = '\\';
+ break;
+
+ default:
+ /* leave other characters intact */
+ *dst = *src;
+ break;
+ }
+ }
+
+ /*
+ * If we didn't find the newline, we'll need more space.
+ * This is a bit tricky, because the space we've already set
+ * up may move if we compact the heap while asking for more
+ * space. So, remember our current length, reserve another
+ * buffer-full of space, and set everything up at the new
+ * output location if necessary.
+ */
+ if (!found_nl)
+ {
+ /* reserve another buffer-full (double for expansion) */
+ res_total += sizeof(dbuf) * 2;
+
+ /* note our current offset */
+ dlen = dst - val.runsv.runsvstr;
+ oswp2(val.runsv.runsvstr, dlen);
+
+ /* ask for the space */
+ runhres(ctx->bifcxrun, res_total, 0);
+
+ /*
+ * Since we were at the top of the heap before, we
+ * should still be at the top of the heap. If not,
+ * we'll have to copy from our old location to the new
+ * top of the heap.
+ */
+ if (val.runsv.runsvstr != ctx->bifcxrun->runcxhp)
+ {
+ /* copy our existing text to our new location */
+ memmove(ctx->bifcxrun->runcxhp, val.runsv.runsvstr, dlen);
+
+ /* fix up our pointer */
+ val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+ }
+
+ /* re-establish our output pointer at our new location */
+ dst = val.runsv.runsvstr + dlen;
+ }
+ }
+
+ /* finish setting up the string */
+ dlen = dst - val.runsv.runsvstr;
+ oswp2(val.runsv.runsvstr, dlen);
+
+ /* consume the space */
+ ctx->bifcxrun->runcxhp += dlen;
+
+ /* push the value */
+ runrepush(ctx->bifcxrun, &val);
+ }
+
+ /* success - we've already pushed the return value */
+ return;
+
+ret_error:
+ runpnil(ctx->bifcxrun);
+}
+
+void bifcapture(bifcxdef *ctx, int argc)
+{
+ mcmcxdef *mcx = ctx->bifcxrun->runcxmem;
+ mcmon obj;
+ uint siz;
+ uint ofs;
+ uchar *p;
+
+ /* get the capture on/off flag */
+ bifcntargs(ctx, 1, argc);
+ switch(runtostyp(ctx->bifcxrun))
+ {
+ case DAT_TRUE:
+ /* turn on capturing */
+ tiocapture(ctx->bifcxtio, mcx, TRUE);
+
+ /*
+ * The return value is a status code used to restore the
+ * original status on the bracketing call to turn off output.
+ * The only status necessary is the current output size.
+ */
+ siz = tiocapturesize(ctx->bifcxtio);
+ runpnum(ctx->bifcxrun, (long)siz);
+ break;
+
+ case DAT_NUMBER:
+ /* get the original offset */
+ ofs = runpopnum(ctx->bifcxrun);
+
+ /* get the capture object and size */
+ obj = tiogetcapture(ctx->bifcxtio);
+ siz = tiocapturesize(ctx->bifcxtio);
+ if (obj == MCMONINV)
+ {
+ runpnil(ctx->bifcxrun);
+ return;
+ }
+
+ /* turn off capturing and reset the buffer on the outermost call */
+ if (ofs == 0)
+ {
+ tiocapture(ctx->bifcxtio, mcx, FALSE);
+ tioclrcapture(ctx->bifcxtio);
+ }
+
+ /* lock the object */
+ p = mcmlck(mcx, obj);
+
+ /* include only the part that happened after the matching call */
+ p += ofs;
+ siz = (ofs > siz) ? 0 : siz - ofs;
+
+ ERRBEGIN(ctx->bifcxerr)
+
+ /* push the string onto the stack */
+ runpstr(ctx->bifcxrun, (char *)p, siz, 0);
+
+ ERRCLEAN(ctx->bifcxerr)
+ /* done with the object - unlock it */
+ mcmunlck(mcx, obj);
+ ERRENDCLN(ctx->bifcxerr)
+
+ /* done with the object - unlock it */
+ mcmunlck(mcx, obj);
+ break;
+
+ default:
+ runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "outcapture");
+ }
+}
+
+/*
+ * systemInfo
+ */
+void bifsysinfo(bifcxdef *ctx, int argc)
+{
+ runsdef val;
+ int id;
+ long result;
+
+ /* see what we have */
+ switch(id = (int)runpopnum(ctx->bifcxrun))
+ {
+ case SYSINFO_SYSINFO:
+ /* systemInfo call is supported in this version - return true */
+ bifcntargs(ctx, 1, argc);
+ val.runstyp = DAT_TRUE;
+ runpush(ctx->bifcxrun, DAT_TRUE, &val);
+ break;
+
+ case SYSINFO_VERSION:
+ /* get the run-time version string */
+ bifcntargs(ctx, 1, argc);
+ runpushcstr(ctx->bifcxrun, TADS_RUNTIME_VERSION,
+ strlen(TADS_RUNTIME_VERSION), 0);
+ break;
+
+ case SYSINFO_OS_NAME:
+ /* get the operating system name */
+ bifcntargs(ctx, 1, argc);
+ runpushcstr(ctx->bifcxrun, OS_SYSTEM_NAME, strlen(OS_SYSTEM_NAME), 0);
+ break;
+
+ case SYSINFO_HTML:
+ case SYSINFO_JPEG:
+ case SYSINFO_PNG:
+ case SYSINFO_WAV:
+ case SYSINFO_MIDI:
+ case SYSINFO_WAV_MIDI_OVL:
+ case SYSINFO_WAV_OVL:
+ case SYSINFO_PREF_IMAGES:
+ case SYSINFO_PREF_SOUNDS:
+ case SYSINFO_PREF_MUSIC:
+ case SYSINFO_PREF_LINKS:
+ case SYSINFO_MPEG:
+ case SYSINFO_MPEG1:
+ case SYSINFO_MPEG2:
+ case SYSINFO_MPEG3:
+ case SYSINFO_LINKS_HTTP:
+ case SYSINFO_LINKS_FTP:
+ case SYSINFO_LINKS_NEWS:
+ case SYSINFO_LINKS_MAILTO:
+ case SYSINFO_LINKS_TELNET:
+ case SYSINFO_PNG_TRANS:
+ case SYSINFO_PNG_ALPHA:
+ case SYSINFO_OGG:
+ case SYSINFO_MNG:
+ case SYSINFO_MNG_TRANS:
+ case SYSINFO_MNG_ALPHA:
+ case SYSINFO_TEXT_HILITE:
+ case SYSINFO_INTERP_CLASS:
+ /*
+ * these information types are all handled by the OS layer, and
+ * take no additional arguments
+ */
+ bifcntargs(ctx, 1, argc);
+ if (os_get_sysinfo(id, 0, &result))
+ {
+ /* we got a valid result - return it */
+ runpnum(ctx->bifcxrun, result);
+ }
+ else
+ {
+ /* the code was unknown - return nil */
+ runpnil(ctx->bifcxrun);
+ }
+ break;
+
+ case SYSINFO_HTML_MODE:
+ /* ask the output formatter for its current HTML setting */
+ bifcntargs(ctx, 1, argc);
+ val.runstyp = runclog(tio_is_html_mode());
+ runpush(ctx->bifcxrun, val.runstyp, &val);
+ break;
+
+ case SYSINFO_TEXT_COLORS:
+ /*
+ * Text colors are only supported in full HTML interpreters. If
+ * this is an HTML interpreter, ask the underlying OS layer about
+ * color support; otherwise, colors are not available, since we
+ * don't handle colors in our text-only HTML subset.
+ *
+ * Colors are NOT supported in the HTML mini-parser in text-only
+ * interpreters in TADS 2. So, even if we're running in HTML
+ * mode, if this is a text-only interpreter, we can't display text
+ * colors.
+ */
+ bifcntargs(ctx, 1, argc);
+ if (os_get_sysinfo(SYSINFO_HTML, 0, &result) && result != 0)
+ {
+ /*
+ * we're in HTML mode, so ask the underlying HTML OS
+ * implementation for its level of text color support
+ */
+ if (os_get_sysinfo(id, 0, &result))
+ {
+ /* push the OS-level result */
+ runpnum(ctx->bifcxrun, result);
+ }
+ else
+ {
+ /* the OS code doesn't recognize it; assume no support */
+ runpnum(ctx->bifcxrun, SYSINFO_TXC_NONE);
+ }
+ }
+ else
+ {
+ /* we're a text-only interpreter - no color support */
+ runpnum(ctx->bifcxrun, SYSINFO_TXC_NONE);
+ }
+ break;
+
+ case SYSINFO_BANNERS:
+ /* TADS 2 does not offer banner support */
+ bifcntargs(ctx, 1, argc);
+ runpnum(ctx->bifcxrun, 0);
+ break;
+
+ default:
+ /*
+ * Other codes fail harmlessly with a nil return value. Pop all
+ * remaining arguments and return nil.
+ */
+ for ( ; argc > 1 ; --argc)
+ rundisc(ctx->bifcxrun);
+ runpnil(ctx->bifcxrun);
+ break;
+ }
+}
+
+/*
+ * morePrompt - display the more prompt and wait for the user to respond
+ */
+void bifmore(bifcxdef *ctx, int argc)
+{
+ /* this function takes no arguments */
+ bifcntargs(ctx, 0, argc);
+
+ /* display the MORE prompt */
+ tioflushn(ctx->bifcxtio, 1);
+ out_more_prompt();
+}
+
+/*
+ * parserSetMe
+ */
+void bifsetme(bifcxdef *ctx, int argc)
+{
+ objnum new_me;
+
+ /* this function takes one argument */
+ bifcntargs(ctx, 1, argc);
+
+ /* get the new "Me" object */
+ new_me = runpopobj(ctx->bifcxrun);
+
+ /* "Me" cannot be nil */
+ if (new_me == MCMONINV)
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "parserSetMe");
+
+ /* set the current "Me" object in the parser */
+ voc_set_me(ctx->bifcxrun->runcxvoc, new_me);
+}
+
+/*
+ * parserGetMe
+ */
+void bifgetme(bifcxdef *ctx, int argc)
+{
+ /* this function takes no arguments */
+ bifcntargs(ctx, 0, argc);
+
+ /* return the current Me object */
+ runpobj(ctx->bifcxrun, ctx->bifcxrun->runcxvoc->voccxme);
+}
+
+/*
+ * reSearch
+ */
+void bifresearch(bifcxdef *ctx, int argc)
+{
+ uchar *patstr;
+ size_t patlen;
+ uchar *searchstr;
+ size_t searchlen;
+ int result_len;
+ int match_ofs;
+
+ /* this function takes two parameters: pattern, string */
+ bifcntargs(ctx, 2, argc);
+
+ /* get the pattern string */
+ patstr = runpopstr(ctx->bifcxrun);
+ patlen = osrp2(patstr) - 2;
+ patstr += 2;
+
+ /* get the search string */
+ searchstr = runpopstr(ctx->bifcxrun);
+ searchlen = osrp2(searchstr) - 2;
+ searchstr += 2;
+
+ /* search for the pattern in the string */
+ match_ofs = re_compile_and_search(&ctx->bifcxregex,
+ (char *)patstr, patlen,
+ (char *)searchstr, searchlen,
+ &result_len);
+
+ /*
+ * if we didn't match, return nil; otherwise, return a list with the
+ * match offset and length
+ */
+ if (match_ofs < 0)
+ {
+ /* no match - return nil */
+ runpnil(ctx->bifcxrun);
+ }
+ else
+ {
+ ushort listsiz;
+ runsdef val;
+ uchar *p;
+
+ /*
+ * build a list consisting of two numbers and a string: two
+ * bytes for the list header, then two elements at (one byte for
+ * the datatype header, four bytes for the number), then the
+ * string element with (one byte for the datatype, two bytes for
+ * the string length prefix, and the bytes of the string)
+ */
+ listsiz = 2 + (1+4)*2 + (1 + 2 + (ushort)(result_len));
+
+ /* allocate the space */
+ runhres(ctx->bifcxrun, listsiz, 0);
+
+ /* set up the list stack item */
+ val.runstyp = DAT_LIST;
+ p = val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+
+ /* set the list's length */
+ oswp2(p, listsiz);
+ p += 2;
+
+ /*
+ * Add the offset element. For consistency with TADS
+ * conventions, use 1 as the offset of the first character in
+ * the string - this makes it easy to use the offset value with
+ * substr().
+ */
+ *p++ = DAT_NUMBER;
+ oswp4s(p, match_ofs + 1);
+ p += 4;
+
+ /* add the length element */
+ *p++ = DAT_NUMBER;
+ oswp4s(p, result_len);
+ p += 4;
+
+ /* add the result string */
+ *p++ = DAT_SSTRING;
+ oswp2(p, result_len + 2);
+ p += 2;
+ memcpy(p, ctx->bifcxregex.strbuf + match_ofs, result_len);
+
+ /* reserve the space in the heap */
+ ctx->bifcxrun->runcxhp += listsiz;
+
+ /* return the list */
+ runrepush(ctx->bifcxrun, &val);
+ }
+}
+
+/* reGetGroup */
+void bifregroup(bifcxdef *ctx, int argc)
+{
+ int grp;
+ size_t len;
+ re_group_register *reg;
+ ushort hplen;
+ runsdef val;
+ uchar *p;
+ long numval;
+
+ /* this function takes one parameter: the group number to retrieve */
+ bifcntargs(ctx, 1, argc);
+
+ /* get the group number */
+ grp = (int)runpopnum(ctx->bifcxrun);
+
+ /* make sure it's within range */
+ if (grp < 1 || grp > RE_GROUP_REG_CNT)
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "reGetGroup");
+
+ /* adjust from a 1-bias to an array index */
+ --grp;
+
+ /* if the group was never set, return nil */
+ if (grp >= ctx->bifcxregex.cur_group)
+ {
+ runpnil(ctx->bifcxrun);
+ return;
+ }
+
+ /* get the register */
+ reg = &ctx->bifcxregex.regs[grp];
+
+ /* if the group wasn't set, return nil */
+ if (reg->start_ofs == 0 || reg->end_ofs == 0)
+ {
+ runpnil(ctx->bifcxrun);
+ return;
+ }
+
+ /* calculate the length of the string in this group */
+ len = reg->end_ofs - reg->start_ofs;
+
+ /*
+ * reserve the necessary heap space: two bytes for the list length
+ * prefix, two number elements (one byte each for the type, four
+ * bytes each for the value), and the string element (one byte for
+ * the type, two bytes for the length prefix, plus the string
+ * itself).
+ */
+ hplen = (ushort)(2 + 2*(1+4) + (1 + 2 + len));
+ runhres(ctx->bifcxrun, hplen, 0);
+
+ /* set up the stack value */
+ val.runstyp = DAT_LIST;
+ p = val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+
+ /* put in the list length prefix */
+ oswp2(p, hplen);
+ p += 2;
+
+ /* add the starting character position of the group - adjust to 1-bias */
+ *p++ = DAT_NUMBER;
+ numval = (long)(reg->start_ofs - ctx->bifcxregex.strbuf) + 1;
+ oswp4s(p, numval);
+ p += 4;
+
+ /* add the length of the group */
+ *p++ = DAT_NUMBER;
+ numval = (long)(reg->end_ofs - reg->start_ofs);
+ oswp4s(p, numval);
+ p += 4;
+
+ /* set up the string */
+ *p++ = DAT_SSTRING;
+ oswp2(p, len+2);
+ p += 2;
+ memcpy(p, reg->start_ofs, len);
+
+ /* consume the heap space */
+ ctx->bifcxrun->runcxhp += hplen;
+
+ /* push the result */
+ runrepush(ctx->bifcxrun, &val);
+}
+
+
+/*
+ * inputevent
+ */
+void bifinpevt(bifcxdef *ctx, int argc)
+{
+ unsigned long timeout = 0;
+ int use_timeout = FALSE;
+ os_event_info_t info;
+ int evt;
+ uchar *p;
+ ushort lstsiz;
+ runsdef val;
+ size_t paramlen = 0;
+ char keyname[20];
+
+ /* check for a timeout value */
+ if (argc == 0)
+ {
+ /* there's no timeout */
+ use_timeout = FALSE;
+ timeout = 0;
+ }
+ else if (argc >= 1)
+ {
+ /* get the timeout value */
+ use_timeout = TRUE;
+ timeout = (unsigned long)runpopnum(ctx->bifcxrun);
+ }
+
+ /* ensure we don't have too many arguments */
+ if (argc > 1)
+ runsig(ctx->bifcxrun, ERR_BIFARGC);
+
+ /* flush any pending output */
+ tioflushn(ctx->bifcxtio, 0);
+
+ /* reset count of lines since keyboard input */
+ tioreset(ctx->bifcxtio);
+
+ /* ask the OS code for an event */
+ evt = os_get_event(timeout, use_timeout, &info);
+
+ /*
+ * the list always minimally needs two bytes of length prefix plus a
+ * number with the event code (one byte for the type, four bytes for
+ * the value)
+ */
+ lstsiz = 2 + (1 + 4);
+
+ /* figure out how much space we'll need based on the event type */
+ switch(evt)
+ {
+ case OS_EVT_KEY:
+ /*
+ * we need space for a string with one or two bytes (depending
+ * on whether or not we have an extended key code) - 1 byte for
+ * type code, 2 for length prefix, and 1 or 2 for the string's
+ * contents
+ */
+ paramlen = (info.key[0] == 0 ? 2 : 1);
+
+ /* map the extended key */
+ get_ext_key_name(keyname, info.key[0], info.key[1]);
+
+ /* determine the length we need for the string */
+ paramlen = strlen(keyname);
+
+ /* add it into the list */
+ lstsiz += 1 + 2 + paramlen;
+ break;
+
+ case OS_EVT_HREF:
+ /*
+ * we need space for the href string - 1 byte for type code, 2
+ * for length prefix, plus the string's contents
+ */
+ paramlen = strlen(info.href);
+ lstsiz += 1 + 2 + (ushort)paramlen;
+ break;
+
+ default:
+ /* other event types have no extra data */
+ break;
+ }
+
+ /* allocate space for the list */
+ runhres(ctx->bifcxrun, lstsiz, 0);
+
+ /* set up the stack value */
+ val.runstyp = DAT_LIST;
+ p = val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+
+ /* set up the list length prefix */
+ oswp2(p, lstsiz);
+ p += 2;
+
+ /* set up the event type element */
+ *p++ = DAT_NUMBER;
+ oswp4s(p, evt);
+ p += 4;
+
+ /* add the event parameters, if any */
+ switch(evt)
+ {
+ case OS_EVT_KEY:
+ /* set up the string for the key */
+ *p++ = DAT_SSTRING;
+ oswp2(p, paramlen + 2);
+ p += 2;
+
+ /* add the characters to the string */
+ memcpy(p, keyname, paramlen);
+ p += paramlen;
+ break;
+
+ case OS_EVT_HREF:
+ /* add the string for the href */
+ *p++ = DAT_SSTRING;
+ oswp2(p, paramlen + 2);
+ memcpy(p + 2, info.href, paramlen);
+ break;
+ }
+
+ /* consume the heap space */
+ ctx->bifcxrun->runcxhp += lstsiz;
+
+ /* push the result */
+ runrepush(ctx->bifcxrun, &val);
+}
+
+/* timeDelay */
+void bifdelay(bifcxdef *ctx, int argc)
+{
+ long delay;
+
+ /* ensure we have the right number of arguments */
+ bifcntargs(ctx, 1, argc);
+
+ /* flush any pending output */
+ tioflushn(ctx->bifcxtio, 0);
+
+ /* get the delay time */
+ delay = runpopnum(ctx->bifcxrun);
+
+ /* let the system perform the delay */
+ os_sleep_ms(delay);
+}
+
+/* setOutputFilter */
+void bifsetoutfilter(bifcxdef *ctx, int argc)
+{
+ /* ensure we have the right number of arguments */
+ bifcntargs(ctx, 1, argc);
+
+ /* see what we have */
+ switch(runtostyp(ctx->bifcxrun))
+ {
+ case DAT_NIL:
+ /* remove the current filter */
+ out_set_filter(MCMONINV);
+
+ /* discard the argument */
+ rundisc(ctx->bifcxrun);
+ break;
+
+ case DAT_FNADDR:
+ /* set the filter to the given function */
+ out_set_filter(runpopfn(ctx->bifcxrun));
+ break;
+
+ default:
+ /* anything else is invalid */
+ runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "setOutputFilter");
+ }
+}
+
+/*
+ * Get an optional object argument. If the next argument is not an
+ * object value, or we're out of arguments, we'll return MCMONINV.
+ * Otherwise, we'll pop the object value and return it, decrementing the
+ * remaining argument counter provided.
+ */
+static objnum bif_get_optional_obj_arg(bifcxdef *ctx, int *rem_argc)
+{
+ /* if we're out of arguments, there's no object value */
+ if (*rem_argc == 0)
+ return MCMONINV;
+
+ /*
+ * if the next argument is not an object or nil, we're out of object
+ * arguments
+ */
+ if (runtostyp(ctx->bifcxrun) != DAT_OBJECT
+ && runtostyp(ctx->bifcxrun) != DAT_NIL)
+ return MCMONINV;
+
+ /* we have an object - remove it from the remaining argument count */
+ --(*rem_argc);
+
+ /* pop and return the object value */
+ return runpopobjnil(ctx->bifcxrun);
+}
+
+/*
+ * execCommand flag values
+ */
+#define EC_HIDE_SUCCESS 0x00000001
+#define EC_HIDE_ERROR 0x00000002
+#define EC_SKIP_VALIDDO 0x00000004
+#define EC_SKIP_VALIDIO 0x00000008
+
+/*
+ * execCommand - execute a recursive command
+ */
+void bifexec(bifcxdef *ctx, int argc)
+{
+ objnum actor;
+ objnum verb;
+ objnum dobj;
+ objnum prep;
+ objnum iobj;
+ int err;
+ uint capture_start = 0;
+ uint capture_end;
+ objnum capture_obj;
+ ulong flags;
+ int hide_any;
+ int rem_argc;
+
+ /*
+ * Check for the correct argument count. The first two arguments
+ * are required; additional arguments are optional.
+ */
+ if (argc < 2 || argc > 6)
+ runsig(ctx->bifcxrun, ERR_BIFARGC);
+
+ /* pop the arguments - actor, verb, dobj, prep, iobj */
+ actor = runpopobjnil(ctx->bifcxrun);
+ verb = runpopobjnil(ctx->bifcxrun);
+
+ /*
+ * The other object arguments are optional. If we run into a
+ * numeric argument, it's the flags value, in which case we're out
+ * of objects.
+ */
+ rem_argc = argc - 2;
+ dobj = bif_get_optional_obj_arg(ctx, &rem_argc);
+ prep = bif_get_optional_obj_arg(ctx, &rem_argc);
+ iobj = bif_get_optional_obj_arg(ctx, &rem_argc);
+
+ /* if we have a flags argument, pop it */
+ if (rem_argc > 0)
+ {
+ /* the last argument is the flags - pop the numeric value */
+ flags = runpopnum(ctx->bifcxrun);
+
+ /* remove it from the remaining argument counter */
+ --rem_argc;
+ }
+ else
+ {
+ /* no flags specified - use zero by default */
+ flags = 0;
+ }
+
+ /*
+ * make sure we don't have any arguments left - if we do, then it
+ * means that we got an incorrect type and skipped an argument when
+ * we were trying to sense the meanings of the arguments from their
+ * types
+ */
+ if (rem_argc != 0)
+ runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "execCommand");
+
+ /* if we're hiding any output, start output capture */
+ hide_any = ((flags & (EC_HIDE_SUCCESS | EC_HIDE_ERROR)) != 0);
+ if (hide_any)
+ {
+ /* start capturing */
+ tiocapture(ctx->bifcxtio, ctx->bifcxrun->runcxmem, TRUE);
+
+ /* note the current output position */
+ capture_start = tiocapturesize(ctx->bifcxtio);
+ }
+
+ /* execute the command */
+ err = execmd_recurs(ctx->bifcxrun->runcxvoc,
+ actor, verb, dobj, prep, iobj,
+ (flags & EC_SKIP_VALIDDO) == 0,
+ (flags & EC_SKIP_VALIDIO) == 0);
+
+ /* if we're hiding any output, end hiding */
+ if (hide_any)
+ {
+ uchar *p;
+ int hide;
+
+ /* get the capture buffer size */
+ capture_end = tiocapturesize(ctx->bifcxtio);
+
+ /* turn off capture if it wasn't already on when we started */
+ if (capture_start == 0)
+ tiocapture(ctx->bifcxtio, ctx->bifcxrun->runcxmem, FALSE);
+
+ /* determine whether we're hiding or showing the result */
+ if (err == 0)
+ hide = ((flags & EC_HIDE_SUCCESS) != 0);
+ else
+ hide = ((flags & EC_HIDE_ERROR) != 0);
+
+ /* show or hide the result, as appropriate */
+ if (hide)
+ {
+ /*
+ * We're hiding this result, so do not display the string.
+ * If there's an enclosing capture, remove the string from
+ * the enclosing capture.
+ */
+ if (capture_start != 0)
+ tiopopcapture(ctx->bifcxtio, capture_start);
+ }
+ else
+ {
+ /*
+ * We're showing the text. If we're in an enclosing
+ * capture, do nothing - simply leave the string in the
+ * enclosing capture buffer; otherwise, actually display it
+ */
+ if (capture_start == 0)
+ {
+ /* lock the capture object */
+ capture_obj = tiogetcapture(ctx->bifcxtio);
+ p = mcmlck(ctx->bifcxrun->runcxmem, capture_obj);
+
+ ERRBEGIN(ctx->bifcxerr)
+ {
+ /* display the string */
+ outformatlen((char *)p + capture_start,
+ capture_end - capture_start);
+ }
+ ERRCLEAN(ctx->bifcxerr)
+ {
+ /* unlock the capture object before signalling out */
+ mcmunlck(ctx->bifcxrun->runcxmem, capture_obj);
+ }
+ ERRENDCLN(ctx->bifcxerr);
+
+ /* unlock the capture object */
+ mcmunlck(ctx->bifcxrun->runcxmem, capture_obj);
+ }
+ }
+
+ /* clear the capture buffer if it wasn't on when we started */
+ if (capture_start == 0)
+ tioclrcapture(ctx->bifcxtio);
+ }
+
+ /* push the result code */
+ runpnum(ctx->bifcxrun, err);
+}
+
+/*
+ * parserGetObj - get one of the objects associated with the command
+ */
+void bifgetobj(bifcxdef *ctx, int argc)
+{
+ int id;
+ objnum obj = 0;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ /* check the argument count */
+ bifcntargs(ctx, 1, argc);
+
+ /* get the argument */
+ id = (int)runpopnum(ctx->bifcxrun);
+
+ /* get the appropriate object */
+ switch(id)
+ {
+ case 1:
+ /* get the current actor */
+ obj = voc->voccxactor;
+
+ /* if there's no current actor, use the current 'me' by default */
+ if (obj == MCMONINV)
+ obj = voc->voccxme;
+
+ /* done */
+ break;
+
+ case 2:
+ /* verb */
+ obj = voc->voccxverb;
+ break;
+
+ case 3:
+ /* direct object */
+ obj = (voc->voccxdobj == 0 ? MCMONINV : voc->voccxdobj->vocolobj);
+ break;
+
+ case 4:
+ /* preposition */
+ obj = voc->voccxprep;
+ break;
+
+ case 5:
+ /* indirect object */
+ obj = (voc->voccxiobj == 0 ? MCMONINV : voc->voccxiobj->vocolobj);
+ break;
+
+ case 6:
+ /* "it" */
+ obj = voc->voccxit;
+ break;
+
+ case 7:
+ /* "him" */
+ obj = voc->voccxhim;
+ break;
+
+ case 8:
+ /* "her" */
+ obj = voc->voccxher;
+ break;
+
+ case 9:
+ /* them */
+ voc_push_objlist(voc, voc->voccxthm, voc->voccxthc);
+
+ /*
+ * return directly, since we've already pushed the result (it's
+ * a list, not an object)
+ */
+ return;
+
+ default:
+ /* invalid argument */
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "parserGetObj");
+ break;
+ }
+
+ /* return the object */
+ runpobj(ctx->bifcxrun, obj);
+}
+
+/*
+ * parseNounList - parse a noun list. Call like this:
+ *
+ * parserParseNounList(wordlist, typelist, starting_index, complain,
+ * multi, check_actor);
+ */
+void bifparsenl(bifcxdef *ctx, int argc)
+{
+ /* check the argument count */
+ bifcntargs(ctx, 6, argc);
+
+ /* call the parser */
+ voc_parse_np(ctx->bifcxrun->runcxvoc);
+}
+
+/*
+ * parserTokenize - given a string, produce a list of tokens. Returns
+ * nil on error, or a list of token strings.
+ *
+ * parserTokenize(commandString);
+ */
+void bifprstok(bifcxdef *ctx, int argc)
+{
+ /* check arguments */
+ bifcntargs(ctx, 1, argc);
+
+ /* call the parser */
+ voc_parse_tok(ctx->bifcxrun->runcxvoc);
+}
+
+/*
+ * parserGetTokTypes - given a list of tokens (represented as strings),
+ * get a corresponding list of token types.
+ *
+ * parserGetTokTypes(tokenList);
+ */
+void bifprstoktyp(bifcxdef *ctx, int argc)
+{
+ /* check arguments */
+ bifcntargs(ctx, 1, argc);
+
+ /* call the parser */
+ voc_parse_types(ctx->bifcxrun->runcxvoc);
+}
+
+/*
+ * parserDictLookup - given a list of tokens and their types, produce a
+ * list of all of the objects that match all of the words.
+ *
+ * parserDictLookup(tokenList, typeList);
+ */
+void bifprsdict(bifcxdef *ctx, int argc)
+{
+ /* check arguments */
+ bifcntargs(ctx, 2, argc);
+
+ /* call the parser */
+ voc_parse_dict_lookup(ctx->bifcxrun->runcxvoc);
+}
+
+/*
+ * parserResolveObjects - resolve an object list of the sort returned by
+ * parseNounList. Validates and disambiguates the objects.
+ *
+ * parserResolveObjects(actor, verb, prep, otherobj, usageType,
+ * verprop, tokenList, objList, silent)
+ */
+void bifprsrslv(bifcxdef *ctx, int argc)
+{
+ /* check arguments */
+ bifcntargs(ctx, 9, argc);
+
+ /* call the parser */
+ voc_parse_disambig(ctx->bifcxrun->runcxvoc);
+}
+
+/*
+ * parserReplaceCommand - replace the current command line with a new
+ * string. Aborts the current command.
+ */
+void bifprsrplcmd(bifcxdef *ctx, int argc)
+{
+ /* check arguments */
+ bifcntargs(ctx, 1, argc);
+
+ /* call the parser */
+ voc_parse_replace_cmd(ctx->bifcxrun->runcxvoc);
+}
+
+/*
+ * exitobj - throw a RUNEXITOBJ error
+ */
+void bifexitobj(bifcxdef *ctx, int argc)
+{
+ /* no arguments are allowed */
+ bifcntargs(ctx, 0, argc);
+
+ /* throw the RUNEXITOBJ error */
+ errsig(ctx->bifcxerr, ERR_RUNEXITOBJ);
+}
+
+/*
+ * Standard system button labels for bifinpdlg()
+ */
+#define BIFINPDLG_LBL_OK 1
+#define BIFINPDLG_LBL_CANCEL 2
+#define BIFINPDLG_LBL_YES 3
+#define BIFINPDLG_LBL_NO 4
+
+/*
+ * inputdialog
+ */
+void bifinpdlg(bifcxdef *ctx, int argc)
+{
+ uchar *p;
+ char prompt[256];
+ char lblbuf[256];
+ char *labels[10];
+ char *dst;
+ char *xp;
+ uint len;
+ size_t bcnt;
+ int default_resp, cancel_resp;
+ int resp;
+ int std_btns;
+ int icon_id;
+
+ /* check for proper arguments */
+ bifcntargs(ctx, 5, argc);
+
+ /* get the icon number */
+ icon_id = runpopnum(ctx->bifcxrun);
+
+ /* get the prompt string */
+ p = runpopstr(ctx->bifcxrun);
+ bifcstr(ctx, prompt, (size_t)sizeof(prompt), p);
+
+ /* translate from internal to local characters */
+ for (xp = prompt ; *xp != '\0' ; xp++)
+ *xp = (char)cmap_i2n(*xp);
+
+ /* check for a standard button set selection */
+ if (runtostyp(ctx->bifcxrun) == DAT_NUMBER)
+ {
+ /* get the standard button set ID */
+ std_btns = runpopnum(ctx->bifcxrun);
+
+ /* there are no actual buttons */
+ bcnt = 0;
+ }
+ else
+ {
+ /* we're not using standard buttons */
+ std_btns = 0;
+
+ /* get the response string list */
+ p = runpoplst(ctx->bifcxrun);
+ len = osrp2(p);
+ p += 2;
+
+ /* build our internal button list */
+ for (bcnt = 0, dst = lblbuf ; len != 0 ; lstadv(&p, &len))
+ {
+ /* see what we have */
+ if (*p == DAT_SSTRING)
+ {
+ /* it's a label string - convert to a C string */
+ bifcstr(ctx, dst, sizeof(lblbuf) - (dst - lblbuf), p + 1);
+
+ /* translate from internal to local characters */
+ for (xp = dst ; *xp != '\0' ; xp++)
+ *xp = (char)cmap_i2n(*xp);
+
+ /* set this button to point to the converted text */
+ labels[bcnt++] = dst;
+
+ /* move past this label in the button buffer */
+ dst += strlen(dst) + 1;
+ }
+ else if (*p == DAT_NUMBER)
+ {
+ int id;
+ int resid;
+
+ /* it's a standard system label ID - get the ID */
+ id = (int)osrp4s(p + 1);
+
+ /* translate it to the appropriate string resource */
+ switch(id)
+ {
+ case BIFINPDLG_LBL_OK:
+ resid = RESID_BTN_OK;
+ break;
+
+ case BIFINPDLG_LBL_CANCEL:
+ resid = RESID_BTN_CANCEL;
+ break;
+
+ case BIFINPDLG_LBL_YES:
+ resid = RESID_BTN_YES;
+ break;
+
+ case BIFINPDLG_LBL_NO:
+ resid = RESID_BTN_NO;
+ break;
+
+ default:
+ resid = 0;
+ break;
+ }
+
+ /*
+ * if we got a valid resource ID, load the resource;
+ * otherwise, skip this button
+ */
+ if (resid != 0
+ && !os_get_str_rsc(resid, dst,
+ sizeof(lblbuf) - (dst - lblbuf)))
+ {
+ /* set this button to point to the converted text */
+ labels[bcnt++] = dst;
+
+ /* move past this label in the button buffer */
+ dst += strlen(dst) + 1;
+ }
+ }
+
+ /* if we have exhausted our label array, stop now */
+ if (bcnt >= sizeof(labels)/sizeof(labels[0])
+ || dst >= lblbuf + sizeof(lblbuf))
+ break;
+ }
+ }
+
+ /* get the default response */
+ if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+ {
+ rundisc(ctx->bifcxrun);
+ default_resp = 0;
+ }
+ else
+ default_resp = runpopnum(ctx->bifcxrun);
+
+ /* get the cancel response */
+ if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+ {
+ rundisc(ctx->bifcxrun);
+ cancel_resp = 0;
+ }
+ else
+ cancel_resp = runpopnum(ctx->bifcxrun);
+
+ /* flush output before showing the dialog */
+ tioflushn(ctx->bifcxtio, 0);
+
+ /* show the dialog */
+ resp = tio_input_dialog(icon_id, prompt, std_btns,
+ (const char **)labels, bcnt,
+ default_resp, cancel_resp);
+
+ /* return the result */
+ runpnum(ctx->bifcxrun, resp);
+}
+
+/*
+ * Determine if a resource exists
+ */
+void bifresexists(bifcxdef *ctx, int argc)
+{
+ uchar *p;
+ char resname[OSFNMAX];
+ appctxdef *appctx;
+ int found;
+ runsdef val;
+
+ /* check for proper arguments */
+ bifcntargs(ctx, 1, argc);
+
+ /* get the resource name string */
+ p = runpopstr(ctx->bifcxrun);
+ bifcstr(ctx, resname, (size_t)sizeof(resname), p);
+
+ /*
+ * if we have a host application context, and it provides a resource
+ * finder function, ask the resource finder if the resource is
+ * available; otherwise, report that the resource is not loadable,
+ * since we must not be running a version of the interpreter that
+ * supports external resource loading
+ */
+ appctx = ctx->bifcxappctx;
+ found = (appctx != 0
+ && appctx->resfile_exists != 0
+ && (*appctx->resfile_exists)(appctx->resfile_exists_ctx,
+ resname, strlen(resname)));
+
+ /* push the result */
+ runpush(ctx->bifcxrun, runclog(found), &val);
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/debug.cpp b/engines/glk/tads/tads2/debug.cpp
new file mode 100644
index 0000000000..94e0b3793f
--- /dev/null
+++ b/engines/glk/tads/tads2/debug.cpp
@@ -0,0 +1,522 @@
+/* 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/debug.h"
+#include "glk/tads/tads2/list.h"
+#include "glk/tads/tads2/run.h"
+#include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/os_glk.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+/* add a string to the history buffer */
+void dbgaddhist(dbgcxdef *ctx, char *buf, int l)
+{
+ char *p;
+ int dell;
+
+ if (ctx->dbgcxhstf + l + 1 >= ctx->dbgcxhstl)
+ {
+ /* delete first lines until we have enough space */
+ for (dell = 0, p = ctx->dbgcxhstp ; *p || dell < l ; ++p, ++dell) ;
+ if (*p) ++p;
+ memmove(ctx->dbgcxhstp, ctx->dbgcxhstp + dell,
+ (size_t)(ctx->dbgcxhstf - dell));
+ ctx->dbgcxhstf -= dell;
+ }
+ memcpy(ctx->dbgcxhstp + ctx->dbgcxhstf, buf, (size_t)l);
+ ctx->dbgcxhstf += l;
+}
+
+/* callback for dbgent - saves history line to a char buffer */
+static void dbgent1(void *ctx0, const char *str, int strl)
+{
+ char **ctx = (char **)ctx0;
+ memcpy(*ctx, str, (size_t)strl);
+ *ctx += strl;
+}
+
+void dbgent(dbgcxdef *ctx, runsdef *bp, objnum self, objnum target,
+ prpnum prop, int binum, int argc)
+{
+ dbgfdef *p;
+
+ ++(ctx->dbgcxdep); /* increment actual depth */
+ if (ctx->dbgcxfcn == DBGMAXFRAME)
+ {
+ --(ctx->dbgcxfcn); /* back to top frame */
+ memmove(ctx->dbgcxfrm, ctx->dbgcxfrm + 1,
+ (size_t)((DBGMAXFRAME - 1) * sizeof(dbgfdef)));
+ }
+ p = &ctx->dbgcxfrm[ctx->dbgcxfcn];
+ ++(ctx->dbgcxfcn); /* increment frame pointer */
+
+ p->dbgfbp = bp;
+ p->dbgfself = self;
+ p->dbgftarg = target;
+ p->dbgfprop = prop;
+ p->dbgfbif = binum;
+ p->dbgfargc = argc;
+ p->dbgffr = 0; /* no frame has yet been recorded */
+ p->dbgflin = 0;
+
+ /* save call history */
+ if (ctx->dbgcxflg & DBGCXFTRC)
+ {
+ char buf[128];
+ char *tmp;
+ int l;
+
+ tmp = buf;
+ dbgstktr(ctx, dbgent1, &tmp, -1, TRUE, FALSE);
+ if ((l = (tmp - buf)) > 0 && buf[l-1] == '\n') --l;
+ buf[l++] = '\0';
+ dbgaddhist(ctx, buf, l);
+ }
+}
+
+void dbglv(dbgcxdef *ctx, int exittype)
+{
+ --(ctx->dbgcxdep); /* decrement actual depth */
+ if (ctx->dbgcxfcn) --(ctx->dbgcxfcn); /* decrement frame pointer */
+
+ /*
+ * if we're in STEP OUT/OVER mode, and the target context is level
+ * 0, and we're now at level 0, it means that we are stepping out of
+ * a routine called directly by the system and the debugger is
+ * supposed to break when that happens -- return to single-stepping
+ * mode so that we break into the debugger the next time the system
+ * calls a method
+ */
+ if ((ctx->dbgcxflg & DBGCXFSS) != 0
+ && (ctx->dbgcxflg & DBGCXFSO) != 0
+ && ctx->dbgcxsof == 0 && ctx->dbgcxdep == 0)
+ {
+ /*
+ * stepping out/over at level 0 - go to normal single-step mode
+ * (clear the out/over flag)
+ */
+ ctx->dbgcxflg &= ~DBGCXFSO;
+ }
+
+ /* record exit in call history if appropriate */
+ if (ctx->dbgcxflg & DBGCXFTRC)
+ {
+ char buf[128];
+ char *p;
+
+ switch(exittype)
+ {
+ case DBGEXVAL:
+ if (ctx->dbgcxfcn > 1)
+ {
+ memset(buf, ' ', (size_t)(ctx->dbgcxfcn - 1));
+ dbgaddhist(ctx, buf, (int)ctx->dbgcxfcn - 1);
+ }
+ memcpy(buf, " => ", (size_t)4);
+ p = buf + 4;
+ dbgpval(ctx, ctx->dbgcxrun->runcxsp - 1,
+ dbgent1, &p, TRUE);
+ *p++ = '\0';
+ dbgaddhist(ctx, buf, (int)(p - buf));
+ break;
+
+ case DBGEXPASS:
+ memcpy(buf, " [pass]", (size_t)8);
+ dbgaddhist(ctx, buf, 8);
+ break;
+ }
+ }
+}
+
+/* get a symbol name; returns length of name */
+int dbgnam(dbgcxdef *ctx, char *outbuf, int typ, int val)
+{
+ toksdef sym;
+
+ if (!ctx->dbgcxtab || !ctx->dbgcxtab->tokthhsh)
+ {
+ memcpy(outbuf, "<NO SYMBOL TABLE>", (size_t)17);
+ return(17);
+ }
+
+ if (tokthfind((toktdef *)ctx->dbgcxtab, typ, val, &sym))
+ {
+ memcpy(outbuf, sym.toksnam, (size_t)sym.tokslen);
+ return(sym.tokslen);
+ }
+ else if (typ == TOKSTOBJ)
+ {
+ if ((mcmon)val == MCMONINV)
+ {
+ memcpy(outbuf, "<invalid object>", 16);
+ return 16;
+ }
+ else
+ {
+ sprintf(outbuf, "<object#%u>", val);
+ return strlen(outbuf);
+ }
+ }
+ else
+ {
+ memcpy(outbuf, "<UNKNOWN>", (size_t)9);
+ return(9);
+ }
+}
+
+/* send a buffer value (as from a list) to ui callback for display */
+static void dbgpbval(dbgcxdef *ctx, dattyp typ, uchar *val,
+ void (*dispfn)(void *, const char *, int),
+ void *dispctx)
+{
+ char buf[TOKNAMMAX + 1];
+ char *p = buf;
+ uint len;
+
+ switch(typ)
+ {
+ case DAT_NUMBER:
+ sprintf(buf, "%ld", (long)osrp4s(val));
+ len = strlen(buf);
+ break;
+
+ case DAT_OBJECT:
+ len = dbgnam(ctx, buf, TOKSTOBJ, osrp2(val));
+ break;
+
+ case DAT_SSTRING:
+ len = osrp2(val) - 2;
+ p = (char *)val + 2;
+ break;
+
+ case DAT_NIL:
+ p = "nil";
+ len = 3;
+ break;
+
+ case DAT_LIST:
+ (*dispfn)(dispctx, "[", 1);
+ len = osrp2(val) - 2;
+ p = (char *)val + 2;
+ while (len)
+ {
+ dbgpbval(ctx, (dattyp)*p, (uchar *)(p + 1), dispfn, dispctx);
+ lstadv((uchar **)&p, &len);
+ if (len) (*dispfn)(dispctx, " ", 1);
+ }
+ (*dispfn)(dispctx, "]", 1);
+ len = 0;
+ break;
+
+ case DAT_TRUE:
+ p = "true";
+ len = 4;
+ break;
+
+ case DAT_FNADDR:
+ (*dispfn)(dispctx, "&", 1);
+ len = dbgnam(ctx, buf, TOKSTFUNC, osrp2(val));
+ break;
+
+ case DAT_PROPNUM:
+ (*dispfn)(dispctx, "&", 1);
+ len = dbgnam(ctx, buf, TOKSTPROP, osrp2(val));
+ break;
+
+ default:
+ p = "[unknown type]";
+ len = 14;
+ break;
+ }
+
+ if (typ == DAT_SSTRING) (*dispfn)(dispctx, "'", 1);
+ if (len) (*dispfn)(dispctx, p, len);
+ if (typ == DAT_SSTRING) (*dispfn)(dispctx, "'", 1);
+}
+
+/* send a value to ui callback for display */
+void dbgpval(dbgcxdef *ctx, runsdef *val,
+ void (*dispfn)(void *ctx, const char *str, int strl),
+ void *dispctx,
+ int showtype)
+{
+ uchar buf[TOKNAMMAX + 1];
+ uint len;
+ uchar *p = buf;
+ char *typ = 0;
+
+ switch(val->runstyp)
+ {
+ case DAT_NUMBER:
+ sprintf((char *)buf, "%ld", val->runsv.runsvnum);
+ len = strlen((char *)buf);
+ typ = "number";
+ break;
+
+ case DAT_OBJECT:
+ len = dbgnam(ctx, (char *)buf, TOKSTOBJ, val->runsv.runsvobj);
+ typ = "object";
+ break;
+
+ case DAT_SSTRING:
+ len = osrp2(val->runsv.runsvstr) - 2;
+ p = val->runsv.runsvstr + 2;
+ typ = "string";
+ break;
+
+ case DAT_NIL:
+ p = (uchar *)"nil";
+ len = 3;
+ break;
+
+ case DAT_LIST:
+ if (showtype) (*dispfn)(dispctx, "list: ", 6);
+ (*dispfn)(dispctx, "[", 1);
+ len = osrp2(val->runsv.runsvstr) - 2;
+ p = val->runsv.runsvstr + 2;
+ while (len)
+ {
+ dbgpbval(ctx, (dattyp)*p, (uchar *)(p + 1), dispfn, dispctx);
+ lstadv(&p, &len);
+ if (len) (*dispfn)(dispctx, " ", 1);
+ }
+ (*dispfn)(dispctx, "]", 1);
+ len = 0;
+ break;
+
+ case DAT_TRUE:
+ p = (uchar *)"true";
+ len = 4;
+ break;
+
+ case DAT_FNADDR:
+ len = dbgnam(ctx, (char *)buf, TOKSTFUNC, val->runsv.runsvobj);
+ typ = "function pointer";
+ break;
+
+ case DAT_PROPNUM:
+ len = dbgnam(ctx, (char *)buf, TOKSTPROP, val->runsv.runsvprp);
+ typ = "property pointer";
+ break;
+
+ default:
+ p = (uchar *)"[unknown type]";
+ len = 14;
+ break;
+ }
+
+ /* show the type prefix if desired, or add a quote if it's a string */
+ if (typ && showtype)
+ {
+ /* show the type prefix */
+ (*dispfn)(dispctx, typ, (int)strlen(typ));
+ (*dispfn)(dispctx, ": ", 2);
+ }
+ else if (val->runstyp == DAT_SSTRING)
+ {
+ /* it's a string, and we're not showing a type - add a quote */
+ (*dispfn)(dispctx, "'", 1);
+ }
+
+ /*
+ * if possible, null-terminate the buffer - do this only if the
+ * length is actually within the buffer, which won't be the case if
+ * the text comes from someplace outside the buffer (which is the
+ * case if it's a string, for example)
+ */
+ if (len < sizeof(buf))
+ buf[len] = '\0';
+
+ /* display a "&" prefix if it's an address of some kind */
+ if (val->runstyp == DAT_PROPNUM || val->runstyp == DAT_FNADDR)
+ (*dispfn)(dispctx, "&", 1);
+
+ /* display the text */
+ if (len != 0)
+ (*dispfn)(dispctx, (char *)p, len);
+
+ /* add a closing quote if it's a string and we showed an open quote */
+ if (val->runstyp == DAT_SSTRING && !(typ && showtype))
+ (*dispfn)(dispctx, "'", 1);
+}
+
+void dbgstktr(dbgcxdef *ctx,
+ void (*dispfn)(void *ctx, const char *str, int strl),
+ void *dispctx,
+ int level, int toponly, int include_markers)
+{
+ dbgfdef *f;
+ int i;
+ int j;
+ int k;
+ char buf[128];
+ char *p;
+ char c;
+
+ for (i = ctx->dbgcxfcn, j = ctx->dbgcxdep, f = &ctx->dbgcxfrm[i-1]
+ ; i ; --f, --j, --i)
+ {
+ p = buf;
+ if (toponly)
+ {
+ int v = (i < 50 ? i : 50);
+
+ if (v > 1)
+ {
+ memset(buf, ' ', (size_t)(v - 1));
+ dbgaddhist(ctx, buf, v-1);
+ }
+ }
+ else if (include_markers)
+ {
+ c = (i == level + 1 ? '*' : ' ');
+ sprintf(buf, "%3d%c ", j, c);
+ p += 4;
+ }
+
+ if (f->dbgftarg == MCMONINV)
+ p += dbgnam(ctx, p, TOKSTBIFN, f->dbgfbif);
+ else
+ p += dbgnam(ctx, p,
+ (f->dbgfself == MCMONINV ? TOKSTFUNC : TOKSTOBJ),
+ (int)f->dbgftarg);
+
+ if (f->dbgfself != MCMONINV && f->dbgfself != f->dbgftarg)
+ {
+ memcpy(p, "<self=", (size_t)6);
+ p += 6;
+ p += dbgnam(ctx, p, TOKSTOBJ, (int)f->dbgfself);
+ *p++ = '>';
+ }
+ if (f->dbgfprop)
+ {
+ *p++ = '.';
+ p += dbgnam(ctx, p, TOKSTPROP, (int)f->dbgfprop);
+ }
+
+ /* display what we have so far */
+ *p++ = '\0';
+ (*dispfn)(dispctx, buf, (int)strlen(buf));
+
+ /* display arguments if there are any */
+ if (f->dbgfself == MCMONINV || f->dbgfargc != 0)
+ {
+ (*dispfn)(dispctx, "(", 1);
+ for (k = 0 ; k < f->dbgfargc ; ++k)
+ {
+ dbgpval(ctx, f->dbgfbp - k - 2, dispfn, dispctx, FALSE);
+ if (k + 1 < f->dbgfargc) (*dispfn)(dispctx, ", ", 2);
+ }
+ (*dispfn)(dispctx, ")", 1);
+ }
+
+ /* send out a newline, then move on to next frame */
+ (*dispfn)(dispctx, "\n", 1);
+
+ /* we're done if doing one function only */
+ if (toponly) break;
+ }
+}
+
+static void dbgdsdisp(void *ctx, const char *buf, int bufl)
+{
+ if (buf[0] == '\n')
+ tioflush((tiocxdef *)ctx);
+ else
+ tioputslen((tiocxdef *)ctx, (char *)buf, bufl);
+}
+
+/* dump the stack */
+void dbgds(dbgcxdef *ctx)
+{
+ /* don't do stack dumps if we're running from the debugger command line */
+ if (ctx->dbgcxflg & DBGCXFIND) return;
+
+ tioflush(ctx->dbgcxtio);
+ tioshow(ctx->dbgcxtio);
+ dbgstktr(ctx, dbgdsdisp, ctx->dbgcxtio, -1, FALSE, TRUE);
+ tioflush(ctx->dbgcxtio);
+ ctx->dbgcxfcn = ctx->dbgcxdep = 0;
+}
+
+/* get information about the currently executing source line */
+void dbglget(dbgcxdef *ctx, uchar *buf)
+{
+ dbglgetlvl(ctx, buf, 0);
+}
+
+/*
+ * Get information about a source line at a particular stack level into
+ * the buffer; leaves out frame info. level 0 is the currently
+ * executing line, 1 is the first enclosing level, and so on.
+ */
+int dbglgetlvl(dbgcxdef *ctx, uchar *buf, int level)
+{
+ uchar *linrec;
+ uchar *obj;
+ dbgfdef *fr;
+
+ /* make sure the level is valid */
+ if (level > ctx->dbgcxfcn - 1)
+ return 1;
+
+ /* get the frame at the given level */
+ fr = &ctx->dbgcxfrm[ctx->dbgcxfcn - level - 1];
+
+ /* if we're in an intrinsic, go to enclosing frame */
+ if (fr->dbgftarg == MCMONINV) --fr;
+
+ /* make sure we've encountered an OPCLINE in this frame */
+ if (fr->dbgflin == 0)
+ return 1;
+
+ /* we need to read from the target object - lock it */
+ obj = mcmlck(ctx->dbgcxmem, (mcmon)fr->dbgftarg);
+
+ linrec = obj + fr->dbgflin;
+ memcpy(buf, linrec + 3, (size_t)(*linrec - 3));
+
+ /* no longer need the target object locked */
+ mcmunlck(ctx->dbgcxmem, (mcmon)fr->dbgftarg);
+
+ /* success */
+ return 0;
+}
+
+/* tell the line source the location of the current line being compiled */
+void dbgclin(tokcxdef *tokctx, objnum objn, uint ofs)
+{
+ uchar buf[4];
+
+ /* package the information and send it to the line source */
+ oswp2(buf, objn);
+ oswp2(buf + 2, ofs);
+ lincmpinf(tokctx->tokcxlin, buf);
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/error.cpp b/engines/glk/tads/tads2/error.cpp
index 793ddccb1d..eeb59cda81 100644
--- a/engines/glk/tads/tads2/error.cpp
+++ b/engines/glk/tads/tads2/error.cpp
@@ -21,11 +21,185 @@
*/
#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/error_handling.h"
+#include "glk/tads/tads2/tokenizer.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
+/* format an error message, sprintf-style, using an erradef array */
+int errfmt(char *outbuf, int outbufl, const char *fmt, int argc, const erradef *argv) {
+ int outlen = 0;
+ int argi = 0;
+ int len = 0;
+ char buf[20];
+ const char *p = nullptr;
+ char fmtchar;
+
+ while (*fmt)
+ {
+ switch(*fmt)
+ {
+ case '\\':
+ ++fmt;
+ len = 1;
+ switch(*fmt)
+ {
+ case '\0':
+ --fmt;
+ break;
+ case '\n':
+ p = "\n";
+ break;
+ case '\t':
+ p = "\t";
+ break;
+ default:
+ p = fmt;
+ break;
+ }
+ break;
+
+ case '%':
+ ++fmt;
+ fmtchar = *fmt;
+ if (argi >= argc) fmtchar = 1; /* too many - ignore it */
+ switch(fmtchar)
+ {
+ case '\0':
+ --fmt;
+ break;
+
+ case '%':
+ p = "%";
+ len = 1;
+ break;
+
+ case 'd':
+ sprintf(buf, "%d", argv[argi].erraint);
+ len = strlen(buf);
+ p = buf;
+ break;
+
+ case 's':
+ p = argv[argi].errastr;
+ len = strlen(p);
+ break;
+
+ case 't':
+ {
+ int i;
+ static struct
+ {
+ int tokid;
+ char *toknam;
+ } toklist[] =
+ {
+ { TOKTSEM, "semicolon" },
+ { TOKTCOLON, "colon" },
+ { TOKTFUNCTION, "\"function\"" },
+ { TOKTCOMMA, "comma" },
+ { TOKTLBRACE, "left brace ('{')" },
+ { TOKTRPAR, "right paren (')')" },
+ { TOKTRBRACK, "right square bracket (']')" },
+ { TOKTWHILE, "\"while\"" },
+ { TOKTLPAR, "left paren ('(')" },
+ { TOKTEQ, "'='" },
+ { 0, (char *)0 }
+ };
+
+ for (i = 0 ; toklist[i].toknam ; ++i)
+ {
+ if (toklist[i].tokid == argv[argi].erraint)
+ {
+ p = toklist[i].toknam;
+ break;
+ }
+ }
+ if (!toklist[i].toknam)
+ p = "<unknown token>";
+ len = strlen(p);
+ break;
+ }
+
+ default:
+ p = "";
+ len = 0;
+ --argi;
+ break;
+ }
+ ++argi;
+ break;
+
+ default:
+ p = fmt;
+ len = 1;
+ break;
+ }
+
+ /* copy output that was set up above */
+ if (len)
+ {
+ if (outbufl >= len)
+ {
+ memcpy(outbuf, p, (size_t)len);
+ outbufl -= len;
+ outbuf += len;
+ }
+ else if (outbufl)
+ {
+ memcpy(outbuf, p, (size_t)outbufl);
+ outbufl = 0;
+ }
+ outlen += len;
+ }
+ ++fmt;
+ }
+
+ if (outbufl) *outbuf++ = '\0';
+ return(outlen);
+}
+
+#ifdef DEBUG
+void errjmp(buf, e)
+jmp_buf buf;
+int e;
+{
+ longjmp(buf, e);
+}
+#endif /* DEBUG */
+
+
+
+#ifdef ERR_NO_MACRO
+
+/* base error signal function */
+void errsign(errcxdef *ctx, int e) {
+ ctx->errcxofs = 0;
+#if 0
+ longjmp(ctx->errcxptr->errbuf, e);
+#else
+ error("errsign");
+#endif
+}
+
+/* enter a string argument */
+char *errstr(errcxdef *ctx, char *str, int len) {
+ char *ret = &ctx->errcxbuf[ctx->errcxofs];
+
+ memcpy(ret, str, (size_t)len);
+ ret[len] = '\0';
+ ctx->errcxofs += len + 1;
+ return(ret);
+}
+
+/* log an error: base function */
+void errlogn(errcxdef *ctx, int err) {
+ error("errlogn");
+}
+
+#endif /* ERR_NO_MACRO */
} // End of namespace TADS2
} // End of namespace TADS
diff --git a/engines/glk/tads/tads2/error_handling.cpp b/engines/glk/tads/tads2/error_handling.cpp
index a2300ee834..b71fc8ce11 100644
--- a/engines/glk/tads/tads2/error_handling.cpp
+++ b/engines/glk/tads/tads2/error_handling.cpp
@@ -20,13 +20,190 @@
*
*/
-#include "error_handling.h"
+#include "glk/tads/tads2/error_handling.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
+/* format an error message, sprintf-style, using an erradef array */
+int errfmt(char *outbuf, int outbufl, char *fmt, int argc, erradef *argv)
+{
+ int outlen = 0;
+ int argi = 0;
+ int len;
+ char buf[20];
+ char *p = nullptr;
+ char fmtchar;
+
+ while (*fmt != '\0' && outbufl > 1)
+ {
+ switch(*fmt)
+ {
+ case '\\':
+ ++fmt;
+ len = 1;
+ switch(*fmt)
+ {
+ case '\0':
+ --fmt;
+ break;
+ case '\n':
+ p = "\n";
+ break;
+ case '\t':
+ p = "\t";
+ break;
+ default:
+ p = fmt;
+ break;
+ }
+ break;
+
+ case '%':
+ ++fmt;
+ fmtchar = *fmt;
+ if (argi >= argc) fmtchar = 1; /* too many - ignore it */
+ switch(fmtchar)
+ {
+ case '\0':
+ --fmt;
+ len = 0;
+ break;
+
+ case '%':
+ p = "%";
+ len = 1;
+ break;
+
+ case 'd':
+ sprintf(buf, "%d", argv[argi].erraint);
+ len = strlen(buf);
+ p = buf;
+ break;
+
+ case 'u':
+ sprintf(buf, "%u", argv[argi].erraint);
+ len = strlen(buf);
+ p = buf;
+ break;
+
+ case 's':
+ p = argv[argi].errastr;
+ len = strlen(p);
+ break;
+
+ default:
+ p = "";
+ len = 0;
+ --argi;
+ break;
+ }
+ ++argi;
+ break;
+
+ default:
+ p = fmt;
+ len = 1;
+ break;
+ }
+
+ /* copy output that was set up above */
+ if (len != 0)
+ {
+ if (outbufl >= len)
+ {
+ memcpy(outbuf, p, (size_t)len);
+ outbufl -= len;
+ outbuf += len;
+ }
+ else if (outbufl > 1)
+ {
+ memcpy(outbuf, p, (size_t)outbufl - 1);
+ outbufl = 1;
+ }
+ outlen += len;
+ }
+ ++fmt;
+ }
+
+ /* add a null terminator */
+ if (outbufl != 0)
+ *outbuf++ = '\0';
+
+ /* return the length */
+ return outlen;
+}
+
+#if defined(DEBUG) && !defined(ERR_NO_MACRO)
+void errjmp(jmp_buf buf, int e)
+{
+ longjmp(buf, e);
+}
+#endif /* DEBUG */
+
+
+
+#ifdef ERR_NO_MACRO
+
+/* base error signal function */
+void errsign(errcxdef *ctx, int e, char *facility)
+{
+ strncpy(ctx->errcxptr->errfac, facility, ERRFACMAX);
+ ctx->errcxptr->errfac[ERRFACMAX] = '\0';
+ ctx->errcxofs = 0;
+#if 0
+ longjmp(ctx->errcxptr->errbuf, e);
+#else
+ error("Error - %s", facility);
+#endif
+}
+
+/* signal an error with no arguments */
+void errsigf(errcxdef *ctx, char *facility, int e)
+{
+ errargc(ctx, 0);
+ errsign(ctx, e, facility);
+}
+
+/* enter a string argument */
+char *errstr(errcxdef *ctx, const char *str, int len)
+{
+ char *ret = &ctx->errcxbuf[ctx->errcxofs];
+
+ memcpy(ret, str, (size_t)len);
+ ret[len] = '\0';
+ ctx->errcxofs += len + 1;
+ return(ret);
+}
+
+/* resignal current error */
+void errrse1(errcxdef *ctx, errdef *fr)
+{
+ errargc(ctx, fr->erraac);
+ memcpy(ctx->errcxptr->erraav, fr->erraav,
+ (size_t)(fr->erraac * sizeof(erradef)));
+ errsign(ctx, fr->errcode, fr->errfac);
+}
+
+/* log an error: base function */
+void errlogn(errcxdef *ctx, int err, char *facility)
+{
+ ctx->errcxofs = 0;
+ (*ctx->errcxlog)(ctx->errcxlgc, facility, err, ctx->errcxptr->erraac,
+ ctx->errcxptr->erraav);
+}
+
+/* log an error with no arguments */
+void errlogf(errcxdef *ctx, char *facility, int err)
+{
+ errargc(ctx, 0);
+ errlogn(ctx, err, facility);
+}
+
+#endif /* ERR_NO_MACRO */
+
} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/os.cpp b/engines/glk/tads/tads2/os.cpp
index e51a4d938e..219bd3ba6c 100644
--- a/engines/glk/tads/tads2/os.cpp
+++ b/engines/glk/tads/tads2/os.cpp
@@ -21,333 +21,16 @@
*/
#include "glk/tads/tads2/os.h"
+#include "common/system.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
-#if 0
-OS::OS(OSystem *syst, const GlkGameDescription &gameDesc) : TADS(syst, gameDesc),
- status_mode(0) {
- Common::fill(&status_left[0], &status_left[OSS_STATUS_STRING_LEN], '\0');
- Common::fill(&status_right[0], &status_right[OSS_STATUS_STRING_LEN], '\0');
+long os_get_sys_clock_ms() {
+ return g_system->getMillis();
}
-void OS::os_terminate(int rc) {
- glk_exit();
-}
-
-uint OS::oss_convert_prompt_type(int type) {
- if (type == OS_AFP_OPEN)
- return filemode_Read;
- return filemode_ReadWrite;
-}
-
-uint OS::oss_convert_file_type(int type) {
- if (type == OSFTSAVE)
- return fileusage_SavedGame;
- if (type == OSFTLOG || type == OSFTTEXT)
- return fileusage_Transcript;
- return fileusage_Data;
-}
-
-uint OS::oss_convert_fileref_to_string(frefid_t file_to_convert, char *buffer, int buf_len) {
-#ifdef TODO
- char temp_string[32];
- uint value, i = 0, digit,
- digit_flag = false, // Have we put a digit in the string yet?
- divisor = 1e9; // The max 32-bit integer is 4294967295
-
- // This could probably be done by using sprintf("%s%ld%s") but I don't want to risk it
- value = (uint)file_to_convert;
- while (divisor != 1) {
- digit = (char)(value / divisor);
- if (digit != 0 || digit_flag) { // This lets us handle, eg, 102
- temp_string[i++] = digit + '0';
- digit_flag = true;
- }
- value = value % divisor;
- divisor /= 10;
- }
- temp_string[i++] = (char)value + '0';
- temp_string[i] = 0;
- if (strlen(temp_string) + strlen(OSS_FILEREF_STRING_PREFIX) +
- strlen(OSS_FILEREF_STRING_SUFFIX) > (size_t)buf_len)
- return false;
- sprintf(buffer, "%s%s%s", OSS_FILEREF_STRING_PREFIX,
- temp_string, OSS_FILEREF_STRING_SUFFIX);
-#endif
- return true;
-}
-
-frefid_t OS::oss_convert_string_to_fileref(char *buffer, uint usage) {
-#ifdef TODO
- char temp_string[32];
- uint value = 0, i, multiplier = 1;
-
- // Does the buffer contain a hashed fileref?
- if (oss_is_string_a_fileref(buffer)) {
- // If so, we need only decode the string in the middle and return its value
- strcpy(temp_string, buffer + strlen(OSS_FILEREF_STRING_PREFIX));
- i = strlen(temp_string) - strlen(OSS_FILEREF_STRING_SUFFIX);
- temp_string[i] = 0;
- while (i != 0) {
- i--;
- value += ((uint)(temp_string[i] - '0') * multiplier);
- multiplier *= 10;
- }
- return ((frefid_t)value);
- }
-
- // If not, return the new fileref
- return (glk_fileref_create_by_name(usage, os_get_root_name(buffer), 0));
-#else
- return nullptr;
-#endif
-}
-
-bool OS::oss_is_string_a_fileref(char *buffer) {
- if ((strncmp(buffer, OSS_FILEREF_STRING_PREFIX,
- strlen(OSS_FILEREF_STRING_PREFIX)) == 0) &&
- (strncmp(buffer + strlen(buffer) - strlen(OSS_FILEREF_STRING_SUFFIX),
- OSS_FILEREF_STRING_SUFFIX,
- strlen(OSS_FILEREF_STRING_SUFFIX)) == 0))
- return true;
- return false;
-}
-
-unsigned char OS::oss_convert_keystroke_to_tads(uint key) {
- // Characters 0 - 255 we return per normal */
- if (key <= 255)
- return ((unsigned char)key);
- switch (key) {
- case keycode_Up:
- return CMD_UP;
- case keycode_Down:
- return CMD_DOWN;
- case keycode_Left:
- return CMD_LEFT;
- case keycode_Right:
- return CMD_RIGHT;
- case keycode_PageUp:
- return CMD_PGUP;
- case keycode_PageDown:
- return CMD_PGDN;
- case keycode_Home:
- return CMD_HOME;
- case keycode_End:
- return CMD_END;
- case keycode_Func1:
- return CMD_F1;
- case keycode_Func2:
- return CMD_F2;
- case keycode_Func3:
- return CMD_F3;
- case keycode_Func4:
- return CMD_F4;
- case keycode_Func5:
- return CMD_F5;
- case keycode_Func6:
- return CMD_F6;
- case keycode_Func7:
- return CMD_F7;
- case keycode_Func8:
- return CMD_F8;
- case keycode_Func9:
- return CMD_F9;
- case keycode_Func10:
- return CMD_F10;
- default:
- return 0;
- }
-}
-
-bool OS::oss_check_path(char *filename) {
- return false;
-}
-
-void OS::oss_revert_path() {
- // No implementation
-}
-
-osfildef *OS::oss_open_stream(char *buffer, uint tadsusage, uint tbusage,
- uint fmode, uint rock) {
- frefid_t fileref;
- strid_t osf;
- int changed_dirs;
-
- fileref = oss_convert_string_to_fileref(buffer,
- oss_convert_file_type(tadsusage) | tbusage);
- changed_dirs = oss_check_path(buffer);
- osf = glk_stream_open_file(fileref, (FileMode)fmode, rock);
- if (changed_dirs)
- oss_revert_path();
- return *osf;
-}
-
-osfildef *OS::osfoprb(const char *fname, uint typ) {
- Common::File *f = new Common::File();
- if (f->open(fname))
- return f;
-
- f->close();
- delete f;
- return nullptr;
-}
-
-void OS::os_gen_charmap_filename(char *filename, const char *internal_id,
- const char *argv0) {
- const char *p;
- const char *rootname;
- size_t pathlen;
-
- // find the path prefix of the original executable filename
- for (p = rootname = argv0; *p != '\0'; ++p) {
- if (*p == '/' || *p == '\\' || *p == ':')
- rootname = p + 1;
- }
-
- // copy the path prefix
- pathlen = rootname - argv0;
- memcpy(filename, argv0, pathlen);
-
- // if there's no trailing backslash, add one
- if (pathlen == 0 || filename[pathlen - 1] != '\\')
- filename[pathlen++] = '\\';
-
- // add "win_", plus the character set ID, plus the extension
- strcpy(filename + pathlen, "win_");
- strcat(filename + pathlen, internal_id);
- strcat(filename + pathlen, ".tcp");
-}
-
-void OS::oss_put_string_with_hilite(winid_t win, const char *str, size_t len) {
- glk_set_window(win);
- glk_put_buffer(str, len);
-}
-
-void OS::oss_draw_status_line(void) {
- uint width, height, division;
-
- if (status_win == nullptr) return; // In case this is a CheapGlk port
-
- glk_window_get_size(status_win, &width, &height);
- if (height == 0) return; // Don't bother if status is invisible
- division = width - strlen(status_right) - 1;
- glk_set_window(status_win);
- glk_window_clear(status_win);
- oss_put_string_with_hilite(status_win, status_left, strlen(status_left));
- glk_window_move_cursor(status_win, division, 0);
- glk_put_string(status_right);
-}
-
-void OS::oss_change_status_string(char *dest, const char *src, size_t len) {
- if (len > OSS_STATUS_STRING_LEN - 1)
- len = OSS_STATUS_STRING_LEN - 1;
- memcpy(dest, src, len);
- dest[len] = '\0';
-}
-
-void OS::oss_change_status_left(const char *str, size_t len) {
- oss_change_status_string(status_left, str, len);
- oss_draw_status_line();
-}
-
-void OS::oss_change_status_right(const char *str) {
- oss_change_status_string(status_right, str, strlen(str));
- oss_draw_status_line();
-}
-
-int OS::memicmp(const char *s1, const char *s2, int len) {
- char *x1, *x2;
- int i, result;
-
- x1 = (char *)malloc(len); x2 = (char *)malloc(len);
-
- if (!x1 || !x2) {
- glk_set_window(story_win);
- glk_put_string("memicmp has run out of memory. Quitting.\n");
- os_waitc();
- glk_exit();
- }
-
- for (i = 0; i < len; i++) {
- if (Common::isUpper(s1[i]))
- x1[i] = glk_char_to_lower((unsigned char)s1[i]);
- else x1[i] = s1[i];
-
- if (Common::isUpper(s2[i]))
- x2[i] = glk_char_to_lower((unsigned char)s2[i]);
- else x2[i] = s2[i];
- }
-
- result = memcmp(x1, x2, len);
- free(x1);
- free(x2);
- return result;
-}
-
-void OS::os_flush() {
- glk_tick();
-}
-
-void OS::os_print(const char *str, size_t len) {
- int current_status_mode;
-
- // Decide what to do based on our status mode
- current_status_mode = os_get_status();
- if (current_status_mode == OSS_STATUS_MODE_STORY) {
- oss_put_string_with_hilite(story_win, str, len);
- } else if (current_status_mode == OSS_STATUS_MODE_STATUS) {
- const char *p;
- size_t rem;
-
- // The string requires some fiddling for the status window
- for (p = str, rem = len; rem != 0 && *p == '\n'; p++, --rem);
- if (rem != 0 && p[rem - 1] == '\n')
- --rem;
-
- // if that leaves anything, update the statusline
- if (rem != 0)
- oss_change_status_left(p, rem);
- }
-}
-
-void OS::os_expause() {
- os_printz("(Strike any key to exit...)");
- os_flush();
- os_waitc();
-}
-
-int OS::oss_getc_from_window(winid_t win) {
- static unsigned char buffered_char = 0;
- int i;
- event_t ev;
-
- if (buffered_char != 0) {
- i = (int)buffered_char;
- buffered_char = 0;
- return i;
- }
- glk_request_char_event(win);
- do {
- glk_select(&ev);
- if (ev.type == evtype_Arrange)
- oss_draw_status_line();
- } while (ev.type != evtype_CharInput);
- if (ev.val1 == keycode_Return)
- ev.val1 = '\n';
- else if (ev.val1 == keycode_Tab)
- ev.val1 = '\t';
- if (ev.val1 <= 255)
- return ((int)ev.val1);
-
- // We got a special character, so handle it appropriately
- buffered_char = oss_convert_keystroke_to_tads(ev.val1);
- return 0;
-}
-#endif
-
} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/string_resources.h b/engines/glk/tads/tads2/string_resources.h
new file mode 100644
index 0000000000..76f245e348
--- /dev/null
+++ b/engines/glk/tads/tads2/string_resources.h
@@ -0,0 +1,69 @@
+/* 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_STRING_RESOURCES
+#define GLK_TADS_TADS2_STRING_RESOURCES
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/*
+ * Dialog buttons. These provide the text for standard buttons in
+ * dialogs created with os_input_dialog().
+ *
+ * These labels can use "&" to indicate a shortcut letter, per the
+ * normal os_input_dialog() interface; for example, if the Yes button
+ * label is "&Yes", the button has the shortcut letter "Y".
+ *
+ * The text of these buttons may vary by system, since these should
+ * conform to local conventions where there are local conventions. In
+ * addition, of course, these strings will vary by language.
+ */
+
+/* OK and Cancel buttons */
+#define RESID_BTN_OK 1
+#define RESID_BTN_CANCEL 2
+
+/* "Yes" and "No" buttons */
+#define RESID_BTN_YES 3
+#define RESID_BTN_NO 4
+
+/*
+ * Reply strings for the yorn() built-in function. These strings are
+ * regular expressions as matched by the regex.h functions. For
+ * English, for example, the "yes" string would be "[Yy].*" and the "no"
+ * string would be "[Nn].*". For German, it might be desirable to
+ * accept both "Ja" and "Yes", so the "Yes" string might be "[JjYy].*".
+ *
+ * It's not necessary in these patterns to consider leading spaces,
+ * since the yorn() function will skip any leading spaces before
+ * performing the pattern match.
+ */
+#define RESID_YORN_YES 5
+#define RESID_YORN_NO 6
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif