diff options
author | Paul Gilbert | 2019-05-17 13:51:43 -1000 |
---|---|---|
committer | Paul Gilbert | 2019-05-24 18:21:06 -0700 |
commit | 105a1b94bd9d5a0f10752e135671f4e9a4b0d8da (patch) | |
tree | 14a535ad171084e1908882a87b29fbd085ea93af /engines/glk/tads | |
parent | e83972f502142b4e6191b962a7be89829bd8d708 (diff) | |
download | scummvm-rg350-105a1b94bd9d5a0f10752e135671f4e9a4b0d8da.tar.gz scummvm-rg350-105a1b94bd9d5a0f10752e135671f4e9a4b0d8da.tar.bz2 scummvm-rg350-105a1b94bd9d5a0f10752e135671f4e9a4b0d8da.zip |
GLK: TADS2: Added code for output, run, various miscellaneous
Diffstat (limited to 'engines/glk/tads')
-rw-r--r-- | engines/glk/tads/os_glk.h | 69 | ||||
-rw-r--r-- | engines/glk/tads/osfrobtads.cpp | 8 | ||||
-rw-r--r-- | engines/glk/tads/osfrobtads.h | 11 | ||||
-rw-r--r-- | engines/glk/tads/tads2/character_map.cpp | 1 | ||||
-rw-r--r-- | engines/glk/tads/tads2/data.cpp | 61 | ||||
-rw-r--r-- | engines/glk/tads/tads2/data.h | 19 | ||||
-rw-r--r-- | engines/glk/tads/tads2/error_handling.h | 7 | ||||
-rw-r--r-- | engines/glk/tads/tads2/lib.h | 37 | ||||
-rw-r--r-- | engines/glk/tads/tads2/list.cpp | 42 | ||||
-rw-r--r-- | engines/glk/tads/tads2/list.h | 47 | ||||
-rw-r--r-- | engines/glk/tads/tads2/os.h | 6 | ||||
-rw-r--r-- | engines/glk/tads/tads2/output.cpp | 3559 | ||||
-rw-r--r-- | engines/glk/tads/tads2/run.cpp | 2394 | ||||
-rw-r--r-- | engines/glk/tads/tads2/run.h | 290 | ||||
-rw-r--r-- | engines/glk/tads/tads2/tokenizer.cpp | 1 | ||||
-rw-r--r-- | engines/glk/tads/tads2/vocabulary.cpp | 1 |
16 files changed, 6295 insertions, 258 deletions
diff --git a/engines/glk/tads/os_glk.h b/engines/glk/tads/os_glk.h new file mode 100644 index 0000000000..e58868b8b7 --- /dev/null +++ b/engines/glk/tads/os_glk.h @@ -0,0 +1,69 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* TADS OS interface file type definitions + * + * Defines certain datatypes used in the TADS operating system interface + */ + +#ifndef GLK_TADS_OS_GLK +#define GLK_TADS_OS_GLK + +namespace Glk { +namespace TADS { + +#define OSPATHCHAR '/' +#define OSPATHALT "" +#define OSPATHURL "/" +#define OSPATHSEP ':' +#define OS_NEWLINE_SEQ "\n" + +/* maximum width (in characters) of a line of text */ +#define OS_MAXWIDTH 255 + +/* round a size to worst-case alignment boundary */ +#define osrndsz(s) (((s)+3) & ~3) + +/* round a pointer to worst-case alignment boundary */ +#define osrndpt(p) ((uchar *)((((unsigned long)(p)) + 3) & ~3)) + +/* read unaligned portable unsigned 2-byte value, returning int */ +#define osrp2(p) READ_LE_UINT16(p) + +/* read unaligned portable signed 2-byte value, returning int */ +#define osrp2s(p) READ_LE_INT16(p) + +/* write int to unaligned portable 2-byte value */ +#define oswp2(p, i) WRITE_LE_UINT16(p, i) +#define oswp2s(p, i) WRITE_LE_INT16(p, i) + +/* read unaligned portable 4-byte value, returning unsigned long */ +#define osrp4(p) READ_LE_UINT32(p) +#define osrp4s(p) READ_LE_INT32(p) + +#define oswp4(p, l) WRITE_LE_UINT32(p, l) +#define oswp4s(p, l) WRITE_LE_INT32(p, l) + +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/osfrobtads.cpp b/engines/glk/tads/osfrobtads.cpp index 13229e5ae2..df265be21e 100644 --- a/engines/glk/tads/osfrobtads.cpp +++ b/engines/glk/tads/osfrobtads.cpp @@ -50,5 +50,13 @@ bool osfwb(osfildef *fp, void *buf, size_t count) { return dynamic_cast<Common::WriteStream *>(fp)->write(buf, count) != count; } +void osfflush(osfildef *fp) { + dynamic_cast<Common::WriteStream *>(fp)->flush(); +} + +osfildef *osfopwt(const char *fname, os_filetype_t typ) { + return osfoprwtb(fname, typ); +} + } // End of namespace TADS } // End of namespace Glk diff --git a/engines/glk/tads/osfrobtads.h b/engines/glk/tads/osfrobtads.h index b22dbaecac..ce1624d343 100644 --- a/engines/glk/tads/osfrobtads.h +++ b/engines/glk/tads/osfrobtads.h @@ -38,13 +38,6 @@ namespace Glk { namespace TADS { -#define OSPATHCHAR '/' -#define OSPATHALT "" -#define OSPATHURL "/" -#define OSPATHSEP ':' -#define OS_NEWLINE_SEQ "\n" - - /* Defined for Gargoyle. */ #define HAVE_STDINT_ @@ -178,7 +171,7 @@ int osfmode( const char* fname, int follow_links, unsigned long* mode, #define osfoprt(fname,typ) (fopen((fname),"r")) /* Open text file for writing. */ -#define osfopwt(fname,typ) (fopen((fname),"w")) +osfildef *osfopwt(const char *fname, os_filetype_t typ); /* Open text file for reading and writing, keeping the file's existing * contents if the file already exists or creating a new file if no @@ -221,7 +214,7 @@ inline osfildef *osfoprwtb(const char *fname, os_filetype_t typ); inline bool osfwb(osfildef *fp, void *buf, size_t count); /* Flush buffered writes to a file. */ -#define osfflush fflush +inline void osfflush(osfildef *fp); /* Read bytes from file. */ int osfrb(osfildef *fp, void *buf, size_t count); diff --git a/engines/glk/tads/tads2/character_map.cpp b/engines/glk/tads/tads2/character_map.cpp index 91daa8668c..9191835db5 100644 --- a/engines/glk/tads/tads2/character_map.cpp +++ b/engines/glk/tads/tads2/character_map.cpp @@ -26,6 +26,7 @@ #include "glk/tads/tads2/os.h" #include "glk/tads/tads2/text_io.h" #include "glk/tads/osfrobtads.h" +#include "glk/tads/os_glk.h" #include "common/algorithm.h" namespace Glk { diff --git a/engines/glk/tads/tads2/data.cpp b/engines/glk/tads/tads2/data.cpp index b19f45f1fd..fe002a3cf7 100644 --- a/engines/glk/tads/tads2/data.cpp +++ b/engines/glk/tads/tads2/data.cpp @@ -24,48 +24,49 @@ #include "glk/tads/tads2/lib.h" #include "glk/tads/tads2/run.h" #include "glk/tads/tads2/vocabulary.h" +#include "glk/tads/os_glk.h" #include "common/algorithm.h" namespace Glk { namespace TADS { namespace TADS2 { -Data::Data(DataType type) : _type(type) { -} - -size_t Data::size() const { - switch (_type) { - case DAT_NUMBER: - return 4; // numbers are in 4-byte lsb-first format - - case DAT_OBJECT: - return 2; // object numbers are in 2-byte lsb-first format +/* return size of a data value */ +uint datsiz(dattyp typ, void *val) +{ + switch(typ) + { + case DAT_NUMBER: + return(4); /* numbers are in 4-byte lsb-first format */ - case DAT_SSTRING: - case DAT_DSTRING: - case DAT_LIST: - return osrp2(_ptr); + case DAT_OBJECT: + return(2); /* object numbers are in 2-byte lsb-first format */ - case DAT_NIL: - case DAT_TRUE: - return 0; + case DAT_SSTRING: + case DAT_DSTRING: + case DAT_LIST: + return(osrp2((char *)val)); - case DAT_PROPNUM: - case DAT_SYN: - case DAT_FNADDR: - case DAT_REDIR: - return 2; + case DAT_NIL: + case DAT_TRUE: + return(0); - case DAT_TPL: - // template is counted array of 10-byte entries, plus length byte */ - return 1 + ((*(uchar *)_ptr) * VOCTPLSIZ); + case DAT_PROPNUM: + case DAT_SYN: + case DAT_FNADDR: + case DAT_REDIR: + return(2); + + case DAT_TPL: + /* template is counted array of 10-byte entries, plus length byte */ + return(1 + ((*(uchar *)val) * VOCTPLSIZ)); - case DAT_TPL2: - return 1 + ((*(uchar *)_ptr) * VOCTPL2SIZ); + case DAT_TPL2: + return(1 + ((*(uchar *)val) * VOCTPL2SIZ)); - default: - return 0; - } + default: + return(0); + } } } // End of namespace TADS2 diff --git a/engines/glk/tads/tads2/data.h b/engines/glk/tads/tads2/data.h index 8d122c444a..e573730fb7 100644 --- a/engines/glk/tads/tads2/data.h +++ b/engines/glk/tads/tads2/data.h @@ -47,23 +47,10 @@ enum DataType { DAT_REDIR = 16, ///< redirection to different object DAT_TPL2 = 17 ///< new-style template }; -typedef DataType dattyp; +typedef int dattyp; -class Data { -private: - DataType _type; - void *_ptr; -public: - /** - * Constructor - */ - Data(DataType type); - - /** - * Return the size of the data - */ - size_t size() const; -}; +/* determine the size of a piece of data */ +uint datsiz(dattyp typ, void *valptr); } // End of namespace TADS2 } // End of namespace TADS diff --git a/engines/glk/tads/tads2/error_handling.h b/engines/glk/tads/tads2/error_handling.h index a225bbea74..ab1f86e396 100644 --- a/engines/glk/tads/tads2/error_handling.h +++ b/engines/glk/tads/tads2/error_handling.h @@ -67,9 +67,6 @@ namespace Glk { namespace TADS { namespace TADS2 { -// TODO: Clean away use of jmp_buf, since ScummVM doesn't allow it -struct jmp_buf {}; - /** * Maximum length of a facility identifier */ @@ -86,7 +83,7 @@ struct errdef { char errfac[ERRFACMAX+1]; /* facility of current error */ erradef erraav[10]; /* parameters for error */ int erraac; /* count of parameters in argc */ - jmp_buf errbuf; /* jump buffer for current error frame */ + //jmp_buf errbuf; ScummVM doesn't support using jump buffers }; #define ERRBUFSIZ 512 @@ -120,7 +117,7 @@ struct errcxdef { #define ERRBEGIN(ctx) \ { \ errdef fr_; \ - if ((fr_.errcode = setjmp(fr_.errbuf)) == 0) \ + if (1 /*(fr_.errcode = setjmp(fr_.errbuf)) == 0 */) \ { \ fr_.errprv = (ctx)->errcxptr; \ (ctx)->errcxptr = &fr_; diff --git a/engines/glk/tads/tads2/lib.h b/engines/glk/tads/tads2/lib.h index 095abfc69f..a7ed2aadf7 100644 --- a/engines/glk/tads/tads2/lib.h +++ b/engines/glk/tads/tads2/lib.h @@ -76,20 +76,12 @@ typedef uint32 uint32_t; #define bic(va, bt) ((va) &= ~(bt)) /* - * noreg/NOREG - use for variables changed in error-protected code that - * are used in error handling code. Use 'noreg' on the declaration like - * a storage class qualifier. Use 'NOREG' as an argument call, passing - * the addresses of all variables declared noreg. For non-ANSI - * compilers, a routine osnoreg(/o_ void *arg0, ... _o/) must be - * defined. + * noreg/NOREG - was used for variables changed in error-protected code that + * are used in error handling code. However, since ScummVM doesn't support + * setjmp for portability reasons, the following can be left as blank defines */ -#ifdef OSANSI -# define noreg volatile -# define NOREG(arglist) -#else /* OSANSI */ # define noreg -# define NOREG(arglist) osnoreg arglist ; -#endif /* OSANSI */ +# define NOREG(arglist) /* * Linting directives. You can define these before including this file @@ -136,27 +128,6 @@ void varused(); #define t_isspace(c) \ (((unsigned char)(c)) <= 127 && Common::isSpace((unsigned char)(c))) - -/* round a size to worst-case alignment boundary */ -#define osrndsz(s) (((s)+3) & ~3) - -/* round a pointer to worst-case alignment boundary */ -#define osrndpt(p) ((uchar *)((((unsigned long)(p)) + 3) & ~3)) - -/* read unaligned portable unsigned 2-byte value, returning int */ -#define osrp2(p) READ_LE_UINT16(p) - -/* read unaligned portable signed 2-byte value, returning int */ -#define osrp2s(p) READ_LE_INT16(p) - -/* write int to unaligned portable 2-byte value */ -#define oswp2(p, i) WRITE_LE_UINT16(p, i) -#define oswp2s(p, i) WRITE_LE_INT16(p, i) - -/* read unaligned portable 4-byte value, returning unsigned long */ -#define osrp4(p) READ_LE_UINT32(p) -#define osrp4s(p) READ_LE_INT32(p) - } // End of namespace TADS2 } // End of namespace TADS } // End of namespace Glk diff --git a/engines/glk/tads/tads2/list.cpp b/engines/glk/tads/tads2/list.cpp new file mode 100644 index 0000000000..88cfad49ba --- /dev/null +++ b/engines/glk/tads/tads2/list.cpp @@ -0,0 +1,42 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/tads2/list.h" +#include "glk/tads/tads2/data.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +void lstadv(uchar **lstp, uint *sizp) +{ + uint siz; + + siz = datsiz(**lstp, (*lstp) + 1) + 1; + assert(siz <= *sizp); + *lstp += siz; + *sizp -= siz; +} + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/tads2/list.h b/engines/glk/tads/tads2/list.h new file mode 100644 index 0000000000..bb5eb2b079 --- /dev/null +++ b/engines/glk/tads/tads2/list.h @@ -0,0 +1,47 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* List definitions + * + * A TADS run-time list is essentially a packed counted array. + * The first thing in a list is a ushort, which specifies the + * number of elements in the list. The list elements are then + * packed into the list immediately following. + */ + +#ifndef GLK_TADS_TADS2_LIST +#define GLK_TADS_TADS2_LIST + +#include "glk/tads/tads2/lib.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* advance a list pointer/size pair to the next element of a list */ +void lstadv(uchar **lstp, uint *sizp); + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/os.h b/engines/glk/tads/tads2/os.h index 5dcc83c935..666a5cb809 100644 --- a/engines/glk/tads/tads2/os.h +++ b/engines/glk/tads/tads2/os.h @@ -393,8 +393,7 @@ int os_get_zoneinfo_key(char *buf, size_t buflen); * timezone's clock settings, name(s), and rules for recurring annual * changes between standard time and daylight time, if applicable. */ -struct os_tzrule_t -{ +struct os_tzrule_t { /* * Day of year, 1-365, NEVER counting Feb 29; set to 0 if not used. * Corresponds to the "J" format in Unix TZ strings. (Called "Julian @@ -448,8 +447,7 @@ struct os_tzrule_t /* time of day, in seconds after midnight (e.g., 2AM is 120 == 2*60*60) */ int time; }; -struct os_tzinfo_t -{ +struct os_tzinfo_t { /* * The local offset from GMT, in seconds, for standard time and * daylight time in this zone. These values are positive for zones diff --git a/engines/glk/tads/tads2/output.cpp b/engines/glk/tads/tads2/output.cpp new file mode 100644 index 0000000000..6aa39b5683 --- /dev/null +++ b/engines/glk/tads/tads2/output.cpp @@ -0,0 +1,3559 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/tads2/character_map.h" +#include "glk/tads/tads2/debug.h" +#include "glk/tads/tads2/error.h" +#include "glk/tads/tads2/memory_cache.h" +#include "glk/tads/tads2/os.h" +#include "glk/tads/tads2/run.h" +#include "glk/tads/tads2/text_io.h" +#include "glk/tads/tads2/vocabulary.h" +#include "glk/tads/os_glk.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* Forward declarations */ +struct out_stream_info; + +/* + * use our own isxxx - anything outside the US ASCII range is not reliably + * classifiable by the normal C isxxx routines + */ +#define outissp(c) (((uchar)(c)) <= 127 && Common::isSpace((uchar)(c))) +#define outisal(c) (((uchar)(c)) <= 127 && Common::isAlpha((uchar)(c))) +#define outisdg(c) (((uchar)(c)) <= 127 && Common::isDigit((uchar)(c))) +#define outisup(c) (((uchar)(c)) <= 127 && Common::isUpper((uchar)(c))) +#define outislo(c) (((uchar)(c)) <= 127 && Common::isLower((uchar)(c))) + + +/* + * Turn on formatter-level MORE mode, EXCEPT under any of the following + * conditions: + * + * - this is a MAC OS port + *. - this is an HTML TADS interpreter + *. - USE_OS_LINEWRAP is defined + * + * Formatter-level MORE mode and formatter-level line wrapping go together; + * you can't have one without the other. So, if USE_OS_LINEWRAP is + * defined, we must also use OS-level MORE mode, which means we don't want + * formatter-level MORE mode. + * + * For historical reasons, we check specifically for MAC_OS. This was the + * first platform for which OS-level MORE mode and OS-level line wrapping + * were invented; at the time, we foolishly failed to anticipate that more + * platforms might eventually come along with the same needs, so we coded a + * test for MAC_OS rather than some more abstract marker. For + * compatibility, we retain this specific test. + * + * USE_OS_LINEWRAP is intended as the more abstract marker we should + * originally have used. A port should #define USE_OS_LINEWRAP in its + * system-specific os_xxx.h header to turn on OS-level line wrapping and + * OS-level MORE mode. Ports should avoid adding new #ifndef tests for + * specific platforms here; we've only retained the MAC_OS test because we + * don't want to break the existing MAC_OS port. + */ +#ifndef MAC_OS +# ifndef USE_HTML +# ifndef USE_OS_LINEWRAP +# define USE_MORE /* activate formatter-level more-mode */ +# endif /* USE_OS_LINEWRAP */ +# endif /* USE_HTML */ +#endif /* MAC_OS */ + +/* + * In HTML mode, don't use MORE mode. Note that we explicitly turn MORE + * mode OFF, even though we won't have turned it on above, because it might + * have been turned on by an os_xxx.h header. This is here for historical + * reasons; in particular, some of the HTML interpreter builds include + * headers that were originally written for the normal builds for those + * same platforms, and those original headers explicitly #define USE_MORE + * somewhere. So, to be absolutely sure we get it right here, we have to + * explicitly turn off USE_MORE when compiling for HTML mode. + */ +#ifdef USE_HTML +# ifdef USE_MORE +# undef USE_MORE +# endif +#endif + +/* + * QSPACE is the special character for a quoted space (internally, the + * sequence "\ " (backslash-space) is converted to QSPACE). It must not + * be any printable character. The value here may need to be changed in + * the extremely unlikely event that TADS is ever ported to an EBCDIC + * machine. + */ +#define QSPACE 26 + +/* + * QTAB is a special hard tab character indicator. We use this when we + * need to generate a hard tab to send to the underlying output layer + * (in particular, we use this to send hard tabs to the HTML formatter + * when we're in HTML mode). + */ +#define QTAB 25 + + +/* maximum width of the display */ +#define MAXWIDTH OS_MAXWIDTH + + +/* ------------------------------------------------------------------------ */ +/* + * Globals and statics. These should really be moved into a context + * structure, so that the output formatter subsystem could be shared + * among multiple clients. For now, there's no practical problem using + * statics, because we only need a single output subsystem at one time. + */ + +/* current script (command input) file */ +extern osfildef *scrfp; + +/* + * This should be TRUE if the output should have two spaces after a + * period (or other such punctuation. It should generally be TRUE for + * fixed-width fonts, and FALSE for proportional fonts. + */ +static int doublespace = 1; + +/* + * Log file handle and name. If we're copying output to a log file, + * these will tell us about the file. + */ +osfildef *logfp; +static char logfname[OSFNMAX]; + +/* flag indicating whether output has occurred since last check */ +static uchar outcnt; + +/* flag indicating whether hidden output has occurred */ +static uchar hidout; + +/* flag indicating whether to show (TRUE) or hide (FALSE) output */ +static uchar outflag; + +/* flag indicating whether output is hidden for debugging purposes */ +int dbghid; + +/* + * Current recursion level in formatter invocation + */ +static int G_recurse = 0; + +/* active stream in current recursion level */ +static out_stream_info *G_cur_stream; + +/* watchpoint mode flag */ +static uchar outwxflag; + +/* + * User filter function. When this function is set, we'll invoke this + * function for each string that's displayed through the output + * formatter. + */ +static objnum G_user_filter = MCMONINV; + + +/* ------------------------------------------------------------------------ */ +/* + * Hack to run with TADS 2.0 with minimal reworking. Rather than using + * an allocated output layer context, store our subsystem context + * information in some statics. This is less clean than using a real + * context, but doesn't create any practical problems as we don't need + * to share the output formatter subsystem among multiple simultaneous + * callers. + */ +static runcxdef *runctx; /* execution context */ +static uchar *fmsbase; /* format string area base */ +static uchar *fmstop; /* format string area top */ +static objnum cmdActor; /* current actor */ + +/* forward declarations of static functions */ +static void outstring_stream(out_stream_info *stream, char *s); +static void outchar_noxlat_stream(out_stream_info *stream, char c); +static char out_parse_entity(char *outbuf, size_t outbuf_size, + char **sp, size_t *slenp); + + +/* ------------------------------------------------------------------------ */ +/* + * HTML lexical analysis mode + */ +#define HTML_MODE_NORMAL 0 /* normal text, not in a tag */ +#define HTML_MODE_TAG 1 /* parsing inside a tag */ +#define HTML_MODE_SQUOTE 2 /* in a single-quoted string in a tag */ +#define HTML_MODE_DQUOTE 3 /* in a double-quoted string in a tag */ + +/* + * HTML parsing mode flag for <BR> tags. We defer these until we've + * read the full tag in order to obey an HEIGHT attribute we find. When + * we encounter a <BR>, we figure out whether we think we'll need a + * flush or a blank line; if we find a HEIGHT attribute, we may change + * this opinion. + */ +#define HTML_DEFER_BR_NONE 0 /* no pending <BR> */ +#define HTML_DEFER_BR_FLUSH 1 /* only need an outflush() */ +#define HTML_DEFER_BR_BLANK 2 /* need an outblank() */ + +/* + * If we're compiling for an HTML-enabled underlying output subsystem, + * we want to call the underlying OS layer when switching in and out of + * HTML mode. If the underlying system doesn't process HTML, we don't + * need to let it know anything about HTML mode. + */ +#ifdef USE_HTML +# define out_start_html(stream) os_start_html() +# define out_end_html(stream) os_end_html() +#else +# define out_start_html(stream) +# define out_end_html(stream) +#endif + + +/* ------------------------------------------------------------------------ */ +/* + * Output formatter stream state structure. This structure encapsulates + * the state of an individual output stream. + */ +struct out_stream_info { + /* low-level display routine (va_list version) */ + void (*do_print)(out_stream_info *stream, const char *str); + + /* current line position and output column */ + uchar linepos; + uchar linecol; + + /* number of lines on the screen (since last MORE prompt) */ + int linecnt; + + /* output buffer */ + char linebuf[MAXWIDTH]; + + /* + * attribute buffer - we keep one attribute entry for each character in + * the line buffer + */ + int attrbuf[MAXWIDTH]; + + /* current attribute for text we're buffering into linebuf */ + int cur_attr; + + /* last attribute we wrote to the osifc layer */ + int os_attr; + + /* CAPS mode - next character output is converted to upper-case */ + uchar capsflag; + + /* NOCAPS mode - next character output is converted to lower-case */ + uchar nocapsflag; + + /* ALLCAPS mode - all characters output are converted to upper-case */ + uchar allcapsflag; + + /* capture information */ + mcmcxdef *capture_ctx; /* memory context to use for capturing */ + mcmon capture_obj; /* object holding captured output */ + uint capture_ofs; /* write offset in capture object */ + int capturing; /* true -> we are capturing output */ + + /* "preview" state for line flushing */ + int preview; + + /* flag indicating that we just flushed a new line */ + int just_did_nl; + + /* this output stream uses "MORE" mode */ + int use_more_mode; + + /* + * This output stream uses OS-level line wrapping - if this is set, + * the output formatter will not insert a newline at the end of a + * line that it's flushing for word wrapping, but will instead let + * the underlying OS display layer handle the wrapping. + */ + int os_line_wrap; + + /* + * Flag indicating that the underlying output system wants to + * receive its output as HTML. + * + * If this is true, we'll pass through HTML to the underlying output + * system, and in addition generate HTML sequences for certain + * TADS-native escapes (for example, we'll convert the "\n" sequence + * to a <BR> sequence). + * + * If this is false, we'll do just the opposite: we'll remove HTML + * from the output stream and convert it into normal text sequences. + */ + int html_target; + + /* + * Flag indicating that the target uses plain text. If this flag is + * set, we won't add the OS escape codes for highlighted characters. + */ + int plain_text_target; + + /* + * Flag indicating that the caller is displaying HTML. We always + * start off in text mode; the client can switch to HTML mode by + * displaying a special escape sequence, and can switch back to text + * mode by displaying a separate special escape sequence. + */ + int html_mode; + + /* current lexical analysis mode */ + unsigned int html_mode_flag; + + /* <BR> defer mode */ + unsigned int html_defer_br; + + /* + * HTML "ignore" mode - we suppress all output when parsing the + * contents of a <TITLE> or <ABOUTBOX> tag + */ + int html_in_ignore; + + /* + * HTML <TITLE> mode - when we're in this mode, we're gathering the + * title (i.e., we're inside a <TITLE> tag's contents). We'll copy + * characters to the title buffer rather than the normal output + * buffer, and then call os_set_title() when we reach the </TITLE> + * tag. + */ + int html_in_title; + + /* buffer for the title */ + char html_title_buf[256]; + + /* pointer to next available character in title buffer */ + char *html_title_ptr; + + /* quoting level */ + int html_quote_level; + + /* PRE nesting level */ + int html_pre_level; + + /* + * Parsing mode flag for ALT attributes. If we're parsing a tag + * that allows ALT, such as IMG or SOUND, we'll set this flag, then + * insert the ALT text if we encounter it during parsing. + */ + int html_allow_alt; +}; + +/* + * Default output converter. This is the output converter for the + * standard display. Functions in the public interface that do not + * specify an output converter will use this converter by default. + */ +static out_stream_info G_std_disp; + +/* + * Log file converter. This is the output converter for a log file. + * Whenever we open a log file, we'll initialize this converter; as we + * display text to the main display, we'll also copy it to the log file. + * + * We maintain an entire separate conversion context for the log file, + * so that we can perform a different set of conversions on it. We may + * want, for example, to pass HTML text through to the OS display + * subsystem (this is the case for the HTML-enabled interpreter), but + * we'll still want to convert log file output to text. By keeping a + * separate display context for the log file, we can format output to + * the log file using an entirely different style than we do for the + * display. + */ +static out_stream_info G_log_disp; + + +/* ------------------------------------------------------------------------ */ +/* + * low-level output handlers for the standard display and log file + */ + +/* standard display printer */ +static void do_std_print(out_stream_info *stream, const char *str) +{ + VARUSED(stream); + + /* display the text through the OS layer */ + os_printz(str); +} + +/* log file printer */ +static void do_log_print(out_stream_info *stream, const char *str) +{ + VARUSED(stream); + + /* display to the log file */ + if (logfp != 0 && G_os_moremode) + { + os_fprintz(logfp, str); + osfflush(logfp); + } +} + + +/* ------------------------------------------------------------------------ */ +/* + * initialize a generic output formatter state structure + */ +static void out_state_init(out_stream_info *stream) +{ + /* start out at the first column */ + stream->linepos = 0; + stream->linecol = 0; + stream->linebuf[0] = '\0'; + + /* set normal text attributes */ + stream->cur_attr = 0; + stream->os_attr = 0; + + /* start out at the first line */ + stream->linecnt = 0; + + /* we're not in either "caps", "nocaps", or "allcaps" mode yet */ + stream->capsflag = stream->nocapsflag = stream->allcapsflag = FALSE; + + /* we're not capturing yet */ + stream->capturing = FALSE; + stream->capture_obj = MCMONINV; + + /* we aren't previewing a line yet */ + stream->preview = 0; + + /* we haven't flushed a new line yet */ + stream->just_did_nl = FALSE; + + /* presume this stream does not use "MORE" mode */ + stream->use_more_mode = FALSE; + + /* presume this stream uses formatter-level line wrapping */ + stream->os_line_wrap = FALSE; + + /* assume that the underlying system is not HTML-enabled */ + stream->html_target = FALSE; + + /* presume this target accepts OS highlighting sequences */ + stream->plain_text_target = FALSE; + + /* start out in text mode */ + stream->html_mode = FALSE; + + /* start out in "normal" lexical state */ + stream->html_mode_flag = HTML_MODE_NORMAL; + + /* not in an ignored tag yet */ + stream->html_in_ignore = FALSE; + + /* not in title mode yet */ + stream->html_in_title = FALSE; + + /* not yet deferring line breaks */ + stream->html_defer_br = HTML_DEFER_BR_NONE; + + /* not yet in quotes */ + stream->html_quote_level = 0; + + /* not yet in a PRE block */ + stream->html_pre_level = 0; + + /* not in an ALT tag yet */ + stream->html_allow_alt = FALSE; +} + + +/* ------------------------------------------------------------------------ */ +/* + * initialize a standard display stream + */ +static void out_init_std(out_stream_info *stream) +{ + /* there's no user output filter function yet */ + out_set_filter(MCMONINV); + + /* initialize the basic stream state */ + out_state_init(stream); + + /* set up the low-level output routine */ + G_std_disp.do_print = do_std_print; + +#ifdef USE_MORE + /* + * We're compiled for MORE mode, and we're not compiling for an + * underlying HTML formatting layer, so use MORE mode for the + * standard display stream. + */ + stream->use_more_mode = TRUE; +#else + /* + * We're compiled for OS-layer (or HTML-layer) MORE handling. For + * this case, use OS-layer (or HTML-layer) line wrapping as well. + */ + stream->os_line_wrap = TRUE; +#endif + +#ifdef USE_HTML + /* + * if we're compiled for HTML mode, set the standard output stream + * so that it knows it has an HTML target - this will ensure that + * HTML tags are passed through to the underlying stream, and that + * we generate HTML equivalents for our own control sequences + */ + stream->html_target = TRUE; +#endif +} + +/* + * initialize a standard log file stream + */ +static void out_init_log(out_stream_info *stream) +{ + /* initialize the basic stream state */ + out_state_init(stream); + + /* set up the low-level output routine */ + stream->do_print = do_log_print; + + /* use plain text in the log file stream */ + stream->plain_text_target = TRUE; +} + + + +/* ------------------------------------------------------------------------ */ +/* + * table of '&' character name sequences + */ +struct amp_tbl_t { + /* entity name */ + const char *cname; + + /* HTML Unicode character value */ + uint html_cval; + + /* native character set expansion */ + char *expan; +}; + +/* + * HTML entity mapping table. When we're in non-HTML mode, we keep our + * own expansion table so that we can map HTML entity names into the + * local character set. + * + * The entries in this table must be in sorted order (by HTML entity + * name), because we use a binary search to find an entity name in the + * table. + */ +static struct amp_tbl_t amp_tbl[] = { + { "AElig", 198, 0 }, + { "Aacute", 193, 0 }, + { "Abreve", 258, 0 }, + { "Acirc", 194, 0 }, + { "Agrave", 192, 0 }, + { "Alpha", 913, 0 }, + { "Aogon", 260, 0 }, + { "Aring", 197, 0 }, + { "Atilde", 195, 0 }, + { "Auml", 196, 0 }, + { "Beta", 914, 0 }, + { "Cacute", 262, 0 }, + { "Ccaron", 268, 0 }, + { "Ccedil", 199, 0 }, + { "Chi", 935, 0 }, + { "Dagger", 8225, 0 }, + { "Dcaron", 270, 0 }, + { "Delta", 916, 0 }, + { "Dstrok", 272, 0 }, + { "ETH", 208, 0 }, + { "Eacute", 201, 0 }, + { "Ecaron", 282, 0 }, + { "Ecirc", 202, 0 }, + { "Egrave", 200, 0 }, + { "Eogon", 280, 0 }, + { "Epsilon", 917, 0 }, + { "Eta", 919, 0 }, + { "Euml", 203, 0 }, + { "Gamma", 915, 0 }, + { "Iacute", 205, 0 }, + { "Icirc", 206, 0 }, + { "Igrave", 204, 0 }, + { "Iota", 921, 0 }, + { "Iuml", 207, 0 }, + { "Kappa", 922, 0 }, + { "Lacute", 313, 0 }, + { "Lambda", 923, 0 }, + { "Lcaron", 317, 0 }, + { "Lstrok", 321, 0 }, + { "Mu", 924, 0 }, + { "Nacute", 323, 0 }, + { "Ncaron", 327, 0 }, + { "Ntilde", 209, 0 }, + { "Nu", 925, 0 }, + { "OElig", 338, 0 }, + { "Oacute", 211, 0 }, + { "Ocirc", 212, 0 }, + { "Odblac", 336, 0 }, + { "Ograve", 210, 0 }, + { "Omega", 937, 0 }, + { "Omicron", 927, 0 }, + { "Oslash", 216, 0 }, + { "Otilde", 213, 0 }, + { "Ouml", 214, 0 }, + { "Phi", 934, 0 }, + { "Pi", 928, 0 }, + { "Prime", 8243, 0 }, + { "Psi", 936, 0 }, + { "Racute", 340, 0 }, + { "Rcaron", 344, 0 }, + { "Rho", 929, 0 }, + { "Sacute", 346, 0 }, + { "Scaron", 352, 0 }, + { "Scedil", 350, 0 }, + { "Sigma", 931, 0 }, + { "THORN", 222, 0 }, + { "Tau", 932, 0 }, + { "Tcaron", 356, 0 }, + { "Tcedil", 354, 0 }, + { "Theta", 920, 0 }, + { "Uacute", 218, 0 }, + { "Ucirc", 219, 0 }, + { "Udblac", 368, 0 }, + { "Ugrave", 217, 0 }, + { "Upsilon", 933, 0 }, + { "Uring", 366, 0 }, + { "Uuml", 220, 0 }, + { "Xi", 926, 0 }, + { "Yacute", 221, 0 }, + { "Yuml", 376, 0 }, + { "Zacute", 377, 0 }, + { "Zcaron", 381, 0 }, + { "Zdot", 379, 0 }, + { "Zeta", 918, 0 }, + { "aacute", 225, 0 }, + { "abreve", 259, 0 }, + { "acirc", 226, 0 }, + { "acute", 180, 0 }, + { "aelig", 230, 0 }, + { "agrave", 224, 0 }, + { "alefsym", 8501, 0 }, + { "alpha", 945, 0 }, + { "amp", '&', 0 }, + { "and", 8743, 0 }, + { "ang", 8736, 0 }, + { "aogon", 261, 0 }, + { "aring", 229, 0 }, + { "asymp", 8776, 0 }, + { "atilde", 227, 0 }, + { "auml", 228, 0 }, + { "bdquo", 8222, 0 }, + { "beta", 946, 0 }, + { "breve", 728, 0 }, + { "brvbar", 166, 0 }, + { "bull", 8226, 0 }, + { "cacute", 263, 0 }, + { "cap", 8745, 0 }, + { "caron", 711, 0 }, + { "ccaron", 269, 0 }, + { "ccedil", 231, 0 }, + { "cedil", 184, 0 }, + { "cent", 162, 0 }, + { "chi", 967, 0 }, + { "circ", 710, 0 }, + { "clubs", 9827, 0 }, + { "cong", 8773, 0 }, + { "copy", 169, 0 }, + { "crarr", 8629, 0 }, + { "cup", 8746, 0 }, + { "curren", 164, 0 }, + { "dArr", 8659, 0 }, + { "dagger", 8224, 0 }, + { "darr", 8595, 0 }, + { "dblac", 733, 0 }, + { "dcaron", 271, 0 }, + { "deg", 176, 0 }, + { "delta", 948, 0 }, + { "diams", 9830, 0 }, + { "divide", 247, 0 }, + { "dot", 729, 0 }, + { "dstrok", 273, 0 }, + { "eacute", 233, 0 }, + { "ecaron", 283, 0 }, + { "ecirc", 234, 0 }, + { "egrave", 232, 0 }, + { "emdash", 8212, 0 }, + { "empty", 8709, 0 }, + { "endash", 8211, 0 }, + { "eogon", 281, 0 }, + { "epsilon", 949, 0 }, + { "equiv", 8801, 0 }, + { "eta", 951, 0 }, + { "eth", 240, 0 }, + { "euml", 235, 0 }, + { "exist", 8707, 0 }, + { "fnof", 402, 0 }, + { "forall", 8704, 0 }, + { "frac12", 189, 0 }, + { "frac14", 188, 0 }, + { "frac34", 190, 0 }, + { "frasl", 8260, 0 }, + { "gamma", 947, 0 }, + { "ge", 8805, 0 }, + { "gt", '>', 0 }, + { "hArr", 8660, 0 }, + { "harr", 8596, 0 }, + { "hearts", 9829, 0 }, + { "hellip", 8230, 0 }, + { "iacute", 237, 0 }, + { "icirc", 238, 0 }, + { "iexcl", 161, 0 }, + { "igrave", 236, 0 }, + { "image", 8465, 0 }, + { "infin", 8734, 0 }, + { "int", 8747, 0 }, + { "iota", 953, 0 }, + { "iquest", 191, 0 }, + { "isin", 8712, 0 }, + { "iuml", 239, 0 }, + { "kappa", 954, 0 }, + { "lArr", 8656, 0 }, + { "lacute", 314, 0 }, + { "lambda", 955, 0 }, + { "lang", 9001, 0 }, + { "laquo", 171, 0 }, + { "larr", 8592, 0 }, + { "lcaron", 318, 0 }, + { "lceil", 8968, 0 }, + { "ldq", 8220, 0 }, + { "ldquo", 8220, 0 }, + { "le", 8804, 0 }, + { "lfloor", 8970, 0 }, + { "lowast", 8727, 0 }, + { "loz", 9674, 0 }, + { "lsaquo", 8249, 0 }, + { "lsq", 8216, 0 }, + { "lsquo", 8216, 0 }, + { "lstrok", 322, 0 }, + { "lt", '<', 0 }, + { "macr", 175, 0 }, + { "mdash", 8212, 0 }, + { "micro", 181, 0 }, + { "middot", 183, 0 }, + { "minus", 8722, 0 }, + { "mu", 956, 0 }, + { "nabla", 8711, 0 }, + { "nacute", 324, 0 }, + { "nbsp", QSPACE, 0 }, + { "ncaron", 328, 0 }, + { "ndash", 8211, 0 }, + { "ne", 8800, 0 }, + { "ni", 8715, 0 }, + { "not", 172, 0 }, + { "notin", 8713, 0 }, + { "nsub", 8836, 0 }, + { "ntilde", 241, 0 }, + { "nu", 957, 0 }, + { "oacute", 243, 0 }, + { "ocirc", 244, 0 }, + { "odblac", 337, 0 }, + { "oelig", 339, 0 }, + { "ogon", 731, 0 }, + { "ograve", 242, 0 }, + { "oline", 8254, 0 }, + { "omega", 969, 0 }, + { "omicron", 959, 0 }, + { "oplus", 8853, 0 }, + { "or", 8744, 0 }, + { "ordf", 170, 0 }, + { "ordm", 186, 0 }, + { "oslash", 248, 0 }, + { "otilde", 245, 0 }, + { "otimes", 8855, 0 }, + { "ouml", 246, 0 }, + { "para", 182, 0 }, + { "part", 8706, 0 }, + { "permil", 8240, 0 }, + { "perp", 8869, 0 }, + { "phi", 966, 0 }, + { "pi", 960, 0 }, + { "piv", 982, 0 }, + { "plusmn", 177, 0 }, + { "pound", 163, 0 }, + { "prime", 8242, 0 }, + { "prod", 8719, 0 }, + { "prop", 8733, 0 }, + { "psi", 968, 0 }, + { "quot", '"', 0 }, + { "rArr", 8658, 0 }, + { "racute", 341, 0 }, + { "radic", 8730, 0 }, + { "rang", 9002, 0 }, + { "raquo", 187, 0 }, + { "rarr", 8594, 0 }, + { "rcaron", 345, 0 }, + { "rceil", 8969, 0 }, + { "rdq", 8221, 0 }, + { "rdquo", 8221, 0 }, + { "real", 8476, 0 }, + { "reg", 174, 0 }, + { "rfloor", 8971, 0 }, + { "rho", 961, 0 }, + { "rsaquo", 8250, 0 }, + { "rsq", 8217, 0 }, + { "rsquo", 8217, 0 }, + { "sacute", 347, 0 }, + { "sbquo", 8218, 0 }, + { "scaron", 353, 0 }, + { "scedil", 351, 0 }, + { "sdot", 8901, 0 }, + { "sect", 167, 0 }, + { "shy", 173, 0 }, + { "sigma", 963, 0 }, + { "sigmaf", 962, 0 }, + { "sim", 8764, 0 }, + { "spades", 9824, 0 }, + { "sub", 8834, 0 }, + { "sube", 8838, 0 }, + { "sum", 8721, 0 }, + { "sup", 8835, 0 }, + { "sup1", 185, 0 }, + { "sup2", 178, 0 }, + { "sup3", 179, 0 }, + { "supe", 8839, 0 }, + { "szlig", 223, 0 }, + { "tau", 964, 0 }, + { "tcaron", 357, 0 }, + { "tcedil", 355, 0 }, + { "there4", 8756, 0 }, + { "theta", 952, 0 }, + { "thetasym", 977, 0 }, + { "thorn", 254, 0 }, + { "thorn", 254, 0 }, + { "tilde", 732, 0 }, + { "times", 215, 0 }, + { "trade", 8482, 0 }, + { "uArr", 8657, 0 }, + { "uacute", 250, 0 }, + { "uarr", 8593, 0 }, + { "ucirc", 251, 0 }, + { "udblac", 369, 0 }, + { "ugrave", 249, 0 }, + { "uml", 168, 0 }, + { "upsih", 978, 0 }, + { "upsilon", 965, 0 }, + { "uring", 367, 0 }, + { "uuml", 252, 0 }, + { "weierp", 8472, 0 }, + { "xi", 958, 0 }, + { "yacute", 253, 0 }, + { "yen", 165, 0 }, + { "yuml", 255, 0 }, + { "zacute", 378, 0 }, + { "zcaron", 382, 0 }, + { "zdot", 380, 0 }, + { "zeta", 950, 0 } +}; + + +/* ------------------------------------------------------------------------ */ +/* + * turn on CAPS mode for a stream + */ +static void outcaps_stream(out_stream_info *stream) +{ + /* turn on CAPS mode */ + stream->capsflag = TRUE; + + /* turn off NOCAPS and ALLCAPS mode */ + stream->nocapsflag = FALSE; + stream->allcapsflag = FALSE; +} + +/* + * turn on NOCAPS mode for a stream + */ +static void outnocaps_stream(out_stream_info *stream) +{ + /* turn on NOCAPS mode */ + stream->nocapsflag = TRUE; + + /* turn off CAPS and ALLCAPS mode */ + stream->capsflag = FALSE; + stream->allcapsflag = FALSE; +} + +/* + * turn on or off ALLCAPS mode for a stream + */ +static void outallcaps_stream(out_stream_info *stream, int all_caps) +{ + /* set the ALLCAPS flag */ + stream->allcapsflag = all_caps; + + /* clear the CAPS and NOCAPS flags */ + stream->capsflag = FALSE; + stream->nocapsflag = FALSE; +} + +/* ------------------------------------------------------------------------ */ +/* + * write a string to a stream + */ +static void stream_print(out_stream_info *stream, char *str) +{ + /* call the stream's do_print method */ + (*stream->do_print)(stream, str); +} + +/* + * Write out a line + */ +static void t_outline(out_stream_info *stream, int nl, + const char *txt, const int *attr) +{ + extern int scrquiet; + + /* + * Check the "script quiet" mode - this indicates that we're reading + * a script and not echoing output to the display. If this mode is + * on, and we're writing to the display, suppress this write. If + * the mode is off, or we're writing to another stream (such as the + * log file), show the output as normal. + */ + if (!scrquiet || stream != &G_std_disp) + { + size_t i; + char buf[MAXWIDTH]; + char *dst; + + /* + * Check to see if we've reached the end of the screen, and if + * so run the MORE prompt. Note that we don't make this check + * at all if USE_MORE is undefined, since this means that the OS + * layer code is taking responsibility for pagination issues. + * We also don't display a MORE prompt when reading from a + * script file. + * + * Note that we suppress the MORE prompt if nl == 0, since this + * is used to flush a partial line of text without starting a + * new line (for example, when displaying a prompt where the + * input will appear on the same line following the prompt). + * + * Skip the MORE prompt if this stream doesn't use it. + */ + if (stream->use_more_mode + && scrfp == 0 + && G_os_moremode + && nl != 0 && nl != 4 + && stream->linecnt++ >= G_os_pagelength) + { + /* display the MORE prompt */ + out_more_prompt(); + } + + /* + * Display the text. Run through the text in pieces; each time the + * attributes change, set attributes at the osifc level. + */ + for (i = 0, dst = buf ; txt[i] != '\0' ; ++i) + { + /* if the attribute is changing, notify osifc */ + if (attr != 0 && attr[i] != stream->os_attr) + { + /* flush the preceding text */ + if (dst != buf) + { + *dst = '\0'; + stream_print(stream, buf); + } + + /* set the new attribute */ + os_set_text_attr(attr[i]); + + /* remember this as the last OS attribute */ + stream->os_attr = attr[i]; + + /* start with a fresh buffer */ + dst = buf; + } + + /* buffer this character */ + *dst++ = txt[i]; + } + + /* flush the last chunk of text */ + if (dst != buf) + { + *dst = '\0'; + stream_print(stream, buf); + } + } +} + +/* ------------------------------------------------------------------------ */ +/* + * Flush the current line to the display. The 'nl' argument specifies + * what kind of flushing to do: + * + * 0: flush the current line but do not start a new line; more text will + * follow on the current line. This is used, for example, to flush text + * after displaying a prompt and before waiting for user input. + * + * 1: flush the line and start a new line. + * + * 2: flush the line as though starting a new line, but don't add an + * actual newline character to the output, since the underlying OS + * display code will handle this. Instead, add a space after the line. + * (This differs from mode 0 in that mode 0 shouldn't add anything at + * all after the line.) + * + * 3: "preview" mode. Flush the line, but do not start a new line, and + * retain the current text in the buffer. This is used for systems that + * handle the line wrapping in the underlying system code to flush a + * partially filled line that will need to be flushed again later. + * + * 4: same as mode 0, but used for internal buffer flushes only. Do not + * involve the underlying OS layer in this type of flush - simply flush + * our buffers with no separation. + */ + +/* flush a given output stream */ +static void outflushn_stream(out_stream_info *stream, int nl) +{ + int i; + + /* null-terminate the current output line buffer */ + stream->linebuf[stream->linepos] = '\0'; + + /* note the position of the last character to display */ + i = stream->linepos - 1; + + /* if we're adding anything, remove trailing spaces */ + if (nl != 0 && nl != 4) + { + /* look for last non-space character */ + for ( ; i >= 0 && outissp(stream->linebuf[i]) ; --i) ; + } + + /* check the output mode */ + if (nl == 3) + { + /* + * this is the special "preview" mode -- only display the part + * that we haven't already previewed for this same line + */ + if (i + 1 > stream->preview) + { + /* write out the line */ + t_outline(stream, 0, &stream->linebuf[stream->preview], + &stream->attrbuf[stream->preview]); + + /* skip past the part we wrote */ + stream->preview += strlen(&stream->linebuf[stream->preview]); + } + } + else + { + char *suffix = nullptr; /* extra text to add after the flushed text */ + int countnl = 0; /* true if line counts for [more] paging */ + + /* null-terminate the buffer at the current position */ + stream->linebuf[++i] = '\0'; + + /* check the mode */ + switch(nl) + { + case 0: + case 3: + case 4: + /* no newline - just flush out what we have with no suffix */ + suffix = 0; + break; + + case 1: + /* + * Add a newline. If there's nothing in the current line, + * or we just wrote out a newline, do not add an extra + * newline. Keep all newlines in PRE mode. + */ + if (stream->linecol != 0 || !stream->just_did_nl + || stream->html_pre_level != 0) + { + /* add a newline after the text */ + suffix = "\n"; + + /* count the line in the page size */ + countnl = 1; + } + else + { + /* don't add a newline */ + suffix = 0; + } + break; + + case 2: + /* + * we're going to depend on the underlying OS output layer + * to do line breaking, so don't add a newline, but do add a + * space, so that the underlying OS layer knows we have a + * word break here + */ + suffix = " "; + break; + } + + /* + * display the line, as long as we have something buffered to + * display; even if we don't, display it if our column is + * non-zero and we didn't just do a newline, since this must + * mean that we've flushed a partial line and are just now doing + * the newline + */ + if (stream->linebuf[stream->preview] != '\0' + || (stream->linecol != 0 && !stream->just_did_nl) + || stream->html_pre_level > 0) + { + /* write it out */ + t_outline(stream, countnl, &stream->linebuf[stream->preview], + &stream->attrbuf[stream->preview]); + + /* write the suffix, if any */ + if (suffix != 0) + t_outline(stream, 0, suffix, 0); + } + + /* generate an HTML line break if necessary */ + if (nl == 1 && stream->html_mode && stream->html_target) + t_outline(stream, 0, "<BR HEIGHT=0>", 0); + + if (nl == 0) + { + /* we're not displaying a newline, so flush what we have */ + os_flush(); + } + else + { + /* we displayed a newline, so reset the column position */ + stream->linecol = 0; + } + + /* reset the line output buffer position */ + stream->linepos = stream->preview = 0; + + /* + * If we just output a newline, note it. If we didn't just + * output a newline, but we did write out anything else, note + * that we're no longer at the start of a line on the underlying + * output device. + */ + if (nl == 1) + stream->just_did_nl = TRUE; + else if (stream->linebuf[stream->preview] != '\0') + stream->just_did_nl = FALSE; + } + + /* + * If the osifc-level attributes don't match the current attributes, + * bring the osifc layer up to date. This is necessary in cases where + * we set attributes immediately before asking for input - we + * essentially need to flush the attributes without flushing any text. + */ + if (stream->cur_attr != stream->os_attr) + { + /* set the osifc attributes */ + os_set_text_attr(stream->cur_attr); + + /* remember the new attributes as the current osifc attributes */ + stream->os_attr = stream->cur_attr; + } +} + +/* ------------------------------------------------------------------------ */ +/* + * Determine if we're showing output. Returns true if output should be + * displayed, false if it should be suppressed. We'll note the output + * for hidden display accounting as needed. + */ +static int out_is_hidden() +{ + /* check the output flag */ + if (!outflag) + { + /* trace the hidden output if desired */ + if (dbghid && !hidout) + trchid(); + + /* note the hidden output */ + hidout = 1; + + /* + * unless we're showing hidden text in the debugger, we're + * suppressing output, so return true + */ + if (!dbghid) + return TRUE; + } + + /* we're not suppressing output */ + return FALSE; +} + +/* ------------------------------------------------------------------------ */ +/* + * Display a blank line to the given stream + */ +static void outblank_stream(out_stream_info *stream) +{ + /* flush the stream */ + outflushn_stream(stream, 1); + + /* if generating for an HTML display target, add an HTML line break */ + if (stream->html_mode && stream->html_target) + outstring_stream(stream, "<BR>"); + + /* write out the newline */ + t_outline(stream, 1, "\n", 0); +} + +/* ------------------------------------------------------------------------ */ +/* + * Generate a tab for a "\t" sequence in the game text. + * + * Standard (non-HTML) version: we'll generate enough spaces to take us + * to the next tab stop. + * + * HTML version: if we're in native HTML mode, we'll just generate a + * <TAB MULTIPLE=4>; if we're not in HTML mode, we'll generate a hard + * tab character, which the HTML formatter will interpret as a <TAB + * MULTIPLE=4>. + */ +static void outtab_stream(out_stream_info *stream) +{ + /* check to see what the underlying system is expecting */ + if (stream->html_target) + { + /* the underlying system is HTML - check for HTML mode */ + if (stream->html_mode) + { + /* we're in HTML mode, so use the HTML <TAB> tag */ + outstring_stream(stream, "<TAB MULTIPLE=4>"); + } + else + { + /* we're not in HTML mode, so generate a hard tab character */ + outchar_noxlat_stream(stream, QTAB); + } + } + else + { + int maxcol; + + /* + * We're not in HTML mode - expand the tab with spaces. Figure + * the maximum column: if we're doing our own line wrapping, never + * go beyond the actual display width. + */ + maxcol = (stream->os_line_wrap ? OS_MAXWIDTH : G_os_linewidth); + + /* add the spaces */ + do + { + stream->attrbuf[stream->linepos] = stream->cur_attr; + stream->linebuf[stream->linepos++] = ' '; + ++(stream->linecol); + } while (((stream->linecol + 1) & 3) != 0 + && stream->linecol < maxcol); + } +} + + +/* ------------------------------------------------------------------------ */ +/* + * Flush a line + */ +static void out_flushline(out_stream_info *stream, int padding) +{ + /* + * check to see if we're using the underlying display layer's line + * wrapping + */ + if (stream->os_line_wrap) + { + /* + * In the HTML version, we don't need the normal *MORE* + * processing, since the HTML layer will handle that. + * Furthermore, we don't need to provide actual newline breaks + * -- that happens after the HTML is parsed, so we don't have + * enough information here to figure out actual line breaks. + * So, we'll just flush out our buffer whenever it fills up, and + * suppress newlines. + * + * Similarly, if we have OS-level MORE processing, don't try to + * figure out where the line breaks go -- just flush our buffer + * without a trailing newline whenever the buffer is full, and + * let the OS layer worry about formatting lines and paragraphs. + * + * If we're using padding, use mode 2. If we don't want padding + * (which is the case if we completely fill up the buffer + * without finding any word breaks), write out in mode 0, which + * just flushes the buffer exactly like it is. + */ + outflushn_stream(stream, padding ? 2 : 4); + } + else + { + /* + * Normal mode - we process the *MORE* prompt ourselves, and we + * are responsible for figuring out where the actual line breaks + * go. Use outflush() to generate an actual newline whenever we + * flush out our buffer. + */ + outflushn_stream(stream, 1); + } +} + + +/* ------------------------------------------------------------------------ */ +/* + * Write a character to an output stream without translation + */ +static void outchar_noxlat_stream(out_stream_info *stream, char c) +{ + int i; + int qspace; + + /* check for the special quoted space character */ + if (c == QSPACE) + { + /* it's a quoted space - note it and convert it to a regular space */ + qspace = 1; + c = ' '; + } + else if (c == QTAB) + { + /* it's a hard tab - convert it to an ordinary tab */ + c = '\t'; + qspace = 0; + } + else + { + /* translate any whitespace character to a regular space character */ + if (outissp(c)) + c = ' '; + + /* it's not a quoted space */ + qspace = 0; + } + + /* check for the caps/nocaps flags */ + if ((stream->capsflag || stream->allcapsflag) && outisal(c)) + { + /* capsflag is set, so capitalize this character */ + if (outislo(c)) + c = toupper(c); + + /* okay, we've capitalized something; clear flag */ + stream->capsflag = 0; + } + else if (stream->nocapsflag && outisal(c)) + { + /* nocapsflag is set, so minisculize this character */ + if (outisup(c)) + c = tolower(c); + + /* clear the flag now that we've done the job */ + stream->nocapsflag = 0; + } + + /* if in capture mode, simply capture the character */ + if (stream->capturing) + { + uchar *p; + + /* if we have a valid capture object, copy to it */ + if (stream->capture_obj != MCMONINV) + { + /* lock the object holding the captured text */ + p = mcmlck(stream->capture_ctx, stream->capture_obj); + + /* make sure the capture object is big enough */ + if (mcmobjsiz(stream->capture_ctx, stream->capture_obj) + <= stream->capture_ofs) + { + /* expand the object by another 256 bytes */ + p = mcmrealo(stream->capture_ctx, stream->capture_obj, + (ushort)(stream->capture_ofs + 256)); + } + + /* add this character */ + *(p + stream->capture_ofs++) = c; + + /* unlock the capture object */ + mcmtch(stream->capture_ctx, stream->capture_obj); + mcmunlck(stream->capture_ctx, stream->capture_obj); + } + + /* + * we're done - we don't want to actually display the character + * while capturing + */ + return; + } + + /* add the character to out output buffer, flushing as needed */ + if (stream->linecol + 1 < G_os_linewidth) + { + /* + * there's room for this character, so add it to the buffer + */ + + /* ignore non-quoted space at start of line outside of PRE */ + if (outissp(c) && c != '\t' && stream->linecol == 0 && !qspace + && stream->html_pre_level == 0) + return; + + /* is this a non-quoted space not at the start of the line? */ + if (outissp(c) && c != '\t' && stream->linecol != 0 && !qspace + && stream->html_pre_level == 0) + { + int pos1 = stream->linepos - 1; + char p = stream->linebuf[pos1]; /* check previous character */ + + /* ignore repeated spaces - collapse into a single space */ + if (outissp(p)) + return; + + /* + * Certain punctuation requires a double space: a period, a + * question mark, an exclamation mark, or a colon; or any of + * these characters followed by any number of single and/or + * double quotes. First, scan back to before any quotes, if + * are on one now, then check the preceding character; if + * it's one of the punctuation marks requiring a double + * space, add this space a second time. (In addition to + * scanning back past quotes, scan past parentheses, + * brackets, and braces.) Don't double the spacing if we're + * not in the normal doublespace mode; some people may + * prefer single spacing after punctuation, so we make this + * a run-time option. + */ + if (doublespace) + { + /* find the previous relevant punctuation character */ + while (pos1 && + (p == '"' || p == '\'' || p == ')' || p == ']' + || p == '}')) + { + p = stream->linebuf[--pos1]; + } + if ( p == '.' || p == '?' || p == '!' || p == ':' ) + { + /* a double-space is required after this character */ + stream->attrbuf[stream->linepos] = stream->cur_attr; + stream->linebuf[stream->linepos++] = c; + ++(stream->linecol); + } + } + } + + /* add this character to the buffer */ + stream->attrbuf[stream->linepos] = stream->cur_attr; + stream->linebuf[stream->linepos++] = c; + + /* advance the output column position */ + ++(stream->linecol); + return; + } + + /* + * The line would overflow if this character were added. Find the + * most recent word break, and output the line up to the previous + * word. Note that if we're trying to output a space, we'll just + * add it to the line buffer. If the last character of the line + * buffer is already a space, we won't do anything right now. + */ + if (outissp(c) && c != '\t' && !qspace) + { + /* this is a space, so we're at a word break */ + if (stream->linebuf[stream->linepos - 1] != ' ') + { + stream->attrbuf[stream->linepos] = stream->cur_attr; + stream->linebuf[stream->linepos++] = ' '; + } + return; + } + + /* + * Find the most recent word break: look for a space or dash, starting + * at the end of the line. + * + * If we're about to write a hyphen, we want to skip all contiguous + * hyphens, because we want to keep them together as a single + * punctuation mark; then keep going in the normal manner, which will + * keep the hyphens plus the word they're attached to together as a + * single unit. If spaces precede the sequence of hyphens, include + * the prior word as well. + */ + i = stream->linepos - 1; + if (c == '-') + { + /* skip any contiguous hyphens at the end of the line */ + for ( ; i >= 0 && stream->linebuf[i] == '-' ; --i) ; + + /* skip any spaces preceding the sequence of hyphens */ + for ( ; i >= 0 && outissp(stream->linebuf[i]) ; --i) ; + } + + /* + * Now find the preceding space. If we're doing our own wrapping + * (i.e., we're not using OS line wrapping), then look for the + * nearest hyphen as well. + */ + for ( ; i >= 0 && !outissp(stream->linebuf[i]) + && !(!stream->os_line_wrap && stream->linebuf[i] == '-') ; --i) ; + + /* check to see if we found a good place to break */ + if (i < 0) + { + /* + * we didn't find any good place to break - flush the entire + * line as-is, breaking arbitrarily in the middle of a word + */ + out_flushline(stream, FALSE); + + /* + * we've completely cleared out the line buffer, so reset all of + * the line buffer counters + */ + stream->linepos = 0; + stream->linecol = 0; + stream->linebuf[0] = '\0'; + } + else + { + char brkchar; + char tmpbuf[MAXWIDTH]; + int tmpattr[MAXWIDTH]; + size_t tmpcnt; + + /* remember the word-break character */ + brkchar = stream->linebuf[i]; + + /* null-terminate the line buffer */ + stream->linebuf[stream->linepos] = '\0'; + + /* the next line starts after the break - save a copy */ + tmpcnt = strlen(&stream->linebuf[i+1]); + memcpy(tmpbuf, &stream->linebuf[i+1], tmpcnt + 1); + memcpy(tmpattr, &stream->attrbuf[i+1], tmpcnt * sizeof(tmpattr[0])); + + /* + * terminate the buffer at the space or after the hyphen, + * depending on where we broke + */ + if (outissp(brkchar)) + stream->linebuf[i] = '\0'; + else + stream->linebuf[i+1] = '\0'; + + /* write out everything up to the word break */ + out_flushline(stream, TRUE); + + /* copy the next line into line buffer */ + memcpy(stream->linebuf, tmpbuf, tmpcnt + 1); + memcpy(stream->attrbuf, tmpattr, tmpcnt * sizeof(tmpattr[0])); + stream->linepos = tmpcnt; + + /* + * figure what column we're now in - count all of the printable + * characters in the new line + */ + for (stream->linecol = 0, i = 0 ; i < stream->linepos ; ++i) + { + /* if it's printable, count it */ + if (((unsigned char)stream->linebuf[i]) >= 26) + ++(stream->linecol); + } + } + + /* add the new character to buffer */ + stream->attrbuf[stream->linepos] = stream->cur_attr; + stream->linebuf[stream->linepos++] = c; + + /* advance the column counter */ + ++(stream->linecol); +} + +/* ------------------------------------------------------------------------ */ +/* + * Write out a character, translating to the local system character set + * from the game's internal character set. + */ +static void outchar_stream(out_stream_info *stream, char c) +{ + outchar_noxlat_stream(stream, cmap_i2n(c)); +} + +/* + * write out a string, translating to the local system character set + */ +static void outstring_stream(out_stream_info *stream, char *s) +{ + /* write out each character in the string */ + for ( ; *s ; ++s) + outchar_stream(stream, *s); +} + +/* + * write out a string without translation + */ +static void outstring_noxlat_stream(out_stream_info *stream, char *s) +{ + for ( ; *s ; ++s) + outchar_noxlat_stream(stream, *s); +} + + +/* ------------------------------------------------------------------------ */ +/* + * Write out an HTML character value, translating to the local character + * set. + */ +static void outchar_html_stream(out_stream_info *stream, + unsigned int htmlchar) +{ + amp_tbl_t *ampptr; + + /* + * search for a mapping entry for this entity, in case it's defined + * in an external mapping file + */ + for (ampptr = amp_tbl ; + ampptr < amp_tbl + sizeof(amp_tbl)/sizeof(amp_tbl[0]) ; ++ampptr) + { + /* if this is the one, stop looking */ + if (ampptr->html_cval == htmlchar) + break; + } + + /* + * If we found a mapping table entry, and the entry has an expansion + * from the external character mapping table file, use the external + * expansion; otherwise, use the default expansion. + */ + if (ampptr >= amp_tbl + sizeof(amp_tbl)/sizeof(amp_tbl[0]) + || ampptr->expan == 0) + { + char xlat_buf[50]; + + /* + * there's no external mapping table file expansion -- use the + * default OS mapping routine + */ + os_xlat_html4(htmlchar, xlat_buf, sizeof(xlat_buf)); + outstring_noxlat_stream(stream, xlat_buf); + } + else + { + /* + * use the explicit mapping from the mapping table file + */ + outstring_noxlat_stream(stream, ampptr->expan); + } +} + + +/* ------------------------------------------------------------------------ */ +/* + * Enter a recursion level. Returns TRUE if the caller should proceed + * with the operation, FALSE if not. + * + * If we're making a recursive call, thereby re-entering the formatter, + * and this stream is not the same as the enclosing stream, we want to + * ignore this call and suppress any output to this stream, so we'll + * return FALSE. + */ +static int out_push_stream(out_stream_info *stream) +{ + /* + * if we're already in the formatter, and the new stream doesn't + * match the enclosing recursion level's stream, tell the caller to + * abort the operation + */ + if (G_recurse != 0 && G_cur_stream != stream) + return FALSE; + + /* note the active stream */ + G_cur_stream = stream; + + /* count the entry */ + ++G_recurse; + + /* tell the caller to proceed */ + return TRUE; +} + +/* + * Leave a recursion level + */ +static void out_pop_stream() +{ + /* count the exit */ + --G_recurse; +} + +/* ------------------------------------------------------------------------ */ +/* + * nextout() returns the next character in a string, and updates the + * string pointer and remaining length. Returns zero if no more + * characters are available in the string. + */ +/* static char nextout(char **s, uint *len); */ +#define nextout(s, len) ((char)(*(len) == 0 ? 0 : (--(*(len)), *((*(s))++)))) + + +/* ------------------------------------------------------------------------ */ +/* + * Get the next character, writing the previous character to the given + * output stream if it's not null. + */ +static char nextout_copy(char **s, size_t *slen, + char prv, out_stream_info *stream) +{ + /* if there's a stream, write the previous character to the stream */ + if (stream != 0) + outchar_stream(stream, prv); + + /* return the next character */ + return nextout(s, slen); +} + +/* ------------------------------------------------------------------------ */ +/* + * Read an HTML tag, for our primitive mini-parser. If 'stream' is not + * null, we'll copy each character we read to the output stream. Returns + * the next character after the tag name. + */ +static char read_tag(char *dst, size_t dstlen, int *is_end_tag, + char **s, size_t *slen, out_stream_info *stream) +{ + char c; + + /* skip the opening '<' */ + c = nextout_copy(s, slen, '<', stream); + + /* skip spaces */ + while (outissp(c)) + c = nextout_copy(s, slen, c, stream); + + /* note if this is a closing tag */ + if (c == '/' || c == '\\') + { + /* it's an end tag - note it and skip the slash */ + *is_end_tag = TRUE; + c = nextout_copy(s, slen, c, stream); + + /* skip yet more spaces */ + while (outissp(c)) + c = nextout_copy(s, slen, c, stream); + } + else + *is_end_tag = FALSE; + + /* + * find the end of the tag name - the tag continues to the next space, + * '>', or end of line + */ + for ( ; c != '\0' && !outissp(c) && c != '>' ; + c = nextout_copy(s, slen, c, stream)) + { + /* add this to the tag buffer if it fits */ + if (dstlen > 1) + { + *dst++ = c; + --dstlen; + } + } + + /* null-terminate the tag name */ + if (dstlen > 0) + *dst = '\0'; + + /* return the next character */ + return c; +} + + +/* ------------------------------------------------------------------------ */ +/* + * display a string of a given length to a given stream + */ +static int outformatlen_stream(out_stream_info *stream, + char *s, size_t slen) +{ + char c; + int done = 0; + char fmsbuf[40]; /* space for constructing translation string */ + uint fmslen; + char *f = 0; + char *f1; + int infmt = 0; + + /* + * This routine can recurse because of format strings ("%xxx%" + * sequences). When we recurse, we want to ensure that the + * recursion is directed to the original stream only. So, note the + * current stream statically in case we re-enter the formatter. + */ + if (!out_push_stream(stream)) + return 0; + + /* get the first character */ + c = nextout(&s, &slen); + + /* if we have anything to show, show it */ + while (c != '\0') + { + /* check if we're collecting translation string */ + if (infmt) + { + /* + * if the string is too long for our buffer, or we've come + * across a backslash (illegal in a format string), or we've + * come across an HTML-significant character ('&' or '<') in + * HTML mode, we must have a stray percent sign; dump the + * whole string so far and act as though we have no format + * string + */ + if (c == '\\' + || f == &fmsbuf[sizeof(fmsbuf)] + || (stream->html_mode && (c == '<' || c == '&'))) + { + outchar_stream(stream, '%'); + for (f1 = fmsbuf ; f1 < f ; ++f1) + outchar_stream(stream, *f1); + infmt = 0; + + /* process this character again */ + continue; + } + else if (c == '%' && f == fmsbuf) /* double percent sign? */ + { + outchar_stream(stream, '%'); /* send out a single '%' */ + infmt = 0; /* no longer processing translation string */ + } + else if (c == '%') /* found end of string? translate it if so */ + { + uchar *fms; + int initcap = FALSE; + int allcaps = FALSE; + char fmsbuf_srch[sizeof(fmsbuf)]; + + /* null-terminate the string */ + *f = '\0'; + + /* check for an init cap */ + if (outisup(fmsbuf[0])) + { + /* + * note the initial capital, so that we follow the + * original capitalization in the substituted string + */ + initcap = TRUE; + + /* + * if the second letter is capitalized as well, + * capitalize the entire substituted string + */ + if (fmsbuf[1] != '\0' && outisup(fmsbuf[1])) + { + /* use all caps */ + allcaps = TRUE; + } + } + + /* convert the entire string to lower case for searching */ + strcpy(fmsbuf_srch, fmsbuf); + os_strlwr(fmsbuf_srch); + + /* find the string in the format string table */ + fmslen = strlen(fmsbuf_srch); + for (fms = fmsbase ; fms < fmstop ; ) + { + uint propnum; + uint len; + + /* get the information on this entry */ + propnum = osrp2(fms); + len = osrp2(fms + 2) - 2; + + /* check for a match */ + if (len == fmslen && + !memcmp(fms + 4, fmsbuf_srch, (size_t)len)) + { + int old_all_caps; + + /* note the current ALLCAPS mode */ + old_all_caps = stream->allcapsflag; + + /* + * we have a match - set the appropriate + * capitalization mode + */ + if (allcaps) + outallcaps_stream(stream, TRUE); + else if (initcap) + outcaps_stream(stream); + + /* + * evaluate the associated property to generate + * the substitution text + */ + runppr(runctx, cmdActor, (prpnum)propnum, 0); + + /* turn off ALLCAPS mode */ + outallcaps_stream(stream, old_all_caps); + + /* no need to look any further */ + break; + } + + /* move on to next formatstring if not yet found */ + fms += len + 4; + } + + /* if we can't find it, dump the format string as-is */ + if (fms == fmstop) + { + outchar_stream(stream, '%'); + for (f1 = fmsbuf ; f1 < f ; ++f1) + outchar_stream(stream, *f1); + outchar_stream(stream, '%'); + } + + /* no longer reading format string */ + infmt = 0; + } + else + { + /* copy this character of the format string */ + *f++ = c; + } + + /* move on to the next character and continue scanning */ + c = nextout(&s, &slen); + continue; + } + + /* + * If we're parsing HTML here, and we're inside a tag, skip + * characters until we reach the end of the tag. + */ + if (stream->html_mode_flag != HTML_MODE_NORMAL) + { + switch(stream->html_mode_flag) + { + case HTML_MODE_TAG: + /* + * keep skipping up to the closing '>', but note when we + * enter any quoted section + */ + switch(c) + { + case '>': + /* we've reached the end of the tag */ + stream->html_mode_flag = HTML_MODE_NORMAL; + + /* if we have a deferred <BR>, process it now */ + switch(stream->html_defer_br) + { + case HTML_DEFER_BR_NONE: + /* no deferred <BR> */ + break; + + case HTML_DEFER_BR_FLUSH: + outflushn_stream(stream, 1); + break; + + case HTML_DEFER_BR_BLANK: + outblank_stream(stream); + break; + } + + /* no more deferred <BR> pending */ + stream->html_defer_br = HTML_DEFER_BR_NONE; + + /* no more ALT attribute allowed */ + stream->html_allow_alt = FALSE; + break; + + case '"': + /* enter a double-quoted string */ + stream->html_mode_flag = HTML_MODE_DQUOTE; + break; + + case '\'': + /* enter a single-quoted string */ + stream->html_mode_flag = HTML_MODE_SQUOTE; + break; + + default: + /* if it's alphabetic, note the attribute name */ + if (outisal(c)) + { + char attrname[128]; + char attrval[256]; + char *dst; + + /* gather up the attribute name */ + for (dst = attrname ; + dst + 1 < attrname + sizeof(attrname) ; ) + { + /* store this character */ + *dst++ = c; + + /* get the next character */ + c = nextout(&s, &slen); + + /* if it's not alphanumeric, stop scanning */ + if (!outisal(c) && !outisdg(c)) + break; + } + + /* null-terminate the result */ + *dst++ = '\0'; + + /* gather the value if present */ + if (c == '=') + { + char qu; + + /* skip the '=' */ + c = nextout(&s, &slen); + + /* if we have a quote, so note */ + if (c == '"' || c == '\'') + { + /* remember the quote */ + qu = c; + + /* skip it */ + c = nextout(&s, &slen); + } + else + { + /* no quote */ + qu = 0; + } + + /* read the value */ + for (dst = attrval ; + dst + 1 < attrval + sizeof(attrval) ; ) + { + /* store this character */ + *dst++ = c; + + /* read the next one */ + c = nextout(&s, &slen); + if (c == '\0') + { + /* + * we've reached the end of the + * string, and we're still inside + * this attribute - abandon the + * attribute but note that we're + * inside a quoted string if + * necessary + */ + if (qu == '"') + stream->html_mode_flag = + HTML_MODE_DQUOTE; + else if (qu == '\'') + stream->html_mode_flag = + HTML_MODE_SQUOTE; + else + stream->html_mode_flag + = HTML_MODE_TAG; + + /* stop scanning the string */ + break; + } + + /* + * if we're looking for a quote, check + * for the closing quote; otherwise, + * check for alphanumerics + */ + if (qu != 0) + { + /* if this is our quote, stop scanning */ + if (c == qu) + break; + } + else + { + /* if it's non-alphanumeric, we're done */ + if (!outisal(c) && !outisdg(c)) + break; + } + } + + /* skip the closing quote, if necessary */ + if (qu != 0 && c == qu) + c = nextout(&s, &slen); + + /* null-terminate the value string */ + *dst = '\0'; + } + else + { + /* no value */ + attrval[0] = '\0'; + } + + /* + * see if we recognize it, and it's meaningful + * in the context of the current tag + */ + if (!scumm_stricmp(attrname, "height") + && stream->html_defer_br != HTML_DEFER_BR_NONE) + { + int ht; + + /* + * If the height is zero, always treat this + * as a non-blanking flush. If it's one, + * treat it as we originally planned to. If + * it's greater than one, add n blank lines. + */ + ht = atoi(attrval); + if (ht == 0) + { + /* always use non-blanking flush */ + stream->html_defer_br = HTML_DEFER_BR_FLUSH; + } + else if (ht == 1) + { + /* keep original setting */ + } + else + { + for ( ; ht > 0 ; --ht) + outblank_stream(stream); + } + } + else if (!scumm_stricmp(attrname, "alt") + && !stream->html_in_ignore + && stream->html_allow_alt) + { + /* write out the ALT string */ + outstring_stream(stream, attrval); + } + + /* + * since we already read the next character, + * simply loop back immediately + */ + continue; + } + break; + } + break; + + case HTML_MODE_DQUOTE: + /* if we've reached the closing quote, return to tag state */ + if (c == '"') + stream->html_mode_flag = HTML_MODE_TAG; + break; + + case HTML_MODE_SQUOTE: + /* if we've reached the closing quote, return to tag state */ + if (c == '\'') + stream->html_mode_flag = HTML_MODE_TAG; + break; + } + + /* + * move on to the next character, and start over with the + * new character + */ + c = nextout(&s, &slen); + continue; + } + + /* + * If we're in a title, and this isn't the start of a new tag, + * skip the character - we suppress all regular text output + * inside a <TITLE> ... </TITLE> sequence. + */ + if (stream->html_in_ignore && c != '<') + { + /* check for entities */ + char cbuf[50]; + if (c == '&') + { + /* translate the entity */ + c = out_parse_entity(cbuf, sizeof(cbuf), &s, &slen); + } + else + { + /* it's an ordinary character - copy it out literally */ + cbuf[0] = c; + cbuf[1] = '\0'; + + /* get the next character */ + c = nextout(&s, &slen); + } + + /* + * if we're gathering a title, and there's room in the title + * buffer for more (always leaving room for a null + * terminator), add this to the title buffer + */ + if (stream->html_in_title) + { + char *cbp; + for (cbp = cbuf ; *cbp != '\0' ; ++cbp) + { + /* if there's room, add it */ + if (stream->html_title_ptr + 1 < + stream->html_title_buf + + sizeof(stream->html_title_buf)) + *stream->html_title_ptr++ = *cbp; + } + } + + /* don't display anything in an ignore section */ + continue; + } + + if ( c == '%' ) /* translation string? */ + { + infmt = 1; + f = fmsbuf; + } + else if ( c == '\\' ) /* special escape code? */ + { + c = nextout(&s, &slen); + + if (stream->capturing && c != '^' && c != 'v' && c != '\0') + { + outchar_stream(stream, '\\'); + outchar_stream(stream, c); + + /* keep the \- and also put out the next two chars */ + if (c == '-') + { + outchar_stream(stream, nextout(&s, &slen)); + outchar_stream(stream, nextout(&s, &slen)); + } + } + else + { + switch(c) + { + case 'H': /* HTML mode entry */ + /* turn on HTML mode in the renderer */ + switch(c = nextout(&s, &slen)) + { + case '-': + /* if we have an HTML target, notify it */ + if (stream->html_target) + { + /* flush its stream */ + outflushn_stream(stream, 0); + + /* tell the OS layer to switch to normal mode */ + out_end_html(stream); + } + + /* switch to normal mode */ + stream->html_mode = FALSE; + break; + + case '+': + default: + /* if we have an HTML target, notify it */ + if (stream->html_target) + { + /* flush the underlying stream */ + outflushn_stream(stream, 0); + + /* tell the OS layer to switch to HTML mode */ + out_start_html(stream); + } + + /* switch to HTML mode */ + stream->html_mode = TRUE; + + /* + * if the character wasn't a "+", it's not part + * of the "\H" sequence, so display it normally + */ + if (c != '+' && c != 0) + outchar_stream(stream, c); + break; + } + + /* this sequence doesn't result in any actual output */ + break; + + case 'n': /* newline? */ + outflushn_stream(stream, 1); /* yes, output line */ + break; + + case 't': /* tab? */ + outtab_stream(stream); + break; + + case 'b': /* blank line? */ + outblank_stream(stream); + break; + + case '\0': /* line ends here? */ + done = 1; + break; + + case ' ': /* quoted space */ + if (stream->html_target && stream->html_mode) + { + /* + * we're generating for an HTML target and we're + * in HTML mode - generate the HTML non-breaking + * space + */ + outstring_stream(stream, " "); + } + else + { + /* + * we're not in HTML mode - generate our + * internal quoted space character + */ + outchar_stream(stream, QSPACE); + } + break; + + case '^': /* capitalize next character */ + stream->capsflag = 1; + stream->nocapsflag = 0; + break; + + case 'v': + stream->nocapsflag = 1; + stream->capsflag = 0; + break; + + case '(': + /* generate HTML if in the appropriate mode */ + if (stream->html_mode && stream->html_target) + { + /* send HTML to the renderer */ + outstring_stream(stream, "<B>"); + } + else + { + /* turn on the 'hilite' attribute */ + stream->cur_attr |= OS_ATTR_HILITE; + } + break; + + case ')': + /* generate HTML if in the appropriate mode */ + if (stream->html_mode && stream->html_target) + { + /* send HTML to the renderer */ + outstring_stream(stream, "</B>"); + } + else + { + /* turn off the 'hilite' attribute */ + stream->cur_attr &= ~OS_ATTR_HILITE; + } + break; + + case '-': + outchar_stream(stream, nextout(&s, &slen)); + outchar_stream(stream, nextout(&s, &slen)); + break; + + default: /* just pass invalid escapes as-is */ + outchar_stream(stream, c); + break; + } + } + } + else if (!stream->html_target + && stream->html_mode + && (c == '<' || c == '&')) + { + /* + * We're in HTML mode, but the underlying target does not + * accept HTML sequences. It appears we're at the start of + * an "&" entity or a tag sequence, so parse it, remove it, + * and replace it (if possible) with a text-only equivalent. + */ + if (c == '<') + { + /* read the tag */ + char tagbuf[50]; + int is_end_tag; + c = read_tag(tagbuf, sizeof(tagbuf), &is_end_tag, + &s, &slen, 0); + + /* + * Check to see if we recognize the tag. We only + * recognize a few simple tags that map easily to + * character mode. + */ + if (!scumm_stricmp(tagbuf, "br")) + { + /* + * line break - if there's anything buffered up, + * just flush the current line, otherwise write out + * a blank line + */ + if (stream->html_in_ignore) + /* suppress breaks in ignore mode */; + else if (stream->linepos != 0) + stream->html_defer_br = HTML_DEFER_BR_FLUSH; + else + stream->html_defer_br = HTML_DEFER_BR_BLANK; + } + else if (!scumm_stricmp(tagbuf, "b") + || !scumm_stricmp(tagbuf, "i") + || !scumm_stricmp(tagbuf, "em") + || !scumm_stricmp(tagbuf, "strong")) + { + int attr; + + /* choose the attribute flag */ + switch (tagbuf[0]) + { + case 'b': + case 'B': + attr = OS_ATTR_BOLD; + break; + + case 'i': + case 'I': + attr = OS_ATTR_ITALIC; + break; + + case 'e': + case 'E': + attr = OS_ATTR_EM; + break; + + case 's': + case 'S': + attr = OS_ATTR_STRONG; + break; + } + + /* bold on/off - send out appropriate os-layer code */ + if (stream->html_in_ignore) + { + /* suppress any change in 'ignore' mode */ + } + else if (!is_end_tag) + { + /* turn on the selected attribute */ + stream->cur_attr |= attr; + } + else + { + /* turn off the selected attribute */ + stream->cur_attr &= ~attr; + } + } + else if (!scumm_stricmp(tagbuf, "p")) + { + /* paragraph - send out a blank line */ + if (!stream->html_in_ignore) + outblank_stream(stream); + } + else if (!scumm_stricmp(tagbuf, "tab")) + { + /* tab - send out a \t */ + if (!stream->html_in_ignore) + outtab_stream(stream); + } + else if (!scumm_stricmp(tagbuf, "img") || !scumm_stricmp(tagbuf, "sound")) + { + /* IMG and SOUND - allow ALT attributes */ + stream->html_allow_alt = TRUE; + } + else if (!scumm_stricmp(tagbuf, "hr")) + { + int rem; + + if (!stream->html_in_ignore) + { + /* start a new line */ + outflushn_stream(stream, 1); + + /* write out underscores to the display width */ + for (rem = G_os_linewidth - 1 ; rem > 0 ; ) + { + char dashbuf[100]; + int cur; + + /* do as much as we can on this pass */ + cur = rem; + if ((size_t)cur > sizeof(dashbuf) - 1) + cur = sizeof(dashbuf) - 1; + + /* do a buffer-full of dashes */ + memset(dashbuf, '_', cur); + dashbuf[cur] = '\0'; + outstring_stream(stream, dashbuf); + + /* deduct this from the total */ + rem -= cur; + } + + /* put a blank line after the underscores */ + outblank_stream(stream); + } + } + else if (!scumm_stricmp(tagbuf, "q")) + { + unsigned int htmlchar; + + if (!stream->html_in_ignore) + { + /* if it's an open quote, increment the level */ + if (!is_end_tag) + ++(stream->html_quote_level); + + /* add the open quote */ + htmlchar = + (!is_end_tag + ? ((stream->html_quote_level & 1) == 1 + ? 8220 : 8216) + : ((stream->html_quote_level & 1) == 1 + ? 8221 : 8217)); + + /* + * write out the HTML character, translated to + * the local character set + */ + outchar_html_stream(stream, htmlchar); + + /* if it's a close quote, decrement the level */ + if (is_end_tag) + --(stream->html_quote_level); + } + } + else if (!scumm_stricmp(tagbuf, "title")) + { + /* + * Turn ignore mode on or off as appropriate, and + * turn on or off title mode as well. + */ + if (is_end_tag) + { + /* + * note that we're leaving an ignore section and + * a title section + */ + --(stream->html_in_ignore); + --(stream->html_in_title); + + /* + * if we're no longer in a title, call the OS + * layer to tell it the title string, in case it + * wants to change the window title or otherwise + * make use of the title + */ + if (stream->html_in_title == 0) + { + /* null-terminate the title string */ + *stream->html_title_ptr = '\0'; + + /* tell the OS about the title */ + os_set_title(stream->html_title_buf); + } + } + else + { + /* + * if we aren't already in a title, set up to + * capture the title into the title buffer + */ + if (!stream->html_in_title) + stream->html_title_ptr = stream->html_title_buf; + + /* + * note that we're in a title and in an ignore + * section, since nothing within gets displayed + */ + ++(stream->html_in_ignore); + ++(stream->html_in_title); + } + } + else if (!scumm_stricmp(tagbuf, "aboutbox")) + { + /* turn ignore mode on or off as appropriate */ + if (is_end_tag) + --(stream->html_in_ignore); + else + ++(stream->html_in_ignore); + } + else if (!scumm_stricmp(tagbuf, "pre")) + { + /* count the nesting level if starting PRE mode */ + if (!is_end_tag) + stream->html_pre_level += 1; + + /* surround the PRE block with line breaks */ + outblank_stream(stream); + + /* count the nesting level if ending PRE mode */ + if (is_end_tag && stream->html_pre_level != 0) + stream->html_pre_level -= 1; + } + + /* suppress everything up to the next '>' */ + stream->html_mode_flag = HTML_MODE_TAG; + + /* + * continue with the current character; since we're in + * html tag mode, we'll skip everything until we get to + * the closing '>' + */ + continue; + } + else if (c == '&') + { + /* parse it */ + char xlat_buf[50]; + c = out_parse_entity(xlat_buf, sizeof(xlat_buf), &s, &slen); + + /* write it out (we've already translated it) */ + outstring_noxlat_stream(stream, xlat_buf); + + /* proceed with the next character */ + continue; + } + } + else if (stream->html_target && stream->html_mode && c == '<') + { + /* + * We're in HTML mode, and we have an underlying HTML target. + * We don't need to do much HTML interpretation at this level. + * However, we do need to keep track of when we're in a PRE + * block, so that we can pass whitespaces and newlines through + * to the underlying HTML engine without filtering when we're + * in preformatted text. + */ + char tagbuf[50]; + int is_end_tag; + c = read_tag(tagbuf, sizeof(tagbuf), &is_end_tag, + &s, &slen, stream); + + /* check for special tags */ + if (!scumm_stricmp(tagbuf, "pre")) + { + /* count the nesting level */ + if (!is_end_tag) + stream->html_pre_level += 1; + else if (is_end_tag && stream->html_pre_level != 0) + stream->html_pre_level -= 1; + } + + /* copy the last character after the tag to the stream */ + outchar_stream(stream, c); + } + else + { + /* normal character */ + outchar_stream(stream, c); + } + + /* move on to the next character, unless we're finished */ + if (done) + c = '\0'; + else + c = nextout(&s, &slen); + } + + /* if we ended up inside what looked like a format string, dump string */ + if (infmt) + { + outchar_stream(stream, '%'); + for (f1 = fmsbuf ; f1 < f ; ++f1) + outchar_stream(stream, *f1); + } + + /* exit a recursion level */ + out_pop_stream(); + + /* success */ + return 0; +} + +/* ------------------------------------------------------------------------ */ +/* + * Parse an HTML entity markup + */ +static char out_parse_entity(char *outbuf, size_t outbuf_size, + char **sp, size_t *slenp) +{ + char ampbuf[10]; + char *dst; + char *orig_s; + size_t orig_slen; + const amp_tbl_t *ampptr; + size_t lo, hi, cur; + char c; + + /* + * remember where the part after the '&' begins, so we can come back + * here later if necessary + */ + orig_s = *sp; + orig_slen = *slenp; + + /* get the character after the ampersand */ + c = nextout(sp, slenp); + + /* if it's numeric, parse the number */ + if (c == '#') + { + uint val; + + /* skip the '#' */ + c = nextout(sp, slenp); + + /* check for hex */ + if (c == 'x' || c == 'X') + { + /* skip the 'x' */ + c = nextout(sp, slenp); + + /* read the hex number */ + for (val = 0 ; Common::isXDigit((uchar)c) ; c = nextout(sp, slenp)) + { + /* accumulate the current digit into the value */ + val *= 16; + if (outisdg(c)) + val += c - '0'; + else if (c >= 'a' && c <= 'f') + val += c - 'a' + 10; + else + val += c - 'A' + 10; + } + } + else + { + /* read the number */ + for (val = 0 ; outisdg(c) ; c = nextout(sp, slenp)) + { + /* accumulate the current digit into the value */ + val *= 10; + val += c - '0'; + } + } + + /* if we found a ';' at the end, skip it */ + if (c == ';') + c = nextout(sp, slenp); + + /* translate the character into the output buffer */ + os_xlat_html4(val, outbuf, outbuf_size); + + /* we're done with this character */ + return c; + } + + /* + * Parse the sequence after the '&'. Parse up to the closing + * semicolon, or any non-alphanumeric, or until we fill up the buffer. + */ + for (dst = ampbuf ; + c != '\0' && (outisdg(c) || outisal(c)) + && dst < ampbuf + sizeof(ampbuf) - 1 ; + *dst++ = c, c = nextout(sp, slenp)) ; + + /* null-terminate the name */ + *dst = '\0'; + + /* do a binary search for the name */ + lo = 0; + hi = sizeof(amp_tbl)/sizeof(amp_tbl[0]) - 1; + for (;;) + { + int diff; + + /* if we've converged, look no further */ + if (lo > hi || lo >= sizeof(amp_tbl)/sizeof(amp_tbl[0])) + { + ampptr = 0; + break; + } + + /* split the difference */ + cur = lo + (hi - lo)/2; + ampptr = &_tbl[cur]; + + /* see where we are relative to the target item */ + diff = strcmp(ampptr->cname, ampbuf); + if (diff == 0) + { + /* this is it */ + break; + } + else if (diff > 0) + { + /* make sure we don't go off the end */ + if (cur == hi && cur == 0) + { + /* we've failed to find it */ + ampptr = 0; + break; + } + + /* this one is too high - check the lower half */ + hi = (cur == hi ? hi - 1 : cur); + } + else + { + /* this one is too low - check the upper half */ + lo = (cur == lo ? lo + 1 : cur); + } + } + + /* skip to the appropriate next character */ + if (c == ';') + { + /* name ended with semicolon - skip the semicolon */ + c = nextout(sp, slenp); + } + else if (ampptr != 0) + { + int skipcnt; + + /* found the name - skip its exact length */ + skipcnt = strlen(ampptr->cname); + for (*sp = orig_s, *slenp = orig_slen ; skipcnt != 0 ; + c = nextout(sp, slenp), --skipcnt) ; + } + + /* if we found the entry, write out the character */ + if (ampptr != 0) + { + /* + * if this one has an external mapping table entry, use the mapping + * table entry; otherwise, use the default OS routine mapping + */ + if (ampptr->expan != 0) + { + /* + * we have an explicit expansion from the mapping table file - + * use it + */ + size_t copylen = strlen(ampptr->expan); + if (copylen > outbuf_size - 1) + copylen = outbuf_size - 1; + + memcpy(outbuf, ampptr->expan, copylen); + outbuf[copylen] = '\0'; + } + else + { + /* + * there's no mapping table expansion - use the default OS code + * expansion + */ + os_xlat_html4(ampptr->html_cval, outbuf, outbuf_size); + } + } + else + { + /* + * didn't find it - output the '&' literally, then back up and + * output the entire sequence following + */ + *sp = orig_s; + *slenp = orig_slen; + c = nextout(sp, slenp); + + /* fill in the '&' return value */ + outbuf[0] = '&'; + outbuf[1] = '\0'; + } + + /* return the next character */ + return c; +} + + + +/* ------------------------------------------------------------------------ */ +/* + * Initialize the output formatter + */ +void out_init() +{ + /* not yet hiding output */ + outflag = 1; + outcnt = 0; + hidout = 0; + + /* initialize the standard display stream */ + out_init_std(&G_std_disp); + + /* initialize the log file stream */ + out_init_log(&G_log_disp); +} + + +/* ------------------------------------------------------------------------ */ +/* + * initialize the property translation table + */ +void tiosetfmt(tiocxdef *ctx, runcxdef *rctx, uchar *fbase, uint flen) +{ + VARUSED(ctx); + fmsbase = fbase; + fmstop = fbase + flen; + runctx = rctx; +} + + +/* ------------------------------------------------------------------------ */ +/* + * Map an HTML entity to a local character value. The character table + * reader will call this routine during initialization if it finds HTML + * entities in the mapping table file. We'll remember these mappings + * for use in translating HTML entities to the local character set. + * + * Note that the standard run-time can only display a single character + * set, so every HTML entity that we display must be mapped to the + * single active native character set. + */ +void tio_set_html_expansion(unsigned int html_char_val, + const char *expansion, size_t expansion_len) +{ + amp_tbl_t *p; + + /* find the character value */ + for (p = amp_tbl ; + p < amp_tbl + sizeof(amp_tbl)/sizeof(amp_tbl[0]) ; ++p) + { + /* if this is the one, store it */ + if (p->html_cval == html_char_val) + { + /* allocate space for it */ + p->expan = (char *)osmalloc(expansion_len + 1); + + /* save it */ + memcpy(p->expan, expansion, expansion_len); + p->expan[expansion_len] = '\0'; + + /* no need to look any further */ + return; + } + } +} + + +/* ------------------------------------------------------------------------ */ +/* + * Write out a c-style (null-terminated) string. + */ +int outformat(char *s) +{ + return outformatlen(s, strlen(s)); +} + + +/* ------------------------------------------------------------------------ */ +/* + * This routine sends out a string, one character at a time (via outchar). + * Escape codes ('\n', and so forth) are handled here. + */ +int outformatlen(char *s, uint slen) +{ + char c; + uint orig_slen; + char *orig_s; + int ret; + int called_filter; + + /* presume we'll return success */ + ret = 0; + + /* presume we won't call the filter function */ + called_filter = FALSE; + + /* if there's a user filter function to invoke, call it */ + if (G_user_filter != MCMONINV) + { + /* push the string */ + runpstr(runctx, s, slen, 1); + + /* call the filter */ + runfn(runctx, G_user_filter, 1); + + /* + * note that we called the filter, so that we'll remove the + * result of the filter from the stack before we return + */ + called_filter = TRUE; + + /* if the result is a string, use it in place of the original text */ + if (runtostyp(runctx) == DAT_SSTRING) + { + runsdef val; + uchar *p; + + /* pop the value */ + runpop(runctx, &val); + + /* + * get the text from the string, and use it as a replacement + * for the original string + */ + p = val.runsv.runsvstr; + slen = osrp2(p) - 2; + s = (char *)(p + 2); + + /* + * push the string back onto the stack - this will ensure + * that the string stays referenced while we're working, so + * that the garbage collector won't delete it + */ + runrepush(runctx, &val); + } + } + + /* remember the original string, before we scan the first character */ + orig_s = s; + orig_slen = slen; + + /* get the first character to display */ + c = nextout(&s, &slen); + + /* if the string is non-empty, note that we've displayed something */ + if (c != 0) + outcnt = 1; + + /* check to see if we're hiding output */ + if (out_is_hidden()) + goto done; + + /* if the debugger is showing watchpoints, suppress all output */ + if (outwxflag) + goto done; + + /* display the string */ + ret = outformatlen_stream(&G_std_disp, orig_s, orig_slen); + + /* if there's a log file, write to the log file as well */ + if (logfp != 0) + { + outformatlen_stream(&G_log_disp, orig_s, orig_slen); + osfflush(logfp); + } + +done: + /* if we called the filter, remove the result from the stack */ + if (called_filter) + rundisc(runctx); + + /* return the result from displaying to the screen */ + return ret; +} + +/* ------------------------------------------------------------------------ */ +/* + * Display a blank line + */ +void outblank() +{ + /* note that we've displayed something */ + outcnt = 1; + + /* check to see if we're hiding output */ + if (out_is_hidden()) + return; + + /* generate the newline to the standard display */ + outblank_stream(&G_std_disp); + + /* if we're logging, generate the newline to the log file as well */ + if (logfp != 0) + { + outblank_stream(&G_log_disp); + osfflush(logfp); + } +} + + +/* ------------------------------------------------------------------------ */ +/* + * outcaps() - sets an internal flag which makes the next letter output + * a capital, whether it came in that way or not. Set the same state in + * both formatters (standard and log). + */ +void outcaps(void) +{ + outcaps_stream(&G_std_disp); + outcaps_stream(&G_log_disp); +} + +/* + * outnocaps() - sets the next letter to a miniscule, whether it came in + * that way or not. + */ +void outnocaps(void) +{ + outnocaps_stream(&G_std_disp); + outnocaps_stream(&G_log_disp); +} + +/* ------------------------------------------------------------------------ */ +/* + * Open a log file + */ +int tiologopn(tiocxdef *ctx, char *fn) +{ + /* if there's an old log file, close it */ + if (tiologcls(ctx)) + return 1; + + /* save the filename for later */ + strcpy(logfname, fn); + + /* open the new file */ + logfp = osfopwt(fn, OSFTLOG); + + /* + * Reset the log file's output formatter state, since we're opening + * a new file. + */ + out_init_log(&G_log_disp); + + /* + * Set the log file's HTML source mode flag to the same value as is + * currently being used in the main display stream, so that it will + * interpret source markups the same way that the display stream is + * going to. + */ + G_log_disp.html_mode = G_std_disp.html_mode; + + /* return 0 on success, non-zero on failure */ + return (logfp == 0); +} + +/* + * Close the log file + */ +int tiologcls(tiocxdef *ctx) +{ + /* if we have a file, close it */ + if (logfp != 0) + { + /* close the handle */ + osfcls(logfp); + + /* set the system file type to "log file" */ + os_settype(logfname, OSFTLOG); + + /* forget about our log file handle */ + logfp = 0; + } + + /* success */ + return 0; +} + +/* ------------------------------------------------------------------------ */ +/* + * Write text explicitly to the log file. This can be used to add + * special text (such as prompt text) that would normally be suppressed + * from the log file. When more mode is turned off, we don't + * automatically copy text to the log file; any text that the caller + * knows should be in the log file during times when more mode is turned + * off can be explicitly added with this function. + * + * If nl is true, we'll add a newline at the end of this text. The + * caller should not include any newlines in the text being displayed + * here. + */ +void out_logfile_print(char *txt, int nl) +{ + /* if there's no log file, there's nothing to do */ + if (logfp == 0) + return; + + /* add the text */ + os_fprintz(logfp, txt); + + /* add a newline if desired */ + if (nl) + { + /* add a normal newline */ + os_fprintz(logfp, "\n"); + + /* if the logfile is an html target, write an HTML line break */ + if (G_log_disp.html_target && G_log_disp.html_mode) + os_fprintz(logfp, "<BR HEIGHT=0>\n"); + } + + /* flush the output */ + osfflush(logfp); +} + +/* ------------------------------------------------------------------------ */ +/* + * Set the current MORE mode + */ +int setmore(int state) +{ + int oldstate = G_os_moremode; + + G_os_moremode = state; + return oldstate; +} + +/* ------------------------------------------------------------------------ */ +/* + * Run the MORE prompt. If the output layer takes responsibility for + * pagination issues (i.e., USE_MORE is defined), we'll simply display + * the prompt and wait for input. Otherwise, the OS layer controls the + * MORE prompt, so we'll call the OS-layer function to display the + * prompt. + */ +void out_more_prompt() +{ +#ifdef USE_MORE + /* + * USE_MORE defined - we take responsibility for pagination. Show + * our default MORE prompt and wait for a keystroke. + */ + + int done; + int next_page = FALSE; + + /* display the "MORE" prompt */ + os_printz("[More]"); + os_flush(); + + /* wait for an acceptable keystroke */ + for (done = FALSE ; !done ; ) + { + os_event_info_t evt; + + /* get an event */ + switch(os_get_event(0, FALSE, &evt)) + { + case OS_EVT_KEY: + switch(evt.key[0]) + { + case ' ': + /* stop waiting, show one page */ + done = TRUE; + next_page = TRUE; + break; + + case '\r': + case '\n': + /* stop waiting, show one line */ + done = TRUE; + next_page = FALSE; + break; + + default: + /* ignore any other keystrokes */ + break; + } + break; + + case OS_EVT_EOF: + /* end of file - there's nothing to wait for now */ + done = TRUE; + next_page = TRUE; + + /* don't use more prompts any more, as the user can't respond */ + G_os_moremode = FALSE; + break; + + default: + /* ignore other events */ + break; + } + } + + /* + * Remove the prompt from the screen by backing up and overwriting + * it with spaces. (Note that this assumes that we're running in + * some kind of terminal or character mode with a fixed-pitch font; + * if that's not the case, the OS layer should be taking + * responsibility for pagination anyway, so this code shouldn't be + * in use in the first place.) + */ + os_printz("\r \r"); + + /* + * if they pressed the space key, it means that we should show an + * entire new page, so reset the line count to zero; otherwise, + * we'll want to display another MORE prompt at the very next line, + * so leave the line count alone + */ + if (next_page) + G_std_disp.linecnt = 0; + +#else /* USE_MORE */ + + /* + * USE_MORE is undefined - this means that the OS layer is taking + * all responsibility for pagination. We must ask the OS layer to + * display the MORE prompt, because we can't make any assumptions + * about what the prompt looks like. + */ + + os_more_prompt(); + +#endif /* USE_MORE */ +} + +/* ------------------------------------------------------------------------ */ +/* + * reset output + */ +void outreset(void) +{ + G_std_disp.linecnt = 0; +} + +/* ------------------------------------------------------------------------ */ +/* + * Determine if HTML mode is active. Returns true if so, false if not. + * Note that this merely indicates whether an "\H+" sequence is + * currently active -- this will return true after an "\H+" sequence, + * even on text-only interpreters. + */ +int tio_is_html_mode() +{ + /* return the current HTML mode flag for the standard display stream */ + return G_std_disp.html_mode; +} + + +/* ------------------------------------------------------------------------ */ +/* + * Capture routines. Capture affects only the standard display output + * stream; there's no need to capture information redundantly in the log + * file stream. + */ + +/* + * Begin/end capturing + */ +void tiocapture(tiocxdef *tioctx, mcmcxdef *memctx, int flag) +{ + if (flag) + { + /* create a new object if necessary */ + if (G_std_disp.capture_obj == MCMONINV) + { + mcmalo(memctx, 256, &G_std_disp.capture_obj); + mcmunlck(memctx, G_std_disp.capture_obj); + } + + /* remember the memory context */ + G_std_disp.capture_ctx = memctx; + } + + /* + * remember capture status in the standard output stream as well as + * the log stream + */ + G_std_disp.capturing = flag; + G_log_disp.capturing = flag; +} + +/* clear all captured output */ +void tioclrcapture(tiocxdef *tioctx) +{ + G_std_disp.capture_ofs = 0; +} + +/* clear captured output back to a given size */ +void tiopopcapture(tiocxdef *tioctx, uint orig_size) +{ + G_std_disp.capture_ofs = orig_size; +} + +/* get the object handle of the captured output */ +mcmon tiogetcapture(tiocxdef *ctx) +{ + return G_std_disp.capture_obj; +} + +/* get the amount of text captured */ +uint tiocapturesize(tiocxdef *ctx) +{ + return G_std_disp.capture_ofs; +} + +/* ------------------------------------------------------------------------ */ +/* + * set the current actor + */ +void tiosetactor(tiocxdef *ctx, objnum actor) +{ + VARUSED(ctx); + cmdActor = actor; +} + +/* + * get the current actor + */ +objnum tiogetactor(tiocxdef *ctx) +{ + VARUSED(ctx); + return cmdActor; +} + +/* ------------------------------------------------------------------------ */ +/* + * Flush the output line. We'll write to both the standard display and + * the log file, as needed. + */ +void outflushn(int nl) +{ + /* flush the display stream */ + outflushn_stream(&G_std_disp, nl); + + /* flush the log stream, if we have an open log file */ + if (logfp != 0) + { + outflushn_stream(&G_log_disp, nl); + osfflush(logfp); + } +} + +/* + * flush the current line, and start a new line + */ +void outflush(void) +{ + /* use the common flushing routine in mode 1 (regular newline) */ + outflushn(1); +} + +/* ------------------------------------------------------------------------ */ +/* + * Hidden text routines + */ + +/* + * outhide - hide output in the standard display stream + */ +void outhide(void) +{ + outflag = 0; + outcnt = 0; + hidout = 0; +} + +/* + * Check output status. Indicate whether output is currently hidden, + * and whether any hidden output has occurred. + */ +void outstat(int *hidden, int *output_occurred) +{ + *hidden = !outflag; + *output_occurred = outcnt; +} + +/* set the flag to indicate that output has occurred */ +void outsethidden(void) +{ + outcnt = 1; + hidout = 1; +} + +/* + * outshow() - turns output back on, and returns TRUE (1) if any output + * has occurred since the last outshow(), FALSE (0) otherwise. + */ +int outshow(void) +{ + /* turn output back on */ + outflag = 1; + + /* if we're debugging, note the end of hidden output */ + if (dbghid && hidout) + { + hidout = 0; + trcsho(); + } + + /* return the flag indicating whether hidden output occurred */ + return outcnt; +} + +/* ------------------------------------------------------------------------ */ +/* + * start/end watchpoint evaluation - suppress all dstring output + */ +void outwx(int flag) +{ + outwxflag = flag; +} + + +/* ------------------------------------------------------------------------ */ +/* + * Set the user filter function. Setting this to MCMONINV clears the + * filter. + */ +void out_set_filter(objnum filter_fn) +{ + /* remember the filter function */ + G_user_filter = filter_fn; +} + +/* ------------------------------------------------------------------------ */ +/* + * Set the double-space mode + */ +void out_set_doublespace(int dbl) +{ + /* remember the new setting */ + doublespace = dbl; +} + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/tads2/run.cpp b/engines/glk/tads/tads2/run.cpp index 5123d650fe..5a9ede0f2b 100644 --- a/engines/glk/tads/tads2/run.cpp +++ b/engines/glk/tads/tads2/run.cpp @@ -22,30 +22,2410 @@ #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 { -int runsdef::runsiz() const { - switch (runstyp) { +/* 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 *)0, 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 == 0 || !(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, 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, char *str, size_t len, int sav) +{ + 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: - return 4; + 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(runsv.runsvstr); + 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 2; + 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 *)0); +} + +/* 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)) != 0) + { + 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 = 0; + + /* 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: - return 0; + 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; /* property number, when needed */ + objnum obj; /* object number, when needed */ + runsdef *noreg rstsp; /* sp to reset to on DISCARD instructions */ + uchar *lstp; /* list pointer */ + int nargc; /* argument count of called function */ + runsdef *valp; + runsdef *stkval; + int i; + int brkchk; + +#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; + } + break; +#endif + + 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); + dbgleave(ctx->runcxdbg, + ctx->runcxsp != stkp ? DBGEXVAL : 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); + /* NOTREACHED */ + + case OPCABORT: + errsig(ctx->runcxerr, ERR_RUNABRT); + /* NOTREACHED */ + + case OPCASKDO: + errsig(ctx->runcxerr, ERR_RUNASKD); + /* NOTREACHED */ + + case OPCASKIO: + errsig1(ctx->runcxerr, ERR_RUNASKI, ERRTINT, osrp2(p)); + /* NOTREACHED */ + + 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); + uchar instr; + + /* set up the debugger frame record for this line */ + dbgframe(ctx->runcxdbg, osrp2(p + 1), p - ptr); + + /* remember the instruction */ + instr = *(p - 1); + + /* remember the offset of the line record */ + ctx->runcxlofs = ofs = (p + 2 - ptr); + + /* skip to the next instruction */ + p += *p; + + /* let the debugger take over, if it wants to */ + dbgssi(ctx->runcxdbg, ofs, instr, 0, &p); + 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) + { + runsdef val3; + int asityp; + int asiext; + int lclnum; + + 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 <op>= 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 diff --git a/engines/glk/tads/tads2/run.h b/engines/glk/tads/tads2/run.h index b6694bf523..71b8d78d66 100644 --- a/engines/glk/tads/tads2/run.h +++ b/engines/glk/tads/tads2/run.h @@ -46,14 +46,10 @@ namespace Glk { namespace TADS { namespace TADS2 { -/* Forward declarations */ -struct runcxdef; -struct runufdef; -struct voccxdef; +/* forward declarations */ +struct bifcxdef; -/** - * Stack element - the stack is an array of these structures - */ +/* stack element - the stack is an array of these structures */ struct runsdef { uchar runstyp; /* type of element */ union { @@ -61,50 +57,36 @@ struct runsdef { objnum runsvobj; /* object value */ prpnum runsvprp; /* property number value */ uchar *runsvstr; /* string/list value */ - } runsv; - - - /** - * Determine size of a data item - */ - int runsiz() const; + } runsv; }; -/** - * External function control structure - */ +/* external function control structure */ struct runxdef { char runxnam[TOKNAMMAX + 1]; /* name of external function */ int (*runxptr)(void *); /* pointer to memory containing code */ }; -/** - * External function context structure - passed to user exits - */ +/* external function context structure - passed to user exits */ struct runuxdef { - runcxdef *runuxctx; /* run-time context */ - runufdef *runuxvec; /* vector of functions */ - int runuxargc; /* count of arguments to function */ + struct runcxdef osfar_t *runuxctx; /* run-time context */ + struct runufdef osfar_t *runuxvec; /* vector of functions */ + int runuxargc; /* count of arguments to function */ }; -/** - * External function callback vector - */ +/* external function callback vector */ struct runufdef { - int (*runuftyp)(runuxdef *); /* type of top of stack */ - long (*runufnpo)(runuxdef *); /* pop a number */ - uchar *(*runufspo)(runuxdef *); /* pop a string */ - void (*runufdsc)(runuxdef *); /* discard item at top of stack */ - void (*runufnpu)(runuxdef *, long); /* push a number */ - void (*runufspu)(runuxdef *, uchar *); /* push alloc'd string */ - void (*runufcspu)(runuxdef *, char *); /* push a C-string */ - uchar *(*runufsal)(runuxdef *, int); /* allocate a new string */ - void (*runuflpu)(runuxdef *, int);/* push DAT_TRUE or DAT_NIL */ + int (osfar_t *runuftyp)(runuxdef *); /* type of top of stack */ + long (osfar_t *runufnpo)(runuxdef *); /* pop a number */ + uchar *(osfar_t *runufspo)(runuxdef *); /* pop a string */ + void (osfar_t *runufdsc)(runuxdef *); /* discard item at top of stack */ + void (osfar_t *runufnpu)(runuxdef *, long); /* push a number */ + void (osfar_t *runufspu)(runuxdef *, uchar *); /* push alloc'd string */ + void (osfar_t *runufcspu)(runuxdef *, char *); /* push a C-string */ + uchar *(osfar_t *runufsal)(runuxdef *, int); /* allocate a new string */ + void (osfar_t *runuflpu)(runuxdef *, int);/* push DAT_TRUE or DAT_NIL */ }; -/** - * Execution context - */ +/* execution context */ struct runcxdef { errcxdef *runcxerr; /* error management context */ mcmcxdef *runcxmem; /* cache manager context for object references */ @@ -120,8 +102,8 @@ struct runcxdef { void *runcxbcx; /* context for built-in callback functions */ void (**runcxbi)(struct bifcxdef *ctx, int argc); /* built-in functions */ - dbgcxdef *runcxdbg; /* debugger context */ - voccxdef *runcxvoc; /* player command parser context */ + struct dbgcxdef *runcxdbg; /* debugger context */ + struct voccxdef *runcxvoc; /* player command parser context */ void (*runcxdmd)(void *ctx, objnum obj, prpnum prp); /* demand-loader callback function */ void *runcxdmc; /* demand-loader callback context */ @@ -130,120 +112,66 @@ struct runcxdef { uint runcxlofs; /* offset of last line record encountered */ char *runcxgamename; /* name of the .GAM file */ char *runcxgamepath; /* absolute directory path of .GAM file */ - - /** - * Execute a function, given the function object number - */ - void runfn(noreg objnum objn, int argc); - - /** - * Execute p-code given a pointer to the code. p is the actual pointer - * to the first byte of code to be executed. self is the object to be - * used for the special 'self' pseudo-object, and target is the object - * whose data are actually being executed. targprop is the property being - * executed; 0 is used for functions. - */ - void runexe(uchar *p, objnum self, objnum target, - prpnum targprop, int argc); - - /** - * Push a value onto the stack - */ - void runpush(dattyp typ, runsdef *val); - - /** - * Push a value onto the stack that's already in the heap - */ - void runrepush(runsdef *val); - - /** - * Push a number onto the stack - */ - void runpnum(long val); - - /** - * Push an object onto the stack - */ - void runpobj(objnum obj); - - /** - * push nil - */ - void runpnil(runcxdef *ctx); - - /** - * push a value onto the stack from a buffer (propdef, list) - */ - void runpbuf(int typ, void *val); - - /** - * Push a counted-length string onto the stack - */ - void runpstr(char *str, int len, int sav); - - /** - * Push a C-style string onto the stack, converting escape codes. If - * the character contains backslashes, newline, or tab characters, we'll - * convert these characters to their escaped equivalent. - */ - void runpushcstr(char *str, size_t len, int sav); - - /** - * Push a property onto the stack. codepp is a pointer to the caller's - * code pointer, which will be updated if necessary; callobj and - * callofsp are the object and starting offset within the object of the - * code being executed by the caller, which are needed to update - * *codepp. Property 0 is used if a function is being executed. obj - * and prop are the object and property number whose value is to be - * pushed. If 'inh' is TRUE, it means that only a property inherited - * by 'obj' is to be considered; this is used for "pass"/"inherited" - * operations, with the current target object given as 'obj'. - */ - void runpprop(uchar *noreg *codepp, objnum callobj, - prpnum callprop, noreg objnum obj, prpnum prop, int inh, - int argc, objnum self); - - /** - * compare magnitudes of numbers/strings on top of stack; strcmp-like value - */ - int runmcmp(runcxdef *ctx); - - /** - * True if items at top of stack are equal, FALSE otherwise - */ - int runeq(runcxdef *ctx); - - /** - * Garbage collect heap, making sure 'siz' bytes are available afterwards - */ - void runhcmp(uint siz, uint below, - runsdef *val1, runsdef *val2, runsdef *val3); - - /** - * Find a sublist within a list, returning pointer to sublist or null - */ - uchar *runfind(uchar *list, runsdef *item); - - /** - * Restore code pointer from object.property + offset - */ - uchar *runcprst(uint ofs, objnum obj, prpnum prop); - - /** - * Add two runsdef values, returning result in *val - */ - void runadd(runsdef *val2, uint below); - - /** - * Subtract val2 from val, returning result in *val; return TRUE if - * value changed, FALSE otherwise (this is returned when subtracting - * something from a list that isn't in the list) - */ - int runsub(runsdef *val2, uint below); }; +/* execute a function, given the function object number */ +void runfn(runcxdef *ctx, noreg objnum objn, int argc); + +/* + * Execute p-code given a pointer to the code. p is the actual pointer + * to the first byte of code to be executed. self is the object to be + * used for the special 'self' pseudo-object, and target is the object + * whose data are actually being executed. targprop is the property being + * executed; 0 is used for functions. + */ +void runexe(runcxdef *ctx, uchar *p, objnum self, objnum target, + prpnum targprop, int argc); + +/* push a value onto the stack */ +void runpush(runcxdef *ctx, dattyp typ, runsdef *val); + +/* push a value onto the stack that's already in the heap */ +void runrepush(runcxdef *ctx, runsdef *val); + +/* push a number onto the stack */ +void runpnum(runcxdef *ctx, long val); + +/* push an object onto the stack */ +void runpobj(runcxdef *ctx, objnum obj); + +/* push nil */ +void runpnil(runcxdef *ctx); + +/* push a value onto the stack from a buffer (propdef, list) */ +void runpbuf(runcxdef *ctx, int typ, void *val); + +/* push a counted-length string onto the stack */ +void runpstr(runcxdef *ctx, char *str, int len, int sav); + +/* + * Push a C-style string onto the stack, converting escape codes. If + * the character contains backslashes, newline, or tab characters, we'll + * convert these characters to their escaped equivalent. + */ +void runpushcstr(runcxdef *ctx, char *str, size_t len, int sav); + +/* + * Push a property onto the stack. codepp is a pointer to the caller's + * code pointer, which will be updated if necessary; callobj and + * callofsp are the object and starting offset within the object of the + * code being executed by the caller, which are needed to update + * *codepp. Property 0 is used if a function is being executed. obj + * and prop are the object and property number whose value is to be + * pushed. If 'inh' is TRUE, it means that only a property inherited + * by 'obj' is to be considered; this is used for "pass"/"inherited" + * operations, with the current target object given as 'obj'. + */ +void runpprop(runcxdef *ctx, uchar *noreg *codepp, objnum callobj, + prpnum callprop, noreg objnum obj, prpnum prop, int inh, + int argc, objnum self); + /* top level runpprop, when caller is not executing in an object */ -/* void runppr(objnum obj, prpnum prp, int argc); */ +/* void runppr(runcxdef *ctx, objnum obj, prpnum prp, int argc); */ #define runppr(ctx, obj, prp, argc) \ runpprop(ctx, (uchar **)0, (objnum)0, (prpnum)0, obj, prp, FALSE, argc, obj) @@ -252,7 +180,7 @@ struct runcxdef { #define rundisc(ctx) (runstkund(ctx), (--((ctx)->runcxsp))) /* pop the top element on the stack */ -/* void runpop(runsdef *val); */ +/* void runpop(runcxdef *ctx, runsdef *val); */ #define runpop(ctx, v) \ (runstkund(ctx), memcpy(v, (--((ctx)->runcxsp)), (size_t)sizeof(runsdef))) @@ -327,6 +255,17 @@ struct runcxdef { /* int runclog(int log); */ #define runclog(l) ((l) ? DAT_TRUE : DAT_NIL) +/* compare magnitudes of numbers/strings on top of stack; strcmp-like value */ +int runmcmp(runcxdef *ctx); + +/* TRUE if items at top of stack are equal, FALSE otherwise */ +int runeq(runcxdef *ctx); + +/* check for stack underflow */ +/* void runstkund(runcxdef *ctx); */ + +/* check for stack overflow */ +/* void runstkovf(runcxdef *ctx); */ /* * Check to ensure we have enough arguments to pass to a function or method @@ -352,7 +291,7 @@ struct runcxdef { #endif /* RUNFAST */ /* reserve space in heap, collecting garbage if necessary */ -/* void runhres(uint siz, uint below); */ +/* void runhres(runcxdef *ctx, uint siz, uint below); */ #define runhres(ctx, siz, below) \ ((uint)((ctx)->runcxhtop - (ctx)->runcxhp) > (uint)(siz) ? DISCARD 0 : \ (runhcmp(ctx, siz, below, (runsdef *)0, (runsdef *)0, (runsdef *)0),\ @@ -371,9 +310,31 @@ struct runcxdef { ((uint)((ctx)->runcxhtop - (ctx)->runcxhp) > (uint)(siz) ? DISCARD 0 : \ (runhcmp(ctx, siz, below, val1, val2, val3), DISCARD 0)) +/* garbage collect heap, making sure 'siz' bytes are available afterwards */ +void runhcmp(runcxdef *ctx, uint siz, uint below, + runsdef *val1, runsdef *val2, runsdef *val3); + +/* determine size of a data item */ +int runsiz(runsdef *item); + +/* find a sublist within a list, returning pointer to sublist or NULL */ +uchar *runfind(uchar *list, runsdef *item); + +/* add two runsdef values, returning result in *val */ +void runadd(runcxdef *ctx, runsdef *val, runsdef *val2, uint below); + +/* + * subtract val2 from val, returning result in *val; return TRUE if + * value changed, FALSE otherwise (this is returned when subtracting + * something from a list that isn't in the list) + */ +int runsub(runcxdef *ctx, runsdef *val, runsdef *val2, uint below); + +/* restore code pointer from object.property + offset */ +uchar *runcprst(runcxdef *ctx, uint ofs, objnum obj, prpnum prop); /* leave a stack frame, removing arguments */ -/* void runleave(uint parms); */ +/* void runleave(runcxdef *ctx, uint parms); */ #define runleave(ctx, parms) \ (((ctx)->runcxsp = (ctx)->runcxbp), \ ((ctx)->runcxbp = (runsdef *)((--((ctx)->runcxsp))->runsv.runsvstr)), \ @@ -385,6 +346,27 @@ struct runcxdef { ((ctx)->runcxhp = (ctx)->runcxheap), \ dbgrst(ctx->runcxdbg)) +/* set up runtime status line display */ +void runistat(struct voccxdef *vctx, struct runcxdef *rctx, + struct tiocxdef *tctx); + +/* signal a run-time error - allows debugger trapping */ +void runsign(runcxdef *ctx, int err); + +/* sign a run-time error with zero arguments */ +#define runsig(ctx, err) (errargc((ctx)->runcxerr,0),runsign(ctx,err)) + +/* signal a run-time error with one argument */ +#define runsig1(ctx, err, typ, arg) \ + (errargv((ctx)->runcxerr,0,typ,arg),errargc((ctx)->runcxerr,1),\ + runsign(ctx,err)) + +/* draw status line */ +void runstat(void); + +/* initialize output status */ +void runistat(struct voccxdef *vctx, struct runcxdef *rctx, + struct tiocxdef *tctx); } // End of namespace TADS2 } // End of namespace TADS diff --git a/engines/glk/tads/tads2/tokenizer.cpp b/engines/glk/tads/tads2/tokenizer.cpp index 96bd2f18fb..3f9dc6ec27 100644 --- a/engines/glk/tads/tads2/tokenizer.cpp +++ b/engines/glk/tads/tads2/tokenizer.cpp @@ -24,6 +24,7 @@ #include "glk/tads/tads2/error.h" #include "glk/tads/tads2/memory_cache_heap.h" #include "glk/tads/tads2/os.h" +#include "glk/tads/os_glk.h" namespace Glk { namespace TADS { diff --git a/engines/glk/tads/tads2/vocabulary.cpp b/engines/glk/tads/tads2/vocabulary.cpp index 516e9df3f9..8c4fead31d 100644 --- a/engines/glk/tads/tads2/vocabulary.cpp +++ b/engines/glk/tads/tads2/vocabulary.cpp @@ -24,6 +24,7 @@ #include "glk/tads/tads2/vocabulary.h" #include "glk/tads/tads2/error.h" #include "glk/tads/tads2/memory_cache_heap.h" +#include "glk/tads/os_glk.h" namespace Glk { namespace TADS { |