4333 lines
99 KiB
C++
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
|