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

4333 lines
99 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/tads/tads2/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)))
Common::strcpy_s(yesbuf, "[Yy].*");
if (os_get_str_rsc(RESID_YORN_NO, nobuf, sizeof(nobuf)))
Common::strcpy_s(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 *)nullptr, 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 != nullptr ? 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 != nullptr ? 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 *)nullptr, 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 *)nullptr, 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 *)nullptr, 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);
Common::sprintf_s(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 const 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 (const char *)s1;
}
return nullptr;
}
/*
* 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",
nullptr
};
static const char *game_strs[] =
{
"restore",
"game",
nullptr
};
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 != nullptr ; ++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 != nullptr ; ++sp)
{
if (bif_stristr(pbuf, *sp) == nullptr)
{
/*
* 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") != nullptr)
{
/* 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)
{
const char *p = nullptr;
int len = 0;
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:
Common::sprintf_s(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 const 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 */
Common::strcpy_s(namebuf, 20, "\\n");
return TRUE;
case 9:
/* return '\t' for TAB characters */
Common::strcpy_s(namebuf, 20, "\\t");
return TRUE;
case 8:
/* return '[bksp]' for backspace characters */
Common::strcpy_s(namebuf, 20, "[bksp]");
return TRUE;
case 27:
/* return '[esc]' for the escape key */
Common::strcpy_s(namebuf, 20, "[esc]");
return TRUE;
default:
/* return '[ctrl-X]' for other control characters */
Common::strcpy_s(namebuf, 20, "[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 */
Common::strcpy_s(namebuf, 20, 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 */
Common::strcpy_s(namebuf, 20, "[alt-X]");
namebuf[5] = (char)(extc - CMD_ALT + 'a');
return TRUE;
}
/* it's not a valid key - use '[?]' as the name */
Common::strcpy_s(namebuf, 20, "[?]");
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;
const 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 != nullptr && voclistlen(v) != 0 && v->vocolfst != nullptr && v->vocollst != nullptr)
{
for (dst = buf + 2, src = (const uchar *)v->vocolfst ;
src <= (const uchar *)v->vocollst ; src += len + 1)
{
*dst++ = DAT_SSTRING;
len = strlen((const char *)src);
oswp2(dst, len + 2);
Common::strcpy_s((char *)dst + 2, sizeof(buf) - (dst + 2 - buf), (const 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 */
Common::strcpy_s(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 == nullptr)
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 != nullptr && appctx->get_io_safety_level != nullptr)
{
/*
* 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 == nullptr)
{
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 == nullptr)
runsig(ctx->bifcxrun, ERR_BADFILE);
/* put the validated file number, if the caller wants it */
if (fnump != nullptr)
*fnump = (int)fnum;
/* set the binary-mode flag, if the caller wants it */
if (bin_modep != nullptr)
*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, nullptr);
/* close the file and release the slot */
osfcls(fp);
ctx->bifcxfile[fnum].fp = nullptr;
}
void bifftell(bifcxdef *ctx, int argc)
{
osfildef *fp;
/* get the file number */
bifcntargs(ctx, 1, argc);
fp = bif_get_file(ctx, (int *)nullptr, nullptr);
/* 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 *)nullptr, nullptr);
/* 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 *)nullptr, nullptr);
/* 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 *)nullptr, &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 *)nullptr, &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, nullptr, &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, nullptr, &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, nullptr, &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 == nullptr || reg->end_ofs == nullptr)
{
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 == nullptr ? MCMONINV : voc->voccxdobj->vocolobj);
break;
case 4:
/* preposition */
obj = voc->voccxprep;
break;
case 5:
/* indirect object */
obj = (voc->voccxiobj == nullptr ? 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];
const 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 != nullptr
&& appctx->resfile_exists != nullptr
&& (*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