aboutsummaryrefslogtreecommitdiff
path: root/engines/glk/tads
diff options
context:
space:
mode:
authorPaul Gilbert2019-05-17 13:51:43 -1000
committerPaul Gilbert2019-05-24 18:21:06 -0700
commit105a1b94bd9d5a0f10752e135671f4e9a4b0d8da (patch)
tree14a535ad171084e1908882a87b29fbd085ea93af /engines/glk/tads
parente83972f502142b4e6191b962a7be89829bd8d708 (diff)
downloadscummvm-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.h69
-rw-r--r--engines/glk/tads/osfrobtads.cpp8
-rw-r--r--engines/glk/tads/osfrobtads.h11
-rw-r--r--engines/glk/tads/tads2/character_map.cpp1
-rw-r--r--engines/glk/tads/tads2/data.cpp61
-rw-r--r--engines/glk/tads/tads2/data.h19
-rw-r--r--engines/glk/tads/tads2/error_handling.h7
-rw-r--r--engines/glk/tads/tads2/lib.h37
-rw-r--r--engines/glk/tads/tads2/list.cpp42
-rw-r--r--engines/glk/tads/tads2/list.h47
-rw-r--r--engines/glk/tads/tads2/os.h6
-rw-r--r--engines/glk/tads/tads2/output.cpp3559
-rw-r--r--engines/glk/tads/tads2/run.cpp2394
-rw-r--r--engines/glk/tads/tads2/run.h290
-rw-r--r--engines/glk/tads/tads2/tokenizer.cpp1
-rw-r--r--engines/glk/tads/tads2/vocabulary.cpp1
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, "&nbsp;");
+ }
+ 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 = &amp_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 {