/* 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 .
*
*/
#include "glk/tads/tads2/run.h"
#include "glk/tads/tads2/data.h"
#include "glk/tads/tads2/error.h"
#include "glk/tads/tads2/list.h"
#include "glk/tads/tads2/os.h"
#include "glk/tads/tads2/post_compilation.h"
#include "glk/tads/tads2/vocabulary.h"
#include "glk/tads/os_glk.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
/* forward declarations */
struct bifcxdef;
/*
* Create a new object
*/
static void run_new(runcxdef *ctx, uchar *noreg *codepp,
objnum callobj, prpnum callprop)
{
objnum sc = 0;
objnum objn;
objdef *objp;
int sccnt;
vocidef *voci;
/* get the superclass (nil means no superclass) */
if (runtostyp(ctx) == DAT_NIL)
sccnt = 0;
else
{
/* get the superclass */
sc = runpopobj(ctx);
sccnt = 1;
/* make sure it's not a dynamically-allocated object */
voci = vocinh(ctx->runcxvoc, sc);
if (voci->vociflg & VOCIFNEW)
runsig(ctx, ERR_BADNEWSC);
}
/* create a new object and set its superclass */
objp = objnew(ctx->runcxmem, sccnt, 64, &objn, FALSE);
if (sccnt) oswp2(objsc(objp), sc);
/* save undo for the object creation */
vocdusave_newobj(ctx->runcxvoc, objn);
/* touch and unlock the object */
mcmtch(ctx->runcxmem, (mcmon)objn);
mcmunlck(ctx->runcxmem, (mcmon)objn);
/* add a vocabulary inheritance record for the new object */
vociadd(ctx->runcxvoc, objn, MCMONINV, sccnt, &sc, VOCIFNEW | VOCIFVOC);
/* set up its vocabulary, inheriting from the class */
if (sccnt)
supivoc1((struct supcxdef *)nullptr, ctx->runcxvoc,
vocinh(ctx->runcxvoc, objn), objn, TRUE, VOCFNEW);
/* run the constructor */
runpprop(ctx, codepp, callobj, callprop, objn, PRP_CONSTRUCT,
FALSE, 0, objn);
#ifdef NEVER
/*
* add it to its location's contents list by calling
* newobj.moveInto(newobj.location)
*/
runppr(ctx, objn, PRP_LOCATION, 0);
if (runtostyp(ctx) == DAT_OBJECT)
runppr(ctx, objn, PRP_MOVEINTO, 1);
else
rundisc(ctx);
#endif
/* return the new object */
runpobj(ctx, objn);
}
/*
* Delete an object
*/
static void run_delete(runcxdef *ctx, uchar *noreg *codepp,
objnum callobj, prpnum callprop)
{
objnum objn;
vocidef *voci;
int i;
voccxdef *vctx = ctx->runcxvoc;
/* get the object to be deleted */
objn = runpopobj(ctx);
/* make sure it was allocated with "new" */
voci = vocinh(vctx, objn);
if (voci == nullptr || !(voci->vociflg & VOCIFNEW))
runsig(ctx, ERR_BADDEL);
/* run the destructor */
runpprop(ctx, codepp, callobj, callprop, objn, PRP_DESTRUCT,
FALSE, 0, objn);
#ifdef NEVER
/* remove it from its location, if any, by using moveInto(nil) */
runpnil(ctx);
runppr(ctx, objn, PRP_MOVEINTO, 1);
#endif
/* save undo for the object deletion */
vocdusave_delobj(vctx, objn);
/* delete the object's inheritance and vocabulary records */
vocdel(vctx, objn);
vocidel(vctx, objn);
/* forget 'it' if the deleted object is 'it' (or 'them', etc) */
if (vctx->voccxit == objn) vctx->voccxit = MCMONINV;
if (vctx->voccxhim == objn) vctx->voccxhim = MCMONINV;
if (vctx->voccxher == objn) vctx->voccxher = MCMONINV;
for (i = 0; i < vctx->voccxthc; ++i)
{
if (vctx->voccxthm[i] == objn)
{
/* forget the entire 'them' list when deleting from it */
vctx->voccxthc = 0;
break;
}
}
/* forget the 'again' statistics if necessary */
if (vctx->voccxlsd.vocolobj == objn
|| vctx->voccxlsi.vocolobj == objn
|| vctx->voccxlsa == objn
|| vctx->voccxlsv == objn
|| vctx->voccxlsp == objn)
{
/* forget the verb */
vctx->voccxlsv = MCMONINV;
/*
* note in the flags why we lost the "again" verb, for better
* error reporting if the player tries to type "again"
*/
vctx->voccxflg |= VOCCXAGAINDEL;
}
/* delete the memory manager object */
mcmfre(ctx->runcxmem, (mcmon)objn);
}
/*
* invoke a function
*/
void runfn(runcxdef *ctx, noreg objnum objn, int argc)
{
uchar *fn;
int err;
NOREG((&objn))
/* get a lock on the object */
fn = mcmlck(ctx->runcxmem, objn);
/* catch any errors, so we can unlock the object */
ERRBEGIN(ctx->runcxerr)
/* execute the object */
runexe(ctx, fn, MCMONINV, objn, (prpnum)0, argc);
/* in case of error, unlock the object and resignal the error */
ERRCATCH(ctx->runcxerr, err)
mcmunlck(ctx->runcxmem, objn); /* release the lock on the object */
if (err < ERR_RUNEXIT || err > ERR_RUNEXITOBJ)
dbgdump(ctx->runcxdbg); /* dump the stack */
errrse(ctx->runcxerr);
ERREND(ctx->runcxerr)
/* we're done with the object, so unlock it */
mcmunlck(ctx->runcxmem, objn);
}
/*
* compress the heap - remove unreferenced items
*/
void runhcmp(runcxdef *ctx, uint siz, uint below,
runsdef *val1, runsdef *val2, runsdef *val3)
{
uchar *hp = ctx->runcxheap;
uchar *htop = ctx->runcxhp;
runsdef *stop = ctx->runcxsp + below;
runsdef *stk = ctx->runcxstk;
runsdef *sp;
uchar *dst = hp;
uchar *hnxt;
int ref;
/* go through heap, finding references on stack */
for (; hp < htop; hp = hnxt)
{
hnxt = hp + osrp2(hp); /* remember next heap element */
for (ref = FALSE, sp = stk; sp < stop; ++sp)
{
switch (sp->runstyp)
{
case DAT_SSTRING:
case DAT_LIST:
if (sp->runsv.runsvstr == hp) /* reference to this item? */
{
ref = TRUE; /* this heap item is referenced */
sp->runsv.runsvstr = dst; /* reflect imminent move */
}
break;
default: /* other types do not refer to the heap */
break;
}
}
/* check the explicitly referenced value pointers as well */
#define CHECK_VAL(val) \
if (val && val->runsv.runsvstr == hp) \
ref = TRUE, val->runsv.runsvstr = dst;
CHECK_VAL(val1);
CHECK_VAL(val2);
CHECK_VAL(val3);
#undef CHECK_VAL
/* if referenced, copy it to dst and advance dst */
if (ref)
{
if (hp != dst) memmove(dst, hp, (size_t)osrp2(hp));
dst += osrp2(dst);
}
}
/* set heap pointer based on shuffled heap */
ctx->runcxhp = dst;
/* check for space requested, and signal error if not available */
if ((uint)(ctx->runcxhtop - ctx->runcxhp) < siz)
runsig(ctx, ERR_HPOVF);
}
/*
* push a value onto the stack that's already been allocated in heap
*/
void runrepush(runcxdef *ctx, runsdef *val)
{
/* check for stack overflow */
runstkovf(ctx);
OSCPYSTRUCT(*(ctx->runcxsp), *val);
/* increment stack pointer */
++(ctx->runcxsp);
}
/* push a counted-length string onto the stack */
void runpstr(runcxdef *ctx, const char *str, int len, int sav)
{
runsdef val;
/* allocate space and set up new string */
runhres(ctx, len + 2, sav);
oswp2(ctx->runcxhp, len + 2);
memcpy(ctx->runcxhp + 2, str, (size_t)len);
/* push return value */
val.runsv.runsvstr = ctx->runcxhp;
val.runstyp = DAT_SSTRING;
ctx->runcxhp += len + 2;
runrepush(ctx, &val);
}
/* push a C-style string, converting escape codes */
void runpushcstr(runcxdef *ctx, const char *str, size_t len, int sav)
{
const char *p;
char *dst;
size_t need;
runsdef val;
/* determine how much space we'll need after converting escapes */
for (p = str, need = len; p < str + len; ++p)
{
switch (*p)
{
case '\\':
case '\n':
case '\r':
case '\t':
/* these characters need to be escaped */
++need;
break;
default:
break;
}
}
/* reserve space */
runhres(ctx, need + 2, sav);
/* set up the length prefix */
oswp2(ctx->runcxhp, need + 2);
/* copy the string, expanding escapes */
for (p = str, dst = (char *)ctx->runcxhp + 2; p < str + len; ++p)
{
switch (*p)
{
case '\\':
*dst++ = '\\';
*dst++ = '\\';
break;
case '\n':
case '\r':
*dst++ = '\\';
*dst++ = 'n';
break;
case '\t':
*dst++ = '\\';
*dst++ = '\t';
break;
default:
*dst++ = *p;
break;
}
}
/* push the return value */
val.runsv.runsvstr = ctx->runcxhp;
val.runstyp = DAT_SSTRING;
ctx->runcxhp += need + 2;
runrepush(ctx, &val);
}
/* push a value onto the stack */
void runpush(runcxdef *ctx, dattyp typ, runsdef *val)
{
int len;
/* check for stack overflow */
runstkovf(ctx);
OSCPYSTRUCT(*(ctx->runcxsp), *val);
ctx->runcxsp->runstyp = typ;
/* variable-length data must be copied into the heap */
if (typ == DAT_SSTRING || typ == DAT_LIST)
{
len = osrp2(val->runsv.runsvstr);
runhres(ctx, len, 0); /* reserve space in heap */
memcpy(ctx->runcxhp, val->runsv.runsvstr, (size_t)len);
ctx->runcxsp->runsv.runsvstr = ctx->runcxhp;
ctx->runcxhp += len;
}
/* increment stack pointer */
++(ctx->runcxsp);
}
/* push a number onto the stack */
void runpnum(runcxdef *ctx, long num)
{
runsdef val;
val.runsv.runsvnum = num;
runpush(ctx, DAT_NUMBER, &val);
}
/* push an object onto the stack (or nil if obj is MCMONINV) */
void runpobj(runcxdef *ctx, objnum obj)
{
runsdef val;
if (obj == MCMONINV)
runpnil(ctx);
else
{
val.runsv.runsvobj = obj;
runpush(ctx, DAT_OBJECT, &val);
}
}
/* push nil */
void runpnil(runcxdef *ctx)
{
runsdef val;
runpush(ctx, DAT_NIL, &val);
}
/* copy datatype + value from a runsdef into a buffer (such as list) */
static void runputbuf(uchar *dstp, runsdef *val)
{
*dstp++ = val->runstyp;
switch (val->runstyp)
{
case DAT_LIST:
case DAT_SSTRING:
memcpy(dstp, val->runsv.runsvstr, (size_t)osrp2(val->runsv.runsvstr));
break;
case DAT_NUMBER:
oswp4s(dstp, val->runsv.runsvnum);
break;
case DAT_PROPNUM:
oswp2(dstp, val->runsv.runsvprp);
break;
case DAT_OBJECT:
case DAT_FNADDR:
oswp2(dstp, val->runsv.runsvobj);
break;
}
}
/* push a value from a buffer (list, property, etc) onto stack */
void runpbuf(runcxdef *ctx, int typ, void *valp)
{
runsdef val;
switch (typ)
{
case DAT_NUMBER:
val.runsv.runsvnum = osrp4s(valp);
break;
case DAT_OBJECT:
case DAT_FNADDR:
val.runsv.runsvobj = osrp2(valp);
break;
case DAT_PROPNUM:
val.runsv.runsvprp = osrp2(valp);
break;
case DAT_SSTRING:
case DAT_LIST:
val.runsv.runsvstr = (uchar *)valp;
break;
case DAT_NIL:
case DAT_TRUE:
break;
}
runpush(ctx, typ, &val);
}
/* compare items at top of stack for equality; TRUE->equal, FALSE->unequal */
int runeq(runcxdef *ctx)
{
runsdef val1, val2;
/* get values, and see if they have identical type; not equal if not */
runpop(ctx, &val1);
runpop(ctx, &val2);
if (val1.runstyp != val2.runstyp) return(FALSE);
/* types match, so check values */
switch (val1.runstyp)
{
case DAT_NUMBER:
return(val1.runsv.runsvnum == val2.runsv.runsvnum);
case DAT_SSTRING:
case DAT_LIST:
return(osrp2(val1.runsv.runsvstr) == osrp2(val2.runsv.runsvstr)
&& !memcmp(val1.runsv.runsvstr, val2.runsv.runsvstr,
(size_t)osrp2(val1.runsv.runsvstr)));
case DAT_PROPNUM:
return(val1.runsv.runsvprp == val2.runsv.runsvprp);
case DAT_OBJECT:
case DAT_FNADDR:
return(val1.runsv.runsvobj == val2.runsv.runsvobj);
default:
return(TRUE);
}
}
/* compare magnitudes of numbers/strings at top of stack; strcmp-like value */
int runmcmp(runcxdef *ctx)
{
if (runtostyp(ctx) == DAT_NUMBER)
{
long num2 = runpopnum(ctx);
long num1 = runpopnum(ctx);
if (num1 > num2) return(1);
else if (num1 < num2) return(-1);
else return(0);
}
else if (runtostyp(ctx) == DAT_SSTRING)
{
uchar *str2 = runpopstr(ctx);
uchar *str1 = runpopstr(ctx);
uint len1 = osrp2(str1) - 2;
uint len2 = osrp2(str2) - 2;
str1 += 2;
str2 += 2;
while (len1 && len2)
{
if (*str1 < *str2) return(-1); /* character from 1 is greater */
else if (*str1 > *str2) return(1); /* char from 1 is less */
++str1;
++str2;
--len1;
--len2;
}
if (len1) return(1); /* match up to len2, but string 1 is longer */
else if (len2) return(-1); /* match up to len1, but str2 is longer */
else return(0); /* strings are identical */
}
else
{
runsig(ctx, ERR_INVCMP);
}
return 0;
}
/* determine size of a runsdef item */
int runsiz(runsdef *item) {
switch (item->runstyp) {
case DAT_NUMBER:
return(4);
case DAT_SSTRING:
case DAT_LIST:
return(osrp2(item->runsv.runsvstr));
case DAT_PROPNUM:
case DAT_OBJECT:
case DAT_FNADDR:
return(2);
default:
return(0);
}
}
/* find a sublist within a list */
uchar *runfind(uchar *lst, runsdef *item)
{
uint len;
uint curlen;
for (len = osrp2(lst) - 2, lst += 2; len; lst += curlen, len -= curlen)
{
if (*lst == item->runstyp)
{
switch (*lst)
{
case DAT_LIST:
case DAT_SSTRING:
if (osrp2(lst + 1) == osrp2(item->runsv.runsvstr) &&
!memcmp(lst + 1, item->runsv.runsvstr, (size_t)osrp2(lst + 1)))
return(lst);
break;
case DAT_NUMBER:
if (osrp4s(lst + 1) == item->runsv.runsvnum)
return(lst);
break;
case DAT_TRUE:
case DAT_NIL:
return(lst);
case DAT_OBJECT:
case DAT_FNADDR:
if (osrp2(lst + 1) == item->runsv.runsvobj)
return(lst);
break;
case DAT_PROPNUM:
if (osrp2(lst + 1) == item->runsv.runsvprp)
return(lst);
break;
}
}
curlen = datsiz(*lst, lst + 1) + 1;
}
return((uchar *)nullptr);
}
/* add values */
void runadd(runcxdef *ctx, runsdef *val, runsdef *val2, uint below)
{
if (val->runstyp == DAT_LIST)
{
int len1 = osrp2(val->runsv.runsvstr);
int len2 = runsiz(val2);
int newlen;
/* if concatenating a list, take out length + datatype from 2nd */
if (val2->runstyp == DAT_LIST)
newlen = len1 + len2 - 2; /* leave out second list len */
else
newlen = len1 + len2 + 1; /* add in datatype header */
/* get space in heap, copy first list, and set new length */
runhres2(ctx, newlen, below, val, val2);
memcpy(ctx->runcxhp, val->runsv.runsvstr, (size_t)len1);
oswp2(ctx->runcxhp, newlen);
/* append the new element or list of elements */
if (val2->runstyp == DAT_LIST)
memcpy(ctx->runcxhp + len1, val2->runsv.runsvstr + 2,
(size_t)(len2 - 2));
else
runputbuf(ctx->runcxhp + len1, val2);
/* set up return value and update heap pointer */
val->runsv.runsvstr = ctx->runcxhp;
ctx->runcxhp += newlen;
}
else if (val->runstyp == DAT_SSTRING && val2->runstyp == DAT_SSTRING)
{
int len1 = osrp2(val->runsv.runsvstr);
int len2 = osrp2(val2->runsv.runsvstr);
/* reserve space, and concatenate the two strings */
runhres2(ctx, len1 + len2 - 2, below, val, val2);
memcpy(ctx->runcxhp, val->runsv.runsvstr, (size_t)len1);
memcpy(ctx->runcxhp + len1, val2->runsv.runsvstr + 2,
(size_t)len2 - 2);
/* set length to sum of two lengths, minus 2nd length word */
oswp2(ctx->runcxhp, len1 + len2 - 2);
val->runsv.runsvstr = ctx->runcxhp;
ctx->runcxhp += len1 + len2 - 2;
}
else if (val->runstyp == DAT_NUMBER && val2->runstyp == DAT_NUMBER)
val->runsv.runsvnum += val2->runsv.runsvnum;
else
runsig(ctx, ERR_INVADD);
}
/* returns TRUE if value changed */
int runsub(runcxdef *ctx, runsdef *val, runsdef *val2, uint below)
{
if (val->runstyp == DAT_LIST)
{
uchar *sublist;
int subsize;
int listsize;
int part1sz;
if (val2->runstyp == DAT_LIST)
{
uchar *p1;
uchar *p2;
uint rem1;
uint rem2;
uchar *dst;
/* reserve space for another copy of first list */
listsize = runsiz(val);
runhres2(ctx, listsize, below, val, val2);
dst = ctx->runcxhp + 2;
/* get pointer to first list */
p1 = val->runsv.runsvstr;
rem1 = osrp2(p1) - 2;
p1 += 2;
/*
* loop through left list, copying elements to output if
* not in the right list
*/
for (; rem1; lstadv(&p1, &rem1))
{
int found = FALSE;
/* find current element of first list in second list */
p2 = val2->runsv.runsvstr;
rem2 = osrp2(p2) - 2;
p2 += 2;
for (; rem2; lstadv(&p2, &rem2))
{
if (*p1 == *p2)
{
int siz1 = datsiz(*p1, p1 + 1);
int siz2 = datsiz(*p2, p2 + 1);
if (siz1 == siz2 &&
(siz1 == 0 || !memcmp(p1 + 1, p2 + 1, (size_t)siz1)))
{
found = TRUE;
break;
}
}
}
/* if this element wasn't found, copy to output list */
if (!found)
{
uint siz;
*dst++ = *p1;
if ((siz = datsiz(*p1, p1 + 1)) != 0)
{
memcpy(dst, p1 + 1, siz);
dst += siz;
}
}
}
/* we've built the list; write size and we're done */
oswp2(ctx->runcxhp, dst - ctx->runcxhp);
val->runsv.runsvstr = ctx->runcxhp;
ctx->runcxhp = dst;
}
else if ((sublist = runfind(val->runsv.runsvstr, val2)) != nullptr)
{
subsize = datsiz(*sublist, sublist + 1) + 1;
listsize = runsiz(val);
part1sz = sublist - (uchar *)val->runsv.runsvstr;
runhres2(ctx, listsize - subsize, below, val, val2);
memcpy(ctx->runcxhp, val->runsv.runsvstr, (size_t)part1sz);
memcpy(ctx->runcxhp + part1sz, sublist + subsize,
(size_t)(listsize - subsize - part1sz));
oswp2(ctx->runcxhp, listsize - subsize);
val->runsv.runsvstr = ctx->runcxhp;
ctx->runcxhp += listsize - subsize;
}
else
{
return(FALSE); /* no change - value can be re-pushed */
}
}
else if (val->runstyp == DAT_NUMBER && val2->runstyp == DAT_NUMBER)
val->runsv.runsvnum -= val2->runsv.runsvnum;
else
runsig(ctx, ERR_INVSUB);
return(TRUE); /* value has changed; must be pushed anew */
}
/* return code pointer offset */
static uint runcpsav(runcxdef *ctx, uchar *noreg *cp, objnum obj, prpnum prop)
{
uint ofs;
VARUSED(prop);
/* get offset from start of object */
ofs = *cp - mcmobjptr(ctx->runcxmem, (mcmon)obj);
/* clear the pointer so the caller knows the object is unlocked */
*cp = nullptr;
/* unlock the object, and return the derived offset */
mcmunlck(ctx->runcxmem, (mcmon)obj);
return(ofs);
}
/* restore code pointer based on object.property */
uchar *runcprst(runcxdef *ctx, uint ofs, objnum obj, prpnum prop)
{
uchar *ptr;
VARUSED(prop);
/* lock object, and get pointer based on offset */
ptr = mcmlck(ctx->runcxmem, (mcmon)obj) + ofs;
return(ptr);
}
/* get offset of an element within a list */
static uint runindofs(runcxdef *ctx, uint indx, uchar *lstp)
{
uint lstsiz;
uchar *orgp = lstp;
/* verify that index is in range */
if (indx <= 0) runsig(ctx, ERR_LOWINX);
/* get list's size, and point to its data string */
lstsiz = osrp2(lstp) - 2;
lstp += 2;
/* skip the first indx-1 elements */
for (--indx; indx && lstsiz; --indx) lstadv(&lstp, &lstsiz);
/* if we ran out of list, the index is out of range */
if (!lstsiz) runsig(ctx, ERR_HIGHINX);
/* return the offset */
return((uint)(lstp - orgp));
}
/* push an indexed element of a list; index is tos, list is next on stack */
static void runpind(runcxdef *ctx, uint indx, uchar *lstp)
{
uchar *ele;
runsdef val;
/* find the element we want to push */
ele = lstp + runindofs(ctx, indx, lstp);
/* reserve space first, in case lstp gets moved around */
val.runstyp = DAT_LIST;
val.runsv.runsvstr = lstp;
runhres1(ctx, datsiz(*ele, ele + 1), 0, &val);
if (val.runsv.runsvstr != lstp)
ele = val.runsv.runsvstr + runindofs(ctx, indx, val.runsv.runsvstr);
/* push the operand */
runpbuf(ctx, *ele, ele + 1);
}
/*
* Check a property to ensure that it's a data property. Throws an
* error if the property contains a method. This is used for debugger
* speculative evaluation to ensure that we don't call any methods from
* within speculative expressions.
*/
static void runcheckpropdata(runcxdef *ctx, objnum obj, prpnum prop)
{
uint pofs;
objnum target;
objdef *objptr;
prpdef *prpptr;
int typ;
/* if the object is invalid, it's an error */
if (obj == MCMONINV)
errsig(ctx->runcxerr, ERR_REQVOB);
/* get the property */
pofs = objgetap(ctx->runcxmem, obj, prop, &target, FALSE);
/* if there's no property, it's okay - it will just return nil */
if (pofs == 0)
return;
/* get the object */
objptr = mcmlck(ctx->runcxmem, target);
/* get the property */
prpptr = (prpdef *)(((uchar *)objptr) + pofs);
typ = prptype(prpptr);
/* we're done with the object's memory now */
mcmunlck(ctx->runcxmem, target);
/* check the type */
switch (typ)
{
case DAT_CODE:
case DAT_DSTRING:
/*
* we can't call code or evaluate (i.e., print) double-quoted
* strings during speculative evaluation
*/
errsig(ctx->runcxerr, ERR_RTBADSPECEXPR);
default:
/* other types do not involve method calls, so they're okay */
break;
}
}
/* push an object's property */
void runpprop(runcxdef *ctx, uchar *noreg *codepp,
objnum callobj, prpnum callprop,
noreg objnum obj, prpnum prop, int inh, int argc, objnum self)
{
uint pofs;
uint saveofs = 0;
objdef *objptr;
prpdef *prpptr;
uchar *val;
int typ;
runsdef sval;
objnum target;
int times_through = 0;
int err;
objnum otherobj = 0;
NOREG((&obj, &codepp));
if (obj == MCMONINV) runsig(ctx, ERR_RUNNOBJ);
startover:
pofs = objgetap(ctx->runcxmem, obj, prop, &target, inh);
/* if nothing was found, push nil */
if (!pofs)
{
runpush(ctx, DAT_NIL, &sval);
return;
}
/* found a property; get the prpdef, and the value and type of data */
objptr = mcmlck(ctx->runcxmem, target);
ERRBEGIN(ctx->runcxerr) /* catch errors so we can unlock object */
prpptr = (prpdef *)(((uchar *)objptr) + pofs);
val = prpvalp(prpptr);
typ = prptype(prpptr);
/* determine what to do based on property type */
switch (typ)
{
case DAT_CODE:
/* save caller's code offset - caller's object may move */
if (codepp)
saveofs = runcpsav(ctx, codepp, callobj, callprop);
/* execute the code */
runexe(ctx, val, self, target, prop, argc);
/* restore caller's code pointer in case object moved */
if (codepp)
*codepp = runcprst(ctx, saveofs, callobj, callprop);
break;
case DAT_REDIR:
otherobj = osrp2(val);
break;
case DAT_DSTRING:
outfmt(ctx->runcxtio, val);
break;
case DAT_DEMAND:
break;
default:
runpbuf(ctx, typ, val);
break;
}
/* we're done - unlock the object */
mcmunlck(ctx->runcxmem, target);
/* if it's redirected, redirect it now */
if (typ == DAT_REDIR)
{
runpprop(ctx, codepp, callobj, callprop, otherobj, prop,
FALSE, argc, otherobj);
}
/* if an error occurs, unlock the object, and resignal the error */
ERRCATCH(ctx->runcxerr, err)
mcmunlck(ctx->runcxmem, target);
if (err < ERR_RUNEXIT || err > ERR_RUNEXITOBJ)
dbgdump(ctx->runcxdbg); /* dump the stack */
errrse(ctx->runcxerr);
ERREND(ctx->runcxerr)
/* apply special handling for set-on-first-use data */
if (typ == DAT_DEMAND)
{
/*
* if we've already done this, the property isn't being set by
* the callback, so we'll never get out of this loop - abort if
* so
*/
if (++times_through != 1)
runsig(ctx, ERR_DMDLOOP);
/* save caller's code offset - caller's object may move */
if (codepp)
saveofs = runcpsav(ctx, codepp, callobj, callprop);
/* invoke the callback to set the property on demand */
(*ctx->runcxdmd)(ctx->runcxdmc, obj, prop);
/* restore caller's code pointer */
if (codepp)
*codepp = runcprst(ctx, saveofs, callobj, callprop);
/* try again now that it's been set up */
goto startover;
}
}
/* ======================================================================== */
/*
* user exit callbacks
*/
/* External fnctions are now obsolete */
#if 0
static int runuftyp(runuxdef *ctx)
{
return(runtostyp(ctx->runuxctx));
}
static long runufnpo(runuxdef *ctx)
{
return(runpopnum(ctx->runuxctx));
}
static uchar *runufspo(runuxdef *ctx)
{
return(runpopstr(ctx->runuxctx));
}
static void runufdsc(runuxdef *ctx)
{
rundisc(ctx->runuxctx);
}
static void runufnpu(runuxdef *ctx, long num)
{
runpnum(ctx->runuxctx, num);
}
static void runufspu(runuxdef *ctx, uchar *str)
{
runsdef val;
val.runstyp = DAT_SSTRING;
val.runsv.runsvstr = str - 2;
runrepush(ctx->runuxctx, &val);
}
static void runufcspu(runuxdef *ctx, char *str)
{
runpstr(ctx->runuxctx, str, (int)strlen(str), ctx->runuxargc);
}
static uchar *runufsal(runuxdef *ctx, int len)
{
uchar *ret;
len += 2;
runhres(ctx->runuxctx, len, ctx->runuxargc);
ret = ctx->runuxctx->runcxhp;
oswp2(ret, len);
ret += 2;
ctx->runuxctx->runcxhp += len;
return(ret);
}
static void runuflpu(runuxdef *ctx, int typ)
{
runsdef val;
val.runstyp = typ;
runrepush(ctx->runuxctx, &val);
}
#endif
/* convert an osrp2 value to a signed short value */
#define runrp2s(p) ((short)(ushort)osrp2(p))
/* ======================================================================== */
/*
* execute p-code
*/
void runexe(runcxdef *ctx, uchar *p0, objnum self, objnum target,
prpnum targprop, int argc)
{
uchar *noreg p = p0;
uchar opc; /* opcode we're currently working on */
runsdef val; /* stack element (for pushing) */
runsdef val2; /* another one (for popping in two-op instructions) */
uint ofs; /* offset in code of current execution */
prpnum prop = 0; /* property number, when needed */
objnum obj = 0; /* object number, when needed */
runsdef *noreg rstsp; /* sp to reset to on DISCARD instructions */
uchar *lstp = nullptr; /* list pointer */
int nargc; /* argument count of called function */
runsdef *valp;
runsdef *stkval;
int i = 0;
int brkchk;
runsdef val3;
int asityp;
int asiext = 0;
int lclnum = 0;
#ifndef DBG_OFF
int err;
#endif
NOREG((&rstp, &p));
/* save entry SP - this is reset point until ENTER */
rstsp = ctx->runcxsp;
#ifndef DBG_OFF
/*
* For the debugger's sake, set up an error frame so that we catch
* any errors thrown during p-code execution within this function.
* If an error occurs, and the debugger is present, we'll set the
* instruction pointer back to the start of the line that caused the
* error and enter the debugger with the error indication. If the
* debugger isn't present, we'll simply re-throw the error. This
* entire block can be compiled out of the execution engine when
* linking a stand-alone (non-debug) version of the run-time.
*/
resume_from_error:
ERRBEGIN(ctx->runcxerr)
#endif /* DBG_OFF */
for (brkchk = 0;; ++brkchk)
{
/* check for break - signal if user has hit break */
if (brkchk == 1000)
{
brkchk = 0;
if (os_break()) runsig(ctx, ERR_USRINT);
}
opc = *p++;
switch (opc)
{
case OPCPUSHNUM:
val.runsv.runsvnum = osrp4s(p);
runpush(ctx, DAT_NUMBER, &val);
p += 4;
break;
case OPCPUSHOBJ:
val.runsv.runsvobj = osrp2(p);
runpush(ctx, DAT_OBJECT, &val);
p += 2;
break;
case OPCPUSHSELF:
val.runsv.runsvobj = self;
runpush(ctx, DAT_OBJECT, &val);
break;
case OPCPUSHSTR:
val.runsv.runsvstr = p;
runpush(ctx, DAT_SSTRING, &val);
p += osrp2(p); /* skip past string */
break;
case OPCPUSHLST:
val.runsv.runsvstr = p;
runpush(ctx, DAT_LIST, &val);
p += osrp2(p); /* skip past list */
break;
case OPCPUSHNIL:
runpush(ctx, DAT_NIL, &val);
break;
case OPCPUSHTRUE:
runpush(ctx, DAT_TRUE, &val);
break;
case OPCPUSHFN:
val.runsv.runsvobj = osrp2(p);
runpush(ctx, DAT_FNADDR, &val);
p += 2;
break;
case OPCPUSHPN:
val.runsv.runsvprp = osrp2(p);
runpush(ctx, DAT_PROPNUM, &val);
p += 2;
break;
case OPCNEG:
val.runstyp = DAT_NUMBER;
val.runsv.runsvnum = -runpopnum(ctx);
runrepush(ctx, &val);
break;
case OPCBNOT:
val.runstyp = DAT_NUMBER;
val.runsv.runsvnum = ~runpopnum(ctx);
runrepush(ctx, &val);
break;
case OPCNOT:
if (runtoslog(ctx))
runpush(ctx, runclog(!runpoplog(ctx)), &val);
else
runpush(ctx, runclog(runpopnum(ctx)), &val);
break;
case OPCADD:
runpop(ctx, &val2); /* right op is pushed last -> popped 1st */
runpop(ctx, &val);
runadd(ctx, &val, &val2, 2);
runrepush(ctx, &val);
break;
case OPCSUB:
runpop(ctx, &val2); /* right op is pushed last -> popped 1st */
runpop(ctx, &val);
(void)runsub(ctx, &val, &val2, 2);
runrepush(ctx, &val);
break;
case OPCMUL:
val.runstyp = DAT_NUMBER;
val.runsv.runsvnum = runpopnum(ctx);
val.runsv.runsvnum *= runpopnum(ctx);
runrepush(ctx, &val);
break;
case OPCBAND:
val.runstyp = DAT_NUMBER;
val.runsv.runsvnum = runpopnum(ctx);
val.runsv.runsvnum &= runpopnum(ctx);
runrepush(ctx, &val);
break;
case OPCBOR:
val.runstyp = DAT_NUMBER;
val.runsv.runsvnum = runpopnum(ctx);
val.runsv.runsvnum |= runpopnum(ctx);
runrepush(ctx, &val);
break;
case OPCSHL:
val.runstyp = DAT_NUMBER;
val.runsv.runsvnum = runpopnum(ctx);
val.runsv.runsvnum = runpopnum(ctx) << val.runsv.runsvnum;
runrepush(ctx, &val);
break;
case OPCSHR:
val.runstyp = DAT_NUMBER;
val.runsv.runsvnum = runpopnum(ctx);
val.runsv.runsvnum = runpopnum(ctx) >> val.runsv.runsvnum;
runrepush(ctx, &val);
break;
case OPCXOR:
/* allow logical ^ logical or number ^ number */
if (runtoslog(ctx))
{
int a, b;
/* logicals - return a logical value */
a = runpoplog(ctx);
b = runpoplog(ctx);
val.runstyp = runclog(a ^ b);
}
else
{
/* numeric value - return binary xor */
val.runstyp = DAT_NUMBER;
val.runsv.runsvnum = runpopnum(ctx);
val.runsv.runsvnum ^= runpopnum(ctx);
}
runrepush(ctx, &val);
break;
case OPCDIV:
val.runsv.runsvnum = runpopnum(ctx);
if (val.runsv.runsvnum == 0)
runsig(ctx, ERR_DIVZERO);
val.runsv.runsvnum = runpopnum(ctx) / val.runsv.runsvnum;
val.runstyp = DAT_NUMBER;
runrepush(ctx, &val);
break;
case OPCMOD:
val.runsv.runsvnum = runpopnum(ctx);
if (val.runsv.runsvnum == 0)
runsig(ctx, ERR_DIVZERO);
val.runsv.runsvnum = runpopnum(ctx) % val.runsv.runsvnum;
val.runstyp = DAT_NUMBER;
runrepush(ctx, &val);
break;
#ifdef NEVER
case OPCAND:
if (runtostyp(ctx) == DAT_LIST)
runlstisect(ctx);
else
runpush(ctx, runclog(runpoplog(ctx) && runpoplog(ctx)), &val);
break;
case OPCOR:
runpush(ctx, runclog(runpoplog(ctx) || runpoplog(ctx)), &val);
break;
#endif /* NEVER */
case OPCEQ:
runpush(ctx, runclog(runeq(ctx)), &val);
break;
case OPCNE:
runpush(ctx, runclog(!runeq(ctx)), &val);
break;
case OPCLT:
runpush(ctx, runclog(runmcmp(ctx) < 0), &val);
break;
case OPCLE:
runpush(ctx, runclog(runmcmp(ctx) <= 0), &val);
break;
case OPCGT:
runpush(ctx, runclog(runmcmp(ctx) > 0), &val);
break;
case OPCGE:
runpush(ctx, runclog(runmcmp(ctx) >= 0), &val);
break;
case OPCCALL:
{
objnum o;
/* get the argument count */
nargc = *p++;
/* ensure we have enough values to pass as arguments */
runcheckargc(ctx, &nargc);
/* object could move--save offset to restore 'p' after call */
o = osrp2(p);
ofs = runcpsav(ctx, &p, target, targprop);
/* execute the function */
runfn(ctx, o, nargc);
/* restore code pointer in case target object moved */
p = runcprst(ctx, ofs, target, targprop) + 2;
break;
}
case OPCGETP:
nargc = *p++;
runcheckargc(ctx, &nargc);
prop = osrp2(p);
p += 2;
obj = runpopobj(ctx);
runpprop(ctx, &p, target, targprop, obj, prop, FALSE, nargc,
obj);
break;
case OPCGETPDATA:
prop = osrp2(p);
p += 2;
obj = runpopobj(ctx);
runcheckpropdata(ctx, obj, prop);
runpprop(ctx, &p, target, targprop, obj, prop, FALSE, 0, obj);
break;
case OPCGETDBLCL:
#ifdef DBG_OFF
/* non-debug mode - this will always throw an error */
dbgfrfind(ctx->runcxdbg, 0, 0);
#else
/* debug mode - look up the local in the stack frame */
{
objnum frobj;
uint frofs;
runsdef *otherbp;
frobj = osrp2(p);
frofs = osrp2(p + 2);
otherbp = dbgfrfind(ctx->runcxdbg, frobj, frofs);
runrepush(ctx, otherbp + runrp2s(p + 4) - 1);
p += 6;
}
#endif
break;
case OPCGETLCL:
runrepush(ctx, ctx->runcxbp + runrp2s(p) - 1);
p += 2;
break;
case OPCRETURN:
runleave(ctx, argc /* was: osrp2(p) */);
dbgleave(ctx->runcxdbg, DBGEXRET);
goto done;
case OPCRETVAL:
/* if there's nothing on the stack, return nil */
if (runtostyp(ctx) != DAT_BASEPTR)
runpop(ctx, &val);
else
val.runstyp = DAT_NIL;
runleave(ctx, argc /* was: osrp2(p) */);
runrepush(ctx, &val);
dbgleave(ctx->runcxdbg, DBGEXVAL);
goto done;
case OPCENTER:
/* push old base pointer and set up new one */
ctx->runcxsp = rstsp;
val.runsv.runsvstr = (uchar *)ctx->runcxbp;
runpush(ctx, DAT_BASEPTR, &val);
ctx->runcxbp = ctx->runcxsp;
/* add a trace record */
dbgenter(ctx->runcxdbg, ctx->runcxbp, self, target, targprop,
0, argc);
/* initialize locals to nil */
for (i = osrp2(p); i; --i) runpush(ctx, DAT_NIL, &val);
p += 2; /* skip the local count operand */
/* save stack pointer - reset sp to this value on DISCARD */
rstsp = ctx->runcxsp;
break;
case OPCDISCARD:
ctx->runcxsp = rstsp;
break;
case OPCSWITCH:
{
int tostyp;
int match, typmatch;
runpop(ctx, &val);
tostyp = val.runstyp;
switch (tostyp)
{
case DAT_SSTRING:
tostyp = OPCPUSHSTR;
break;
case DAT_LIST:
tostyp = OPCPUSHLST;
break;
case DAT_PROPNUM:
tostyp = OPCPUSHPN;
break;
case DAT_FNADDR:
tostyp = OPCPUSHFN;
break;
case DAT_TRUE:
tostyp = OPCPUSHTRUE;
break;
case DAT_NIL:
tostyp = OPCPUSHNIL;
break;
}
p += osrp2(p); /* find the switch table */
i = osrp2(p); /* get number of cases */
/* look for a matching case */
for (match = FALSE; i && !match; --i)
{
p += 2; /* skip previous jump/size word */
typmatch = (*p == tostyp);
switch (*p++)
{
case OPCPUSHNUM:
match = (typmatch
&& val.runsv.runsvnum == osrp4s(p));
p += 4;
break;
case OPCPUSHLST:
case OPCPUSHSTR:
match = (typmatch
&& osrp2(val.runsv.runsvstr) == osrp2(p)
&& !memcmp(val.runsv.runsvstr,
p, (size_t)osrp2(p)));
p += runrp2s(p);
break;
case OPCPUSHPN:
match = (typmatch
&& val.runsv.runsvprp == osrp2(p));
p += 2;
break;
case OPCPUSHOBJ:
case OPCPUSHFN:
match = (typmatch
&& val.runsv.runsvobj == osrp2(p));
p += 2;
break;
case OPCPUSHSELF:
match = (typmatch && val.runsv.runsvobj == self);
break;
case OPCPUSHTRUE:
case OPCPUSHNIL:
match = typmatch;
break;
}
}
if (!match) p += 2; /* if default, skip to default case */
p += runrp2s(p); /* wherever we left off, p points to jump */
break;
}
case OPCJMP:
p += runrp2s(p);
break;
case OPCJT:
if (runtoslog(ctx))
p += (runpoplog(ctx) ? runrp2s(p) : 2);
else
p += (runpopnum(ctx) != 0 ? runrp2s(p) : 2);
break;
case OPCJF:
if (runtoslog(ctx))
p += ((!runpoplog(ctx)) ? runrp2s(p) : 2);
else if (runtostyp(ctx) == DAT_NUMBER)
p += ((runpopnum(ctx) == 0) ? runrp2s(p) : 2);
else /* consider any other type to be true */
{
rundisc(ctx); /* throw away the item considered to be true */
p += 2;
}
break;
case OPCSAY:
outfmt(ctx->runcxtio, p);
p += osrp2(p); /* skip past string */
break;
case OPCBUILTIN:
{
int binum;
runsdef *stkp;
nargc = *p++;
runcheckargc(ctx, &nargc);
binum = osrp2(p);
ofs = runcpsav(ctx, &p, target, targprop);
stkp = ctx->runcxsp - nargc;
dbgenter(ctx->runcxdbg, ctx->runcxsp + 1, MCMONINV, MCMONINV,
(prpnum)0, binum, nargc);
(*ctx->runcxbi[binum])((struct bifcxdef *)ctx->runcxbcx,
nargc);
if (ctx->runcxsp != stkp) {
dbgleave(ctx->runcxdbg, DBGEXVAL);
} else {
dbgleave(ctx->runcxdbg, DBGEXRET);
}
p = runcprst(ctx, ofs, target, targprop);
p += 2;
break;
}
case OPCPTRCALL:
nargc = *p++;
runcheckargc(ctx, &nargc);
ofs = runcpsav(ctx, &p, target, targprop);
runfn(ctx, runpopfn(ctx), nargc);
p = runcprst(ctx, ofs, target, targprop);
break;
case OPCINHERIT:
nargc = *p++;
runcheckargc(ctx, &nargc);
prop = osrp2(p);
p += 2;
runpprop(ctx, &p, target, targprop, target, prop, TRUE, nargc,
self);
break;
case OPCPTRINH:
nargc = *p++;
runcheckargc(ctx, &nargc);
prop = runpopprp(ctx);
runpprop(ctx, &p, target, targprop, target, prop, TRUE, nargc,
self);
break;
case OPCPTRGETP:
nargc = *p++;
runcheckargc(ctx, &nargc);
prop = runpopprp(ctx);
obj = runpopobj(ctx);
runpprop(ctx, &p, target, targprop, obj, prop, FALSE, nargc,
obj);
break;
case OPCPTRGETPDATA:
prop = runpopprp(ctx);
obj = runpopobj(ctx);
runcheckpropdata(ctx, obj, prop);
runpprop(ctx, &p, target, targprop, obj, prop, FALSE, 0, obj);
break;
case OPCEXPINH:
/* inheritance from explicit superclass */
nargc = *p++;
runcheckargc(ctx, &nargc);
prop = osrp2(p);
obj = osrp2(p + 2);
p += 4;
/*
* Evaluate the property of the given object, but keeping
* the same 'self' as is currently in effect. Note that the
* 'inherit' flag is FALSE in this call, even though we're
* inheriting, because the opcode explicitly specifies the
* object we want to inherit from.
*/
runpprop(ctx, &p, target, targprop, obj, prop, FALSE,
nargc, self);
break;
case OPCEXPINHPTR:
nargc = *p++;
runcheckargc(ctx, &nargc);
prop = runpopprp(ctx);
obj = osrp2(p);
p += 2;
runpprop(ctx, &p, target, targprop, obj, prop, FALSE,
nargc, self);
break;
case OPCPASS:
prop = osrp2(p);
runleave(ctx, 0);
dbgleave(ctx->runcxdbg, DBGEXPASS);
runpprop(ctx, &p, target, targprop, target, prop, TRUE, argc,
self);
goto done;
case OPCEXIT:
errsig(ctx->runcxerr, ERR_RUNEXIT);
break;
case OPCABORT:
errsig(ctx->runcxerr, ERR_RUNABRT);
break;
case OPCASKDO:
errsig(ctx->runcxerr, ERR_RUNASKD);
break;
case OPCASKIO:
errsig1(ctx->runcxerr, ERR_RUNASKI, ERRTINT, osrp2(p));
break;
case OPCJE:
p += (runeq(ctx) ? runrp2s(p) : 2);
break;
case OPCJNE:
p += (!runeq(ctx) ? runrp2s(p) : 2);
break;
case OPCJGT:
p += (runmcmp(ctx) > 0 ? runrp2s(p) : 2);
break;
case OPCJGE:
p += (runmcmp(ctx) >= 0 ? runrp2s(p) : 2);
break;
case OPCJLT:
p += (runmcmp(ctx) < 0 ? runrp2s(p) : 2);
break;
case OPCJLE:
p += (runmcmp(ctx) <= 0 ? runrp2s(p) : 2);
break;
case OPCJNAND:
p += (!(runpoplog(ctx) && runpoplog(ctx)) ? runrp2s(p) : 2);
break;
case OPCJNOR:
p += (!(runpoplog(ctx) || runpoplog(ctx)) ? runrp2s(p) : 2);
break;
case OPCGETPSELF:
nargc = *p++;
runcheckargc(ctx, &nargc);
prop = osrp2(p);
p += 2;
runpprop(ctx, &p, target, targprop, self, prop, FALSE, nargc,
self);
break;
case OPCGETPSELFDATA:
prop = osrp2(p);
p += 2;
runcheckpropdata(ctx, self, prop);
runpprop(ctx, &p, target, targprop, self, prop, FALSE, 0, self);
break;
case OPCGETPPTRSELF:
nargc = *p++;
runcheckargc(ctx, &nargc);
prop = runpopprp(ctx);
runpprop(ctx, &p, target, targprop, self, prop, FALSE, nargc,
self);
break;
case OPCGETPOBJ:
nargc = *p++;
runcheckargc(ctx, &nargc);
obj = osrp2(p);
prop = osrp2(p + 2);
p += 4;
runpprop(ctx, &p, target, targprop, obj, prop, FALSE, nargc,
obj);
break;
case OPCINDEX:
i = runpopnum(ctx); /* get index */
lstp = runpoplst(ctx); /* get the list */
runpind(ctx, i, lstp);
break;
case OPCJST:
if (runtostyp(ctx) == DAT_TRUE)
p += runrp2s(p);
else
{
(void)runpoplog(ctx);
p += 2;
}
break;
case OPCJSF:
if (runtostyp(ctx) == DAT_NIL ||
(runtostyp(ctx) == DAT_NUMBER &&
(ctx->runcxsp - 1)->runsv.runsvnum == 0))
p += runrp2s(p);
else
{
runpop(ctx, &val);
p += 2;
}
break;
case OPCCALLEXT:
{
#if 0 // external functions are now obsolete
static runufdef uf =
{
runuftyp, runufnpo, runufspo, runufdsc,
runufnpu, runufspu, runufcspu, runufsal,
runuflpu
};
int fn;
runxdef *ex;
runuxdef ux;
/* set up callback context */
ux.runuxctx = ctx;
ux.runuxvec = &uf;
ux.runuxargc = *p++;
fn = osrp2(p);
p += 2;
ex = &ctx->runcxext[fn];
if (!ex->runxptr)
{
if ((ex->runxptr = os_exfil(ex->runxnam)) == 0)
runsig1(ctx, ERR_EXTLOAD, ERRTSTR, ex->runxnam);
}
if (os_excall(ex->runxptr, &ux))
runsig1(ctx, ERR_EXTRUN, ERRTSTR, ex->runxnam);
#else
/* external functions are obsolete - throw an error */
runxdef *ex;
p += 1;
ex = &ctx->runcxext[osrp2(p)];
p += 2;
runsig1(ctx, ERR_EXTRUN, ERRTSTR, ex->runxnam);
#endif
}
break;
case OPCDBGRET:
goto done;
case OPCCONS:
{
uint totsiz;
uint oldsiz;
uint tot;
uint cursiz;
runsdef lstend;
tot = i = osrp2(p); /* get # of items to build into list */
p += 2;
/* reserve space for initial list (w/length word only) */
runhres(ctx, 2, 0);
/*
* Set up value to point to output list, making room
* for length prefix. Remember size-so-far separately.
*/
lstend.runstyp = DAT_LIST;
lstend.runsv.runsvstr = ctx->runcxhp;
ctx->runcxhp += 2;
totsiz = 2;
while (i--)
{
runpop(ctx, &val); /* get next value off stack */
cursiz = runsiz(&val);
/*
* Set up to allocate space. Before doing so, make
* sure the list under construction is valid, to
* ensure that it stays around after garbage
* collection.
*/
oldsiz = totsiz;
totsiz += cursiz + 1;
oswp2(lstend.runsv.runsvstr, oldsiz);
ctx->runcxhp = lstend.runsv.runsvstr + oldsiz;
runhres2(ctx, cursiz + 1, tot - i, &val, &lstend);
/* write this item to the list */
runputbuf(lstend.runsv.runsvstr + oldsiz, &val);
}
oswp2(lstend.runsv.runsvstr, totsiz);
ctx->runcxhp = lstend.runsv.runsvstr + totsiz;
runrepush(ctx, &lstend);
}
break;
case OPCARGC:
val.runsv.runsvnum = argc;
runpush(ctx, DAT_NUMBER, &val);
break;
case OPCCHKARGC:
if ((*p & 0x80) ? argc < (*p & 0x7f) : argc != *p)
{
char namebuf[128];
size_t namelen;
/*
* debugger is present - look up the name of the current
* function or method, so that we can report it in the
* error message
*/
if (targprop == 0)
{
/* we're in a function */
namelen = dbgnam(ctx->runcxdbg, namebuf, TOKSTFUNC,
target);
}
else
{
/* we're in an object.method */
namelen = dbgnam(ctx->runcxdbg, namebuf, TOKSTOBJ,
target);
namebuf[namelen++] = '.';
namelen += dbgnam(ctx->runcxdbg, namebuf + namelen,
TOKSTPROP, targprop);
}
namebuf[namelen] = '\0';
runsig1(ctx, ERR_ARGC, ERRTSTR, namebuf);
}
++p;
break;
case OPCLINE:
case OPCBP:
{
uchar *ptr = mcmobjptr(ctx->runcxmem, (mcmon)target);
/* set up the debugger frame record for this line */
dbgframe(ctx->runcxdbg, osrp2(p + 1), p - ptr);
#ifndef DBG_OFF
/* remember the instruction */
uchar instr = *(p - 1);
#endif
/* remember the offset of the line record */
ctx->runcxlofs = ofs = (p + 2 - ptr);
/* skip to the next instruction */
p += *p;
#ifndef DBG_OFF
/* let the debugger take over, if it wants to */
dbgssi(ctx->runcxdbg, ofs, instr, 0, &p);
#endif
break;
}
case OPCFRAME:
/* this is a frame record - just jump past it */
p += osrp2(p);
break;
case OPCASI_MASK | OPCASIDIR | OPCASILCL:
runpop(ctx, &val);
OSCPYSTRUCT(*(ctx->runcxbp + runrp2s(p) - 1), val);
stkval = &val;
p += 2;
goto no_assign;
case OPCASI_MASK | OPCASIDIR | OPCASIPRP:
obj = runpopobj(ctx);
prop = osrp2(p);
p += 2;
runpop(ctx, &val);
stkval = valp = &val;
goto assign_property;
case OPCASI_MASK | OPCASIDIR | OPCASIPRPPTR:
prop = runpopprp(ctx);
obj = runpopobj(ctx);
runpop(ctx, &val);
stkval = valp = &val;
goto assign_property;
case OPCNEW:
run_new(ctx, &p, target, targprop);
break;
case OPCDELETE:
run_delete(ctx, &p, target, targprop);
break;
default:
if ((opc & OPCASI_MASK) == OPCASI_MASK)
{
valp = &val;
stkval = &val;
asityp = (opc & OPCASITYP_MASK);
if (asityp == OPCASIEXT)
asiext = *p++;
/* get list element/property number if needed */
switch (opc & OPCASIDEST_MASK)
{
case OPCASIPRP:
obj = runpopobj(ctx);
prop = osrp2(p);
p += 2;
break;
case OPCASIPRPPTR:
prop = runpopprp(ctx);
obj = runpopobj(ctx);
break;
case OPCASIIND:
i = runpopnum(ctx);
lstp = runpoplst(ctx);
break;
case OPCASILCL:
lclnum = runrp2s(p);
p += 2;
break;
}
if (asityp != OPCASIDIR)
{
/* we have an = operator - get lval, modify, & set */
switch (opc & OPCASIDEST_MASK)
{
case OPCASILCL:
OSCPYSTRUCT(val, *(ctx->runcxbp + lclnum - 1));
break;
case OPCASIPRP:
case OPCASIPRPPTR:
runpprop(ctx, &p, target, targprop, obj, prop,
FALSE, 0, obj);
runpop(ctx, &val);
break;
case OPCASIIND:
runpind(ctx, i, lstp);
runpop(ctx, &val);
break;
}
/* if saving pre-inc/dec value, get the value now */
if ((opc & OPCASIPRE_MASK) == OPCASIPOST)
{
OSCPYSTRUCT(val3, val);
stkval = &val3;
}
}
/* get rvalue, except for inc/dec operations */
if (asityp != OPCASIINC && asityp != OPCASIDEC)
runpop(ctx, &val2);
/* now apply operation to lvalue using rvalue */
switch (asityp)
{
case OPCASIADD:
if ((opc & OPCASIIND) != 0)
{
runsdef val4;
/*
* we're adding to an indexed value out of a list -
* we need to make sure the list is protected from
* garbage collection, so push it back on the stack
* while we're working
*/
val4.runstyp = DAT_LIST;
val4.runsv.runsvstr = lstp;
runrepush(ctx, &val4);
/* carry out the addition */
runadd(ctx, &val, &val2, 2);
/*
* in case the list got moved during garbage
* collection, retrieve it from the stack
*/
lstp = runpoplst(ctx);
}
else
{
/* no list indexing - just carry out the addition */
runadd(ctx, &val, &val2, 2);
}
break;
case OPCASISUB:
if ((opc & OPCASIIND) != 0)
{
runsdef val4;
int result;
/* as with adding, protect the list from GC */
val4.runstyp = DAT_LIST;
val4.runsv.runsvstr = lstp;
runrepush(ctx, &val4);
/* carry out the subtraction and note the result */
result = runsub(ctx, &val, &val2, 2);
/* recover the list pointer */
lstp = runpoplst(ctx);
/* check to see if we have an assignment */
if (!result)
goto no_assign;
}
else
{
/* no list indexing - just do the subtraction */
if (!runsub(ctx, &val, &val2, 2))
goto no_assign;
}
break;
case OPCASIMUL:
if (val.runstyp != DAT_NUMBER
|| val2.runstyp != DAT_NUMBER)
runsig(ctx, ERR_REQNUM);
val.runsv.runsvnum *= val2.runsv.runsvnum;
break;
case OPCASIDIV:
if (val.runstyp != DAT_NUMBER
|| val2.runstyp != DAT_NUMBER)
runsig(ctx, ERR_REQNUM);
if (val2.runsv.runsvnum == 0)
runsig(ctx, ERR_DIVZERO);
val.runsv.runsvnum /= val2.runsv.runsvnum;
break;
case OPCASIINC:
if (val.runstyp != DAT_NUMBER)
runsig(ctx, ERR_REQNUM);
++(val.runsv.runsvnum);
break;
case OPCASIDEC:
if (val.runstyp != DAT_NUMBER)
runsig(ctx, ERR_REQNUM);
--(val.runsv.runsvnum);
break;
case OPCASIDIR:
valp = stkval = &val2;
break;
case OPCASIEXT:
switch (asiext)
{
case OPCASIMOD:
if (val.runstyp != DAT_NUMBER
|| val2.runstyp != DAT_NUMBER)
runsig(ctx, ERR_REQNUM);
if (val2.runsv.runsvnum == 0)
runsig(ctx, ERR_DIVZERO);
val.runsv.runsvnum %= val2.runsv.runsvnum;
break;
case OPCASIBAND:
if ((val.runstyp == DAT_TRUE
|| val.runstyp == DAT_NIL)
&& (val2.runstyp == DAT_TRUE
|| val2.runstyp == DAT_NIL))
{
int a, b;
a = (val.runstyp == DAT_TRUE ? 1 : 0);
b = (val2.runstyp == DAT_TRUE ? 1 : 0);
val.runstyp = runclog(a && b);
}
else if (val.runstyp == DAT_NUMBER
&& val2.runstyp == DAT_NUMBER)
val.runsv.runsvnum &= val2.runsv.runsvnum;
else
runsig(ctx, ERR_REQNUM);
break;
case OPCASIBOR:
if ((val.runstyp == DAT_TRUE
|| val.runstyp == DAT_NIL)
&& (val2.runstyp == DAT_TRUE
|| val2.runstyp == DAT_NIL))
{
int a, b;
a = (val.runstyp == DAT_TRUE ? 1 : 0);
b = (val2.runstyp == DAT_TRUE ? 1 : 0);
val.runstyp = runclog(a || b);
}
else if (val.runstyp == DAT_NUMBER
&& val2.runstyp == DAT_NUMBER)
val.runsv.runsvnum |= val2.runsv.runsvnum;
else
runsig(ctx, ERR_REQNUM);
break;
case OPCASIXOR:
if ((val.runstyp == DAT_TRUE || val.runstyp == DAT_NIL)
&& (val2.runstyp == DAT_TRUE
|| val2.runstyp == DAT_NIL))
{
int a, b;
a = (val.runstyp == DAT_TRUE ? 1 : 0);
b = (val2.runstyp == DAT_TRUE ? 1 : 0);
val.runstyp = runclog(a ^ b);
}
else if (val.runstyp == DAT_NUMBER
&& val2.runstyp == DAT_NUMBER)
val.runsv.runsvnum ^= val2.runsv.runsvnum;
else
runsig(ctx, ERR_REQNUM);
break;
case OPCASISHL:
if (val.runstyp != DAT_NUMBER
|| val2.runstyp != DAT_NUMBER)
runsig(ctx, ERR_REQNUM);
val.runsv.runsvnum <<= val2.runsv.runsvnum;
break;
case OPCASISHR:
if (val.runstyp != DAT_NUMBER
|| val2.runstyp != DAT_NUMBER)
runsig(ctx, ERR_REQNUM);
val.runsv.runsvnum >>= val2.runsv.runsvnum;
break;
default:
runsig(ctx, ERR_INVOPC);
}
break;
default:
runsig(ctx, ERR_INVOPC);
}
/* write the rvalue at *valp to the lvalue */
switch (opc & OPCASIDEST_MASK)
{
case OPCASILCL:
OSCPYSTRUCT(*(ctx->runcxbp + lclnum - 1), *valp);
break;
case OPCASIPRP:
case OPCASIPRPPTR:
assign_property:
{
void *valbuf;
uchar outbuf[4];
switch (valp->runstyp)
{
case DAT_LIST:
case DAT_SSTRING:
valbuf = valp->runsv.runsvstr;
break;
case DAT_NUMBER:
valbuf = outbuf;
oswp4s(outbuf, valp->runsv.runsvnum);
break;
case DAT_OBJECT:
case DAT_FNADDR:
valbuf = outbuf;
oswp2(outbuf, valp->runsv.runsvobj);
break;
case DAT_PROPNUM:
valbuf = outbuf;
oswp2(outbuf, valp->runsv.runsvprp);
break;
default:
valbuf = &valp->runsv;
break;
}
ofs = runcpsav(ctx, &p, target, targprop);
objsetp(ctx->runcxmem, obj, prop, valp->runstyp,
valbuf, ctx->runcxundo);
p = runcprst(ctx, ofs, target, targprop);
break;
}
case OPCASIIND:
{
uint newtot;
uint newsiz;
uint remsiz;
uint delsiz;
uchar *delp;
uchar *remp;
/* compute sizes and pointers to various parts */
ofs = runindofs(ctx, i, lstp);
delp = lstp + ofs; /* ptr to item to replace */
delsiz = datsiz(*delp, delp + 1); /* size of *delp */
remsiz = osrp2(lstp) - ofs - delsiz - 1;
newsiz = runsiz(valp); /* size of new item */
newtot = osrp2(lstp) + newsiz - delsiz; /* new tot */
/* reserve space for the new list & copy first part */
{
runsdef rval3;
/* make sure lstp stays valid before and after */
rval3.runstyp = DAT_LIST;
rval3.runsv.runsvstr = lstp;
runhres3(ctx, newtot, 3, &val, &val2, &rval3);
/* update all of the pointers within lstp */
lstp = rval3.runsv.runsvstr;
delp = lstp + ofs;
remp = lstp + ofs + delsiz + 1;
}
memcpy(ctx->runcxhp + 2, lstp + 2, (size_t)(ofs - 2));
/* set size of new list */
oswp2(ctx->runcxhp, newtot);
/* copy new item into buffer */
runputbuf(ctx->runcxhp + ofs, valp);
/* copy remainder and update heap pointer */
memcpy(ctx->runcxhp + ofs + newsiz + 1, remp,
(size_t)remsiz);
val.runstyp = DAT_LIST;
val.runsv.runsvstr = ctx->runcxhp;
stkval = &val;
ctx->runcxhp += newtot;
break;
}
}
no_assign: /* skip assignment - operation didn't change value */
if (*p == OPCDISCARD)
{
/* next assignment is DISCARD - deal with it now */
++p;
ctx->runcxsp = rstsp;
}
else
runrepush(ctx, stkval);
}
else
errsig(ctx->runcxerr, ERR_INVOPC);
}
}
/*
* come here to return - don't use 'return' directly, since that
* would not properly exit the error frame
*/
done:;
#ifndef DBG_OFF
/*
* Come here to catch any errors that occur during execution of this
* p-code
*/
ERRCATCH(ctx->runcxerr, err)
{
/*
* if the debugger isn't present, or we're already in the
* debugger, or if the debugger can't resume from errors, or if
* we're not in user code (in which case the debugger can't
* resume from this error even if it normally could resume from
* an error), simply re-signal the error
*/
if (!dbgpresent()
|| ctx->runcxdbg->dbgcxfcn == 0
|| !dbgu_err_resume(ctx->runcxdbg)
|| (ctx->runcxdbg->dbgcxflg & DBGCXFIND) != 0)
errrse(ctx->runcxerr);
/* check the error code */
switch (err)
{
case ERR_RUNEXIT:
case ERR_RUNABRT:
case ERR_RUNASKD:
case ERR_RUNASKI:
case ERR_RUNQUIT:
case ERR_RUNRESTART:
case ERR_RUNEXITOBJ:
/* don't trap these errors - resignal it immediately */
errrse(ctx->runcxerr);
default:
/* trap other errors to the debugger */
break;
}
/* if the object was unlocked, re-lock it */
if (p == 0)
mcmlck(ctx->runcxmem, target);
/* set up after the last OPCLINE instruction */
p = mcmobjptr(ctx->runcxmem, (mcmon)target) + ctx->runcxlofs - 2;
p += *p;
/*
* Keep the current error's arguments around for handling
* outside of this handler, since we'll need them in dbgssi.
*/
errkeepargs(ctx->runcxerr);
/* enter the debugger with the error code */
dbgssi(ctx->runcxdbg, ctx->runcxlofs, OPCLINE, err, &p);
/* check the error again */
switch (err)
{
case ERR_ARGC:
/* we can't continue from this - simply return */
break;
default:
/* resume execution */
goto resume_from_error;
}
}
ERREND(ctx->runcxerr);
#endif /* DBG_OFF */
}
/*
* Signal a run-time error. This function first calls the debugger
* single-step function to allow the debugger to trap the error, then
* signals the error as usual when the debugger returns.
*/
void runsign(runcxdef *ctx, int err)
{
/*
* If the debugger isn't capable of resuming from a run-time error,
* trap to the debugger now so that the user can see what happened.
* Do not trap to the debugger here if the debugger can resume from
* an error; instead, we'll trap in the p-code loop, since we'll be
* able to resume execution from the point of the error.
*
* Note that we can't resume from an error when there's no stack
* frame, so we'll trap to the debugger here in that case.
*/
if (ctx->runcxdbg->dbgcxfcn == 0
|| !dbgu_err_resume(ctx->runcxdbg))
dbgssi(ctx->runcxdbg, ctx->runcxlofs, OPCLINE, err, 0);
/* signal the error */
errsign(ctx->runcxerr, err, "TADS");
}
} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk