aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Gilbert2019-05-17 10:04:03 -1000
committerPaul Gilbert2019-05-24 18:21:06 -0700
commit0279143a62c2cdab635894103da03b43eba7cf9c (patch)
treefbc77cd007a4104204287825902601b6fb50bad1
parent54d240d81f8858f7ad694c690fcf738b3ec8b89d (diff)
downloadscummvm-rg350-0279143a62c2cdab635894103da03b43eba7cf9c.tar.gz
scummvm-rg350-0279143a62c2cdab635894103da03b43eba7cf9c.tar.bz2
scummvm-rg350-0279143a62c2cdab635894103da03b43eba7cf9c.zip
GLK: TADS2: Adding headers
-rw-r--r--engines/glk/module.mk13
-rw-r--r--engines/glk/tads/os_filetype.h64
-rw-r--r--engines/glk/tads/osfrobtads.cpp42
-rw-r--r--engines/glk/tads/osfrobtads.h267
-rw-r--r--engines/glk/tads/tads2/appctx.h248
-rw-r--r--engines/glk/tads/tads2/built_in.h216
-rw-r--r--engines/glk/tads/tads2/character_map.cpp339
-rw-r--r--engines/glk/tads/tads2/character_map.h131
-rw-r--r--engines/glk/tads/tads2/data.cpp3
-rw-r--r--engines/glk/tads/tads2/data.h1
-rw-r--r--engines/glk/tads/tads2/debug.h589
-rw-r--r--engines/glk/tads/tads2/error.cpp32
-rw-r--r--engines/glk/tads/tads2/error.h390
-rw-r--r--engines/glk/tads/tads2/error_handling.cpp32
-rw-r--r--engines/glk/tads/tads2/error_handling.h338
-rw-r--r--engines/glk/tads/tads2/file_io.cpp32
-rw-r--r--engines/glk/tads/tads2/file_io.h137
-rw-r--r--engines/glk/tads/tads2/ler.cpp152
-rw-r--r--engines/glk/tads/tads2/ler.h617
-rw-r--r--engines/glk/tads/tads2/lib.h164
-rw-r--r--engines/glk/tads/tads2/line_source.h129
-rw-r--r--engines/glk/tads/tads2/memory_cache.cpp31
-rw-r--r--engines/glk/tads/tads2/memory_cache.h403
-rw-r--r--engines/glk/tads/tads2/memory_cache_heap.cpp51
-rw-r--r--engines/glk/tads/tads2/memory_cache_heap.h60
-rw-r--r--engines/glk/tads/tads2/memory_cache_loader.cpp31
-rw-r--r--engines/glk/tads/tads2/memory_cache_loader.h57
-rw-r--r--engines/glk/tads/tads2/memory_cache_swap.cpp31
-rw-r--r--engines/glk/tads/tads2/memory_cache_swap.h122
-rw-r--r--engines/glk/tads/tads2/object.cpp35
-rw-r--r--engines/glk/tads/tads2/object.h396
-rw-r--r--engines/glk/tads/tads2/opcode_defs.h218
-rw-r--r--engines/glk/tads/tads2/os.cpp2
-rw-r--r--engines/glk/tads/tads2/os.h5324
-rw-r--r--engines/glk/tads/tads2/post_compilation.h96
-rw-r--r--engines/glk/tads/tads2/property.h167
-rw-r--r--engines/glk/tads/tads2/regex.cpp2481
-rw-r--r--engines/glk/tads/tads2/regex.h378
-rw-r--r--engines/glk/tads/tads2/run.cpp52
-rw-r--r--engines/glk/tads/tads2/run.h393
-rw-r--r--engines/glk/tads/tads2/tads2.cpp523
-rw-r--r--engines/glk/tads/tads2/tads2.h123
-rw-r--r--engines/glk/tads/tads2/tads2_cmap.cpp268
-rw-r--r--engines/glk/tads/tads2/text_io.h233
-rw-r--r--engines/glk/tads/tads2/tokenizer.cpp33
-rw-r--r--engines/glk/tads/tads2/tokenizer.h473
-rw-r--r--engines/glk/tads/tads2/types.h190
-rw-r--r--engines/glk/tads/tads2/vocabulary.cpp1
-rw-r--r--engines/glk/tads/tads2/vocabulary.h763
49 files changed, 13987 insertions, 2884 deletions
diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index d84d542825..43c2523b80 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -92,13 +92,22 @@ MODULE_OBJS := \
scott/detection.o \
scott/scott.o \
tads/detection.o \
+ tads/osfrobtads.o \
tads/tads.o \
+ tads/tads2/character_map.o \
tads/tads2/data.o \
- tads/tads2/ler.o \
+ tads/tads2/error.o \
+ tads/tads2/error_handling.o \
+ tads/tads2/file_io.o \
+ tads/tads2/memory_cache.o \
+ tads/tads2/memory_cache_heap.o \
+ tads/tads2/memory_cache_loader.o \
+ tads/tads2/memory_cache_swap.o \
tads/tads2/os.o \
tads/tads2/regex.o \
+ tads/tads2/run.o \
tads/tads2/tads2.o \
- tads/tads2/tads2_cmap.o \
+ tads/tads2/tokenizer.o \
tads/tads2/vocabulary.o \
tads/tads3/tads3.o
diff --git a/engines/glk/tads/os_filetype.h b/engines/glk/tads/os_filetype.h
new file mode 100644
index 0000000000..93b101698f
--- /dev/null
+++ b/engines/glk/tads/os_filetype.h
@@ -0,0 +1,64 @@
+/* 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_DATATYPE
+#define GLK_TADS_OS_DATATYPE
+
+namespace Glk {
+namespace TADS {
+
+/**
+ * File types. These type codes are used when opening or creating a
+ * file, so that the OS routine can set appropriate file system metadata
+ * to describe or find the file type.
+ *
+ * The type os_filetype_t is defined for documentary purposes; it's
+ * always just an int.
+ */
+enum os_filetype_t {
+ OSFTGAME = 0, /* a game data file (.gam) */
+ OSFTSAVE = 1, /* a saved game (.sav) */
+ OSFTLOG = 2, /* a transcript (log) file */
+ OSFTSWAP = 3, /* swap file */
+ OSFTDATA = 4, /* user data file (used with the TADS fopen() call) */
+ OSFTCMD = 5, /* QA command/log file */
+ OSFTERRS = 6, /* error message file */
+ OSFTTEXT = 7, /* text file - used for source files */
+ OSFTBIN = 8, /* binary file of unknown type - resources, etc */
+ OSFTCMAP = 9, /* character mapping file */
+ OSFTPREF = 10, /* preferences file */
+ OSFTUNK = 11, /* unknown - as a filter, matches any file type */
+ OSFTT3IMG = 12, /* T3 image file (.t3 - formerly .t3x) */
+ OSFTT3OBJ = 13, /* T3 object file (.t3o) */
+ OSFTT3SYM = 14, /* T3 symbol export file (.t3s) */
+ OSFTT3SAV = 15, /* T3 saved state file (.t3v) */
+};
+
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/osfrobtads.cpp b/engines/glk/tads/osfrobtads.cpp
new file mode 100644
index 0000000000..fbcf1c1050
--- /dev/null
+++ b/engines/glk/tads/osfrobtads.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/osfrobtads.h"
+#include "common/file.h"
+
+namespace Glk {
+namespace TADS {
+
+osfildef *osfoprb(const char *fname, os_filetype_t typ) {
+ Common::File f;
+ if (f.open(fname))
+ return f.readStream(f.size());
+ else
+ return nullptr;
+}
+
+int osfrb(osfildef *fp, void *buf, size_t count) {
+ return fp->read(buf, count);
+}
+
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/osfrobtads.h b/engines/glk/tads/osfrobtads.h
new file mode 100644
index 0000000000..2dce84c5ec
--- /dev/null
+++ b/engines/glk/tads/osfrobtads.h
@@ -0,0 +1,267 @@
+/* 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.
+ *
+ */
+
+/* OS-layer functions and macros.
+ *
+ * This file does not introduce any curses (or other screen-API)
+ * dependencies; it can be used for both the interpreter as well as the
+ * compiler.
+ */
+
+#ifndef GLK_TADS_OSFROBTADS
+#define GLK_TADS_OSFROBTADS
+
+#include "common/fs.h"
+#include "common/stream.h"
+#include "glk/glk_api.h"
+#include "glk/tads/os_filetype.h"
+
+namespace Glk {
+namespace TADS {
+
+
+/* Defined for Gargoyle. */
+#define HAVE_STDINT_
+
+#if 0
+#include "common.h"
+#endif
+
+/* Used by the base code to inhibit "unused parameter" compiler warnings. */
+#ifndef VARUSED
+#define VARUSED(var) (void)var
+#endif
+
+/* We assume that the C-compiler is mostly ANSI compatible. */
+#define OSANSI
+
+/* Special function qualifier needed for certain types of callback
+ * functions. This is for old 16-bit systems; we don't need it and
+ * define it to nothing. */
+#define OS_LOADDS
+
+/* Unices don't suffer the near/far pointers brain damage (thank God) so
+ * we make this a do-nothing macro. */
+#define osfar_t
+
+/* This is used to explicitly discard computed values (some compilers
+ * would otherwise give a warning like "computed value not used" in some
+ * cases). Casting to void should work on every ANSI-Compiler. */
+#define DISCARD (void)
+
+/* Copies a struct into another. ANSI C allows the assignment operator
+ * to be used with structs. */
+#define OSCPYSTRUCT(x,y) ((x)=(y))
+
+/* Link error messages into the application. */
+#define ERR_LINK_MESSAGES
+
+/* Program Exit Codes. */
+#define OSEXSUCC 0 /* Successful completion. */
+#define OSEXFAIL 1 /* Failure. */
+
+/* Here we configure the osgen layer; refer to tads2/osgen3.c for more
+ * information about the meaning of these macros. */
+#define USE_DOSEXT
+#define USE_NULLSTYPE
+
+/* Theoretical maximum osmalloc() size.
+ * Unix systems have at least a 32-bit memory space. Even on 64-bit
+ * systems, 2^32 is a good value, so we don't bother trying to find out
+ * an exact value. */
+#define OSMALMAX 0xffffffffL
+
+#define OSFNMAX 255
+
+/* File handle structure for osfxxx functions. */
+typedef Common::SeekableReadStream osfildef;
+
+/* Directory handle for searches via os_open_dir() et al. */
+typedef Common::FSNode *osdirhdl_t;
+
+/* file type/mode bits */
+#define OSFMODE_FILE S_IFREG
+#define OSFMODE_DIR S_IFDIR
+#define OSFMODE_CHAR S_IFCHR
+#define OSFMODE_BLK S_IFBLK
+#define OSFMODE_PIPE S_IFIFO
+#ifdef S_IFLNK
+#define OSFMODE_LINK S_IFLNK
+#else
+#define OSFMODE_LINK 0
+#endif
+#ifdef S_IFSOCK
+#define OSFMODE_SOCKET S_IFSOCK
+#else
+#define OSFMODE_SOCKET 0
+#endif
+
+/* File attribute bits. */
+#define OSFATTR_HIDDEN 0x0001
+#define OSFATTR_SYSTEM 0x0002
+#define OSFATTR_READ 0x0004
+#define OSFATTR_WRITE 0x0008
+
+/* Get a file's stat() type. */
+int osfmode( const char* fname, int follow_links, unsigned long* mode,
+ unsigned long* attr );
+
+#if 0
+/* The maximum width of a line of text.
+ *
+ * We ignore this, but the base code needs it defined. If the
+ * interpreter is run inside a console or terminal with more columns
+ * than the value defined here, weird things will happen, so we go safe
+ * and use a large value. */
+#define OS_MAXWIDTH 255
+#endif
+
+/* Disable the Tads swap file; computers have plenty of RAM these days.
+ */
+#define OS_DEFAULT_SWAP_ENABLED 0
+
+/* TADS 2 macro/function configuration. Modern configurations always
+ * use the no-macro versions, so these definitions should always be set
+ * as shown below. */
+#define OS_MCM_NO_MACRO
+#define ERR_NO_MACRO
+
+/* These values are used for the "mode" parameter of osfseek() to
+ * indicate where to seek in the file. */
+#define OSFSK_SET SEEK_SET /* Set position relative to the start of the file. */
+#define OSFSK_CUR SEEK_CUR /* Set position relative to the current file position. */
+#define OSFSK_END SEEK_END /* Set position relative to the end of the file. */
+
+
+/* ============= Functions follow ================ */
+
+/* Allocate a block of memory of the given size in bytes. */
+#define osmalloc malloc
+
+/* Free memory previously allocated with osmalloc(). */
+#define osfree free
+
+/* Reallocate memory previously allocated with osmalloc() or osrealloc(),
+ * changing the block's size to the given number of bytes. */
+#define osrealloc realloc
+
+/* Open text file for reading. */
+#define osfoprt(fname,typ) (fopen((fname),"r"))
+
+/* Open text file for writing. */
+#define osfopwt(fname,typ) (fopen((fname),"w"))
+
+/* 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
+ * such file exists. */
+osfildef *osfoprwt(const char *fname, os_filetype_t typ);
+
+/* Open text file for reading/writing. If the file already exists,
+ * truncate the existing contents. Create a new file if it doesn't
+ * already exist. */
+#define osfoprwtt(fname,typ) (fopen((fname),"w+"))
+
+/* Open binary file for writing. */
+#define osfopwb(fname,typ) (fopen((fname),"wb"))
+
+/* Open source file for reading - use the appropriate text or binary
+ * mode. */
+#define osfoprs osfoprt
+
+/* Open binary file for reading. */
+osfildef *osfoprb(const char *fname, os_filetype_t typ);
+
+/* Open binary file for reading/writing. If the file already exists,
+ * keep the existing contents. Create a new file if it doesn't already
+ * exist. */
+osfildef*
+osfoprwb( const char* fname, os_filetype_t typ );
+
+/* Open binary file for reading/writing. If the file already exists,
+ * truncate the existing contents. Create a new file if it doesn't
+ * already exist. */
+#define osfoprwtb(fname,typ) (fopen((fname),"w+b"))
+
+/* Get a line of text from a text file. */
+#define osfgets fgets
+
+/* Write a line of text to a text file. */
+#define osfputs fputs
+
+/* Write bytes to file. */
+#define osfwb(fp,buf,bufl) (fwrite((buf),(bufl),1,(fp))!=1)
+
+/* Flush buffered writes to a file. */
+#define osfflush fflush
+
+/* Read bytes from file. */
+int osfrb(osfildef *fp, void *buf, size_t count);
+
+/* Read bytes from file and return the number of bytes read. */
+#define osfrbc(fp,buf,bufl) (fread((buf),1,(bufl),(fp)))
+
+/* Get the current seek location in the file. */
+#define osfpos ftell
+
+/* Seek to a location in the file. */
+#define osfseek fseek
+
+/* Close a file. */
+#define osfcls delete
+
+/* Delete a file. */
+#define osfdel remove
+
+/* Access a file - determine if the file exists.
+ *
+ * We map this to the access() function. It should be available in
+ * virtually every system out there, as it appears in many standards
+ * (SVID, AT&T, POSIX, X/OPEN, BSD 4.3, DOS, MS Windows, maybe more). */
+#define osfacc(fname) (access((fname), F_OK))
+
+/* Rename a file. */
+#define os_rename_file(from, to) (rename(from, to) == 0)
+
+/* Get a file's stat() type. */
+struct os_file_stat_t;
+int os_file_stat( const char* fname, int follow_links,
+ struct os_file_stat_t* s );
+
+/* Get a character from a file. */
+#define osfgetc fgetc
+
+/* Set busy cursor.
+ *
+ * We don't have a mouse cursor so there's no need to implement this. */
+#define os_csr_busy(a)
+
+/* Update progress display.
+ *
+ * We don't provide any kind of "compilation progress display", so we
+ * just define this as an empty macro.
+ */
+#define os_progress(fname,linenum)
+
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/appctx.h b/engines/glk/tads/tads2/appctx.h
new file mode 100644
index 0000000000..3b3f499777
--- /dev/null
+++ b/engines/glk/tads/tads2/appctx.h
@@ -0,0 +1,248 @@
+/* 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.
+ *
+ */
+
+#ifndef GLK_TADS_TADS2_APPCTX
+#define GLK_TADS_TADS2_APPCTX
+
+#include "common/scummsys.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/**
+ * Application container context. The TADS run-time is a subsystem that
+ * can be invoked from different types of applications; in fact, even
+ * when only the standard stand-alone run-time is considered, multiple
+ * application containers must be supported because of differences
+ * between operating systems. The application container context is an
+ * optional mechanism that the main application can use to provide
+ * structured interaction between itself and the TADS run-time subsystem.
+ *
+ * The function pointers contained herein are intended to allow the
+ * run-time subsystem to call the host system to notify it of certain
+ * events, or obtain optional services from the host system. Any of
+ * these function pointers can be null, in which case the run-time
+ * subsystem will skip calling them.
+ *
+ * Note that each function has an associated callback context. This
+ * allows the host system to recover any necessary context information
+ * when the callback is invoked.
+ */
+struct appctxdef {
+ /**
+ * Get the .GAM file name. The run-time will call this only if it
+ * can't find a game file to load through some other means first.
+ * The run-time determines the game file first by looking at the
+ * command line, then by checking to see if a .GAM file is attached
+ * to the executable. If none of these checks yields a game, the
+ * run-time will call this routine to see if the host system wants
+ * to provide a game. This routine can be implemented on a GUI
+ * system, for example, to display a dialog prompting the user to
+ * select a game file to open. A trivial implementation of this
+ * routine (that merely returns false) is okay.
+ *
+ * This routine should return true (any non-zero value) if it
+ * provides the name of a file to open, false (zero) if not.
+ */
+ int (*get_game_name)(void *appctxdat, char *buf, size_t buflen);
+ void *get_game_name_ctx;
+
+ /**
+ * Set the .GAM file name. When the run-time determines the name of
+ * the file it will use to read the game, it calls this routine.
+ * The host system should note the game filename if it will need to
+ * access the game file itself (for example, to load resources).
+ */
+ void (*set_game_name)(void *appctxdat, const char *fname);
+ void *set_game_name_ctx;
+
+ /**
+ * Set the root path for individual resources. By default, we use the
+ * directory containing the game file, but this can be used to override
+ * that.
+ */
+ void (*set_res_dir)(void *appctxdat, const char *fname);
+ void *set_res_dir_ctx;
+
+ /**
+ * Set the resource map address in the game file. If the .GAM
+ * reader encounters a resource map in the file, it calls this
+ * routine with the seek offset of the first resource. Each
+ * resource's address is given as an offset from this point.
+ *
+ * fileno is the file number assigned by the host system in
+ * add_resfile. File number zero is always the .GAM file.
+ */
+ void (*set_resmap_seek)(void *appctxdat, unsigned long seekpos, int fileno);
+ void *set_resmap_seek_ctx;
+
+ /**
+ * Add a resource entry. The 'ofs' entry is the byte offset of the
+ * start of the resource, relative to the seek position previously
+ * set with set_resmap_seek. 'siz' is the size of the resource in
+ * bytes; the resource is stored as contiguous bytes starting at the
+ * given offset for the given size. Note that resources may be
+ * added before the resource map seek position is set, so the host
+ * system must simply store the resource information for later use.
+ * The 'fileno' is zero for the .GAM file, or the number assigned by
+ * the host system in add_resfile for other resource files.
+ */
+ void (*add_resource)(void *appctxdat, unsigned long ofs,
+ unsigned long siz, const char *nm, size_t nmlen,
+ int fileno);
+ void *add_resource_ctx;
+
+ /**
+ * Add a resource link entry. 'fname' and 'fnamelen' give the name of
+ * a local file containing the resource data; 'resname' and
+ * 'resnamelen' give the name of the resource as it appears within the
+ * compiled game file. This creates a link from a .GAM resource name
+ * to a local filename, where the actual binary resource data reside,
+ * so that we can retrieve a resource by .GAM resource name without
+ * actually copying the data into the .GAM file. This is used mostly
+ * for debugging purposes: it allows the compiler to skip the step of
+ * copying the resource data into the .GAM file, but still allows the
+ * game to load resources by .GAM resource name, to create a testing
+ * environment that's consistent with the full build version (where the
+ * resources would actually be copied).
+ */
+ void (*add_resource_link)(void *appctxdat,
+ const char *fname, size_t fnamelen,
+ const char *resname, size_t resnamelen);
+ void *add_resource_link_ctx;
+
+ /**
+ * Add a resource path. 'path' is a string giving a directory prefix
+ * in local system notation.
+ *
+ * This adds a directory to the list of directories that we'll search
+ * when we're looking for an individual resource as an external file
+ * (such as a ".jpg" image file or ".ogg" sound file). This can be
+ * called zero or more times; each call adds another directory to
+ * search after any previous directories. We'll always search the
+ * default directory first (this is the directory containing the game
+ * file); then we'll search directories added with this call in the
+ * order in which the directories were added.
+ */
+ void (*add_res_path)(void *appctxdat, const char *path, size_t len);
+ void *add_res_path_ctx;
+
+ /**
+ * Find a resource entry. If the resource can be found, this must
+ * return an osfildef* handle to the resource, with its seek position
+ * set to the first byte of the resource data, and set *res_size to
+ * the size in bytes of the resource data in the file. If the
+ * resource cannot be found, returns null.
+ */
+ osfildef *(*find_resource)(void *appctxdat,
+ const char *resname, size_t resnamelen,
+ unsigned long *res_size);
+ void *find_resource_ctx;
+
+ /**
+ * Add a resource file. The return value is a non-zero file number
+ * assigned by the host system; we'll use this number in subsequent
+ * calls to add_resource to add the resources from this file.
+ *
+ * After calling this routine to add the file, we'll parse the file
+ * and add any resources using add_resource.
+ */
+ int (*add_resfile)(void *appctxdat, const char *fname);
+ void *add_resfile_ctx;
+
+ /**
+ * Determine if a resource exists. Returns true if the resource can
+ * be loaded, false if not. The resource name is in the standard
+ * URL-style format.
+ */
+ int (*resfile_exists)(void *appctxdat, const char *res_name,
+ size_t res_name_len);
+ void *resfile_exists_ctx;
+
+ /**
+ * Resource file path. If we should look for resource files in a
+ * different location than the .GAM file, the host system can set
+ * this to a path that we should use to look for resource files. If
+ * it's null, we'll look in the directory that contains the .GAM
+ * file. Note that if the path is provided, it must be set up with
+ * a trailing path separator character, so that we can directly
+ * append a name to this path to form a valid fully-qualified
+ * filename.
+ */
+ const char *ext_res_path;
+
+ /**
+ * File safety level get/set. During initialization, we'll call the
+ * host system to tell it the file safety level selected by the user on
+ * the command line; if the host system is saving preference
+ * information, it should temporarily override its saved preferences
+ * and use the command line setting (and it may, if appropriate, want
+ * to save the command line setting as the saved preference setting,
+ * depending on how it handles preferences). During execution, any
+ * time the game tries to open a file (using the fopen built-in
+ * function), we'll call the host system to ask it for the current
+ * setting, and use this new setting rather than the original command
+ * line setting.
+ *
+ * Refer to bif.c for information on the meanings of the file safety
+ * levels.
+ */
+ void (*set_io_safety_level)(void *ctx, int read, int write);
+ void (*get_io_safety_level)(void *ctx, int *read, int *write);
+ void *io_safety_level_ctx;
+
+ /**
+ * Network safety level get/set. This is analogous to the file safety
+ * level scheme, but controls access to network resources. There are
+ * two components to the network safety setting: client and server.
+ * The client component controls the game's ability to open network
+ * connections to access information on remote machines, such as
+ * opening http connections to access web sites. The server component
+ * controls the game's ability to create servers of its own and accept
+ * incoming connections. Each component can be set to one of the
+ * following:
+ *
+ *. 0 = no restrictions (least "safety"): all network access granted
+ *. 1 = 'localhost' access only
+ *. 2 = no network access
+ *
+ * This only applies to the TADS 3 VM. TADS 2 doesn't support any
+ * network features, so this doesn't apply.
+ */
+ void (*set_net_safety_level)(void *ctx, int client_level, int srv_level);
+ void (*get_net_safety_level)(void *ctx, int *client_level, int *srv_level);
+ void *net_safety_level_ctx;
+
+ /**
+ * Name of run-time application for usage messages. If this is
+ * null, the default run-time application name will be used for
+ * usage messages.
+ */
+ const char *usage_app_name;
+};
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/built_in.h b/engines/glk/tads/tads2/built_in.h
new file mode 100644
index 0000000000..f5807e61a8
--- /dev/null
+++ b/engines/glk/tads/tads2/built_in.h
@@ -0,0 +1,216 @@
+/* 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.
+ *
+ */
+
+/* Built-in functions interface
+ *
+ * Interface to run-time intrinsic function implementation
+ */
+
+#ifndef GLK_TADS_TADS2_BUILT_IN
+#define GLK_TADS_TADS2_BUILT_IN
+
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/run.h"
+#include "glk/tads/tads2/text_io.h"
+#include "glk/tads/tads2/regex.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* forward definitions */
+struct vocidef;
+struct voccxdef;
+
+/* maximum number of file handles available */
+#define BIFFILMAX 10
+
+
+/* file contexts for the built-in file handling functions */
+struct biffildef {
+ osfildef *fp; /* underyling system file handle */
+ uint flags; /* flags */
+#define BIFFIL_F_BINARY 0x01 /* file is binary */
+};
+
+/* built-in execution context */
+struct bifcxdef {
+ errcxdef *bifcxerr; /* error-handling context */
+ runcxdef *bifcxrun; /* code execution context */
+ tiocxdef *bifcxtio; /* text I/O context */
+ long bifcxrnd; /* random number seed */
+ int bifcxseed1; /* first seed for new generator */
+ int bifcxseed2; /* second seed for new generator */
+ int bifcxseed3; /* third seed for new generator */
+ int bifcxrndset; /* randomize() has been called */
+ biffildef bifcxfile[BIFFILMAX]; /* file handles for fopen, etc */
+ int bifcxsafetyr; /* file I/O safety level - read */
+ int bifcxsafetyw; /* file I/O safety level - write */
+ char *bifcxsavext; /* saved game extension (null by default) */
+ appctxdef *bifcxappctx; /* host application context */
+ re_context bifcxregex; /* regular expression searching context */
+
+ bifcxdef() : bifcxerr(nullptr), bifcxrun(nullptr), bifcxtio(nullptr),
+ bifcxrnd(0), bifcxseed1(0), bifcxseed2(0), bifcxseed3(0), bifcxrndset(0),
+ bifcxsafetyr(0), bifcxsafetyw(0), bifcxsavext(nullptr), bifcxappctx(nullptr) {
+ }
+};
+
+/*
+ * argument list checking routines - can be disabled for faster
+ * run-time
+ */
+
+/* check for proper number of arguments */
+/* void bifcntargs(bifcxdef *ctx, int argcnt) */
+
+/* check that next argument has proper type */
+/* void bifchkarg(bifcxdef *ctx, dattyp typ); */
+
+#ifdef RUNFAST
+# define bifcntargs(ctx, parmcnt, argcnt)
+# define bifchkarg(ctx, typ)
+#else /* RUNFAST */
+# define bifcntargs(ctx, parmcnt, argcnt) \
+ (parmcnt == argcnt ? DISCARD 0 : \
+ (runsig(ctx->bifcxrun, ERR_BIFARGC), DISCARD 0))
+# define bifchkarg(ctx, typ) \
+ (runtostyp(ctx->bifcxrun) == typ ? DISCARD 0 : \
+ (runsig(ctx->bifcxrun, ERR_INVTBIF), DISCARD 0))
+#endif /* RUNFAST */
+
+/* determine if one object is a subclass of another */
+int bifinh(struct voccxdef *voc, struct vocidef *v, objnum cls);
+
+
+/* enumerate the built-in functions */
+void bifyon(bifcxdef *ctx, int argc); /* yorn - yes or no */
+void bifsfs(bifcxdef *ctx, int argc); /* setfuse */
+void bifrfs(bifcxdef *ctx, int argc); /* remfuse */
+void bifsdm(bifcxdef *ctx, int argc); /* setdaemon */
+void bifrdm(bifcxdef *ctx, int argc); /* remdaemon */
+void bifinc(bifcxdef *ctx, int argc); /* incturn */
+void bifqui(bifcxdef *ctx, int argc); /* quit */
+void bifsav(bifcxdef *ctx, int argc); /* save */
+void bifrso(bifcxdef *ctx, int argc); /* restore */
+void biflog(bifcxdef *ctx, int argc); /* logging */
+void bifres(bifcxdef *ctx, int argc); /* restart */
+void bifinp(bifcxdef *ctx, int argc); /* input - get line from kb */
+void bifnfy(bifcxdef *ctx, int argc); /* notify */
+void bifunn(bifcxdef *ctx, int argc); /* unnotify */
+void biftrc(bifcxdef *ctx, int argc); /* trace on/off */
+void bifsay(bifcxdef *ctx, int argc); /* say */
+void bifcar(bifcxdef *ctx, int argc); /* car */
+void bifcdr(bifcxdef *ctx, int argc); /* cdr */
+void bifcap(bifcxdef *ctx, int argc); /* caps */
+void biflen(bifcxdef *ctx, int argc); /* length */
+void biffnd(bifcxdef *ctx, int argc); /* find */
+void bifsit(bifcxdef *ctx, int argc); /* setit - set current 'it' */
+void bifsrn(bifcxdef *ctx, int argc); /* randomize: seed rand */
+void bifrnd(bifcxdef *ctx, int argc); /* rand - get a random number */
+void bifask(bifcxdef *ctx, int argc); /* askfile */
+void bifssc(bifcxdef *ctx, int argc); /* setscore */
+void bifsub(bifcxdef *ctx, int argc); /* substr */
+void bifcvs(bifcxdef *ctx, int argc); /* cvtstr: convert to string */
+void bifcvn(bifcxdef *ctx, int argc); /* cvtnum: convert to number */
+void bifupr(bifcxdef *ctx, int argc); /* upper */
+void biflwr(bifcxdef *ctx, int argc); /* lower */
+void biffob(bifcxdef *ctx, int argc); /* firstobj */
+void bifnob(bifcxdef *ctx, int argc); /* nextobj */
+void bifsvn(bifcxdef *ctx, int argc); /* setversion */
+void bifarg(bifcxdef *ctx, int argc); /* getarg */
+void biftyp(bifcxdef *ctx, int argc); /* datatype */
+void bifisc(bifcxdef *ctx, int argc); /* isclass */
+void bifund(bifcxdef *ctx, int argc); /* undo */
+void bifdef(bifcxdef *ctx, int argc); /* defined */
+void bifpty(bifcxdef *ctx, int argc); /* proptype */
+void bifoph(bifcxdef *ctx, int argc); /* outhide */
+void bifgfu(bifcxdef *ctx, int argc); /* getfuse */
+void bifruf(bifcxdef *ctx, int argc); /* runfuses */
+void bifrud(bifcxdef *ctx, int argc); /* rundaemons */
+void biftim(bifcxdef *ctx, int argc); /* gettime */
+void bifsct(bifcxdef *ctx, int argc); /* intersect */
+void bifink(bifcxdef *ctx, int argc); /* inputkey */
+void bifwrd(bifcxdef *ctx, int argc); /* objwords */
+void bifadw(bifcxdef *ctx, int argc); /* addword */
+void bifdlw(bifcxdef *ctx, int argc); /* delword */
+void bifgtw(bifcxdef *ctx, int argc); /* getwords */
+void bifnoc(bifcxdef *ctx, int argc); /* nocaps */
+void bifskt(bifcxdef *ctx, int argc); /* skipturn */
+void bifcls(bifcxdef *ctx, int argc); /* clearscreen */
+void bif1sc(bifcxdef *ctx, int argc); /* firstsc */
+void bifvin(bifcxdef *ctx, int argc); /* verbinfo */
+void bifcapture(bifcxdef *ctx, int argc); /* outcapture */
+
+void biffopen(bifcxdef *ctx, int argc); /* fopen */
+void biffclose(bifcxdef *ctx, int argc); /* fclose */
+void biffwrite(bifcxdef *ctx, int argc); /* fwrite */
+void biffread(bifcxdef *ctx, int argc); /* fread */
+void biffseek(bifcxdef *ctx, int argc); /* fseek */
+void biffseekeof(bifcxdef *ctx, int argc); /* fseekeof */
+void bifftell(bifcxdef *ctx, int argc); /* ftell */
+
+void bifsysinfo(bifcxdef *ctx, int argc); /* systemInfo */
+void bifmore(bifcxdef *ctx, int argc); /* morePrompt */
+void bifsetme(bifcxdef *ctx, int argc); /* parserSetMe */
+void bifgetme(bifcxdef *ctx, int argc); /* parserGetMe */
+
+void bifresearch(bifcxdef *ctx, int argc); /* reSearch */
+void bifregroup(bifcxdef *ctx, int argc); /* reGetGroup */
+
+void bifinpevt(bifcxdef *ctx, int argc); /* inputevent */
+void bifdelay(bifcxdef *ctx, int argc); /* timeDelay */
+
+void bifsetoutfilter(bifcxdef *ctx, int argc); /* setOutputFilter */
+void bifexec(bifcxdef *ctx, int argc); /* execCommand */
+void bifgetobj(bifcxdef *ctx, int argc); /* parserGetObj */
+void bifparsenl(bifcxdef *ctx, int argc); /* parserParseNounList */
+void bifprstok(bifcxdef *ctx, int argc); /* parserTokenize */
+void bifprstoktyp(bifcxdef *ctx, int argc); /* parserGetTokTypes */
+void bifprsdict(bifcxdef *ctx, int argc); /* parserDictLookup */
+void bifprsrslv(bifcxdef *ctx, int argc); /* parserResolveObjects */
+void bifprsrplcmd(bifcxdef *ctx, int argc); /* parserReplaceCommand */
+void bifexitobj(bifcxdef *ctx, int argc); /* exitobj */
+void bifinpdlg(bifcxdef *ctx, int argc); /* inputdialog */
+void bifresexists(bifcxdef *ctx, int argc); /* resourceExists */
+
+/*
+ * TADS/graphic functions - these are present in the text system, but
+ * don't do anything.
+ */
+void bifgrp(bifcxdef *ctx, int argc); /* g_readpic: read picture */
+void bifgsp(bifcxdef *ctx, int argc); /* g_showpic: show picture */
+void bifgsh(bifcxdef *ctx, int argc); /* g_sethot: set hot list */
+void bifgin(bifcxdef *ctx, int argc); /* g_inventory */
+void bifgco(bifcxdef *ctx, int argc); /* g_compass */
+void bifgov(bifcxdef *ctx, int argc); /* g_overlay */
+void bifgmd(bifcxdef *ctx, int argc); /* g_mode */
+void bifgmu(bifcxdef *ctx, int argc); /* g_music */
+void bifgpa(bifcxdef *ctx, int argc); /* g_pause */
+void bifgef(bifcxdef *ctx, int argc); /* g_effect */
+void bifgsn(bifcxdef *ctx, int argc); /* g_sound */
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/character_map.cpp b/engines/glk/tads/tads2/character_map.cpp
new file mode 100644
index 0000000000..91daa8668c
--- /dev/null
+++ b/engines/glk/tads/tads2/character_map.cpp
@@ -0,0 +1,339 @@
+/* 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/lib.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/os.h"
+#include "glk/tads/tads2/text_io.h"
+#include "glk/tads/osfrobtads.h"
+#include "common/algorithm.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Global variables for character mapping tables
+ */
+
+unsigned char G_cmap_input[256];
+unsigned char G_cmap_output[256];
+char G_cmap_id[5];
+char G_cmap_ldesc[CMAP_LDESC_MAX_LEN + 1];
+
+
+/*
+ * static variables
+ */
+
+/*
+ * flag: true -> a character set has been explicitly loaded, so we
+ * should ignore any game character set setting
+ */
+static int S_cmap_loaded;
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Initialize the default character mappings
+ */
+void cmap_init_default(void)
+{
+ size_t i;
+
+ /* initialize the input table */
+ for (i = 0 ; i < sizeof(G_cmap_input)/sizeof(G_cmap_input[0]) ; ++i)
+ G_cmap_input[i] = (unsigned char)i;
+
+ /* initialize the output table */
+ for (i = 0 ; i < sizeof(G_cmap_output)/sizeof(G_cmap_output[0]) ; ++i)
+ G_cmap_output[i] = (unsigned char)i;
+
+ /* we have a null ID */
+ memset(G_cmap_id, 0, sizeof(G_cmap_id));
+
+ /* indicate that it's the default */
+ strcpy(G_cmap_ldesc, "(native/no mapping)");
+
+ /* note that we have no character set loaded */
+ S_cmap_loaded = FALSE;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Internal routine to load a character map from a file
+ */
+static int cmap_load_internal(char *filename)
+{
+ osfildef *fp;
+ static char sig1[] = CMAP_SIG_S100;
+ char buf[256];
+ uchar lenbuf[2];
+ size_t len;
+ int sysblk;
+
+ /* if there's no mapping file, use the default mapping */
+ if (filename == 0)
+ {
+ /* initialize with the default mapping */
+ cmap_init_default();
+
+ /* return success */
+ return 0;
+ }
+
+ /* open the file */
+ fp = osfoprb(filename, OSFTCMAP);
+ if (fp == 0)
+ return 1;
+
+ /* check the signature */
+ if (osfrb(fp, buf, sizeof(sig1))
+ || memcmp(buf, sig1, sizeof(sig1)) != 0)
+ {
+ osfcls(fp);
+ return 2;
+ }
+
+ /* load the ID */
+ G_cmap_id[4] = '\0';
+ if (osfrb(fp, G_cmap_id, 4))
+ {
+ osfcls(fp);
+ return 3;
+ }
+
+ /* load the long description */
+ if (osfrb(fp, lenbuf, 2)
+ || (len = osrp2(lenbuf)) > sizeof(G_cmap_ldesc)
+ || osfrb(fp, G_cmap_ldesc, len))
+ {
+ osfcls(fp);
+ return 4;
+ }
+
+ /* load the two tables - input, then output */
+ if (osfrb(fp, G_cmap_input, sizeof(G_cmap_input))
+ || osfrb(fp, G_cmap_output, sizeof(G_cmap_output)))
+ {
+ osfcls(fp);
+ return 5;
+ }
+
+ /* read the next section header */
+ if (osfrb(fp, buf, 4))
+ {
+ osfcls(fp);
+ return 6;
+ }
+
+ /* if it's "SYSI", read the system information string */
+ if (!memcmp(buf, "SYSI", 4))
+ {
+ /* read the length prefix, then the string */
+ if (osfrb(fp, lenbuf, 2)
+ || (len = osrp2(lenbuf)) > sizeof(buf)
+ || osfrb(fp, buf, len))
+ {
+ osfcls(fp);
+ return 7;
+ }
+
+ /* we have a system information block */
+ sysblk = TRUE;
+ }
+ else
+ {
+ /* there's no system information block */
+ sysblk = FALSE;
+ }
+
+ /*
+ * call the OS code, so that it can do any system-dependent
+ * initialization for the new character mapping
+ */
+ os_advise_load_charmap(G_cmap_id, G_cmap_ldesc, sysblk ? buf : "");
+
+ /* read the next section header */
+ if (sysblk && osfrb(fp, buf, 4))
+ {
+ osfcls(fp);
+ return 8;
+ }
+
+ /* see if we have an entity list */
+ if (!memcmp(buf, "ENTY", 4))
+ {
+ /* read the entities */
+ for (;;)
+ {
+ size_t blen;
+ unsigned int cval;
+ char expansion[CMAP_MAX_ENTITY_EXPANSION];
+
+ /* read the next item's length and character value */
+ if (osfrb(fp, buf, 4))
+ {
+ osfcls(fp);
+ return 9;
+ }
+
+ /* decode the values */
+ blen = osrp2(buf);
+ cval = osrp2(buf+2);
+
+ /* if we've reached the zero marker, we're done */
+ if (blen == 0 && cval == 0)
+ break;
+
+ /* read the string */
+ if (blen > CMAP_MAX_ENTITY_EXPANSION
+ || osfrb(fp, expansion, blen))
+ {
+ osfcls(fp);
+ return 10;
+ }
+
+ /* tell the output code about the expansion */
+ tio_set_html_expansion(cval, expansion, blen);
+ }
+ }
+
+ /*
+ * ignore anything else we find - if the file format is updated to
+ * include extra information in the future, and this old code tries
+ * to load an updated file, we'll just ignore the new information,
+ * which should always be placed after the "SYSI" block (if present)
+ * to ensure compatibility with past versions (such as this code)
+ */
+
+ /* no problems - close the file and return success */
+ osfcls(fp);
+ return 0;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Explicitly load a character set from a file. This character set
+ * mapping will override any implicit character set mapping that we read
+ * from a game file. This should be called when the player explicitly
+ * loads a character set (via a command line option or similar action).
+ */
+int cmap_load(char *filename)
+{
+ int err;
+
+ /* try loading the file */
+ if ((err = cmap_load_internal(filename)) != 0)
+ return err;
+
+ /*
+ * note that we've explicitly loaded a character set, if they named
+ * a character set (if not, this simply establishes the default
+ * setting, so we haven't explicitly loaded anything)
+ */
+ if (filename != 0)
+ S_cmap_loaded = TRUE;
+
+ /* success */
+ return 0;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Explicitly override any game character set and use no character set
+ * instead.
+ */
+void cmap_override(void)
+{
+ /* apply the default mapping */
+ cmap_init_default();
+
+ /*
+ * pretend we have a character map loaded, so that we don't try to
+ * load another one if the game specifies a character set
+ */
+ S_cmap_loaded = TRUE;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Set the game's internal character set. This is called when a game is
+ * loaded, and the game specifies a character set.
+ */
+void cmap_set_game_charset(errcxdef *ec,
+ char *internal_id, char *internal_ldesc,
+ char *argv0)
+{
+ char filename[OSFNMAX];
+
+ /*
+ * If a character set is already explicitly loaded, ignore the
+ * game's character set - the player asked us to use a particular
+ * mapping, so ignore what the game wants. (This will probably
+ * result in incorrect display of non-ASCII character values, but
+ * the player is most likely to use this to avoid errors when an
+ * appropriate mapping file for the game is not available. In this
+ * case, the player informs us by setting the option that he or she
+ * knows and accepts that the game will not look exactly right.)
+ */
+ if (S_cmap_loaded)
+ return;
+
+ /*
+ * ask the operating system to name the mapping file -- this routine
+ * will determine, if possible, the current native character set,
+ * and apply a system-specific naming convention to tell us what
+ * mapping file we should open
+ */
+ os_gen_charmap_filename(filename, internal_id, argv0);
+
+ /* try loading the mapping file */
+ if (cmap_load_internal(filename))
+ errsig2(ec, ERR_CHRNOFILE,
+ ERRTSTR, errstr(ec, filename, strlen(filename)),
+ ERRTSTR, errstr(ec, internal_ldesc, strlen(internal_ldesc)));
+
+ /*
+ * We were successful - the game's internal character set is now
+ * mapped to the current native character set. Even though we
+ * loaded an ldesc from the mapping file, forget that and store the
+ * internal ldesc that the game specified. The reason we do this is
+ * that it's possible that the player will dynamically switch native
+ * character sets in the future, at which point we'll need to
+ * re-load the mapping table, which could raise an error if a
+ * mapping file for the new character set isn't available. So, we
+ * may need to provide the same explanation later that we needed to
+ * provide here. Save the game's character set ldesc for that
+ * eventuality, since it describes exactly what the *game* wanted.
+ */
+ strcpy(G_cmap_ldesc, internal_ldesc);
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/character_map.h b/engines/glk/tads/tads2/character_map.h
new file mode 100644
index 0000000000..c61406fd27
--- /dev/null
+++ b/engines/glk/tads/tads2/character_map.h
@@ -0,0 +1,131 @@
+/* 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.
+ *
+ */
+
+#ifndef GLK_TADS_CHARACTER_MAP
+#define GLK_TADS_CHARACTER_MAP
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+struct errcxdef;
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Initialize the default character mappings. If no mapping file is to
+ * be read, this function will establish identify mappings that leave
+ * characters untranslated.
+ */
+void cmap_init_default(void);
+
+
+/*
+ * Load a character map file. Returns zero on success, non-zero on
+ * failure. If filename is null, we'll use the default mapping.
+ */
+int cmap_load(char *filename);
+
+
+/*
+ * Turn off character translation. This overrides any game character
+ * set that we find and simply uses the default translation.
+ */
+void cmap_override(void);
+
+
+/*
+ * Set the game's internal character set. This should be called when a
+ * game is loaded, and the game specifies an internal character set. If
+ * there is no character map file explicitly loaded, we will attempt to
+ * load a character mapping file that maps this character set to the
+ * current native character set. Signals an error on failure. This
+ * routine will succeed (without doing anything) if a character set has
+ * already been explicitly loaded, since an explicitly-loaded character
+ * set overrides the automatic character set selection that we attempt
+ * when loading a game.
+ *
+ * argv0 must be provided so that we know where to look for our mapping
+ * file on systems where mapping files are stored in the same directory
+ * as the TADS executables.
+ */
+void cmap_set_game_charset(struct errcxdef *errctx,
+ char *internal_id, char *internal_ldesc,
+ char *argv0);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Mapping macros
+ */
+
+/* map a native character (read externally) into an internal character */
+#define cmap_n2i(c) (G_cmap_input[(unsigned char)(c)])
+
+/* map an internal character into a native character (for display) */
+#define cmap_i2n(c) (G_cmap_output[(unsigned char)(c)])
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Global character mapping tables. The character map is established at
+ * start-up.
+ */
+
+/*
+ * input-mapping table - for native character 'n', cmap_input[n] yields
+ * the internal character code
+ */
+extern unsigned char G_cmap_input[256];
+
+/*
+ * output-mapping table - for internal character 'n', cmap_output[n]
+ * yields the output character code
+ */
+extern unsigned char G_cmap_output[256];
+
+/* the ID of the loaded character set */
+extern char G_cmap_id[5];
+
+/* the full name (for display purposes) of the loaded character set */
+#define CMAP_LDESC_MAX_LEN 40
+extern char G_cmap_ldesc[CMAP_LDESC_MAX_LEN + 1];
+
+/*
+ * Maximum expansion for an HTML entity mapping
+ */
+#define CMAP_MAX_ENTITY_EXPANSION 50
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Signatures for character map files. The signature is stored at the
+ * beginning of the file.
+ */
+
+/* single-byte character map version 1.0.0 */
+#define CMAP_SIG_S100 "TADS2 charmap S100\n\r\01a"
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/data.cpp b/engines/glk/tads/tads2/data.cpp
index c60fcb2c5f..b19f45f1fd 100644
--- a/engines/glk/tads/tads2/data.cpp
+++ b/engines/glk/tads/tads2/data.cpp
@@ -21,7 +21,8 @@
*/
#include "glk/tads/tads2/data.h"
-#include "glk/tads/tads2/types.h"
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/run.h"
#include "glk/tads/tads2/vocabulary.h"
#include "common/algorithm.h"
diff --git a/engines/glk/tads/tads2/data.h b/engines/glk/tads/tads2/data.h
index 8db2153e1f..8d122c444a 100644
--- a/engines/glk/tads/tads2/data.h
+++ b/engines/glk/tads/tads2/data.h
@@ -47,6 +47,7 @@ enum DataType {
DAT_REDIR = 16, ///< redirection to different object
DAT_TPL2 = 17 ///< new-style template
};
+typedef DataType dattyp;
class Data {
private:
diff --git a/engines/glk/tads/tads2/debug.h b/engines/glk/tads/tads2/debug.h
new file mode 100644
index 0000000000..52695cfb86
--- /dev/null
+++ b/engines/glk/tads/tads2/debug.h
@@ -0,0 +1,589 @@
+/* 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.
+ *
+ */
+
+#ifndef GLK_TADS_TADS2_DEBUG
+#define GLK_TADS_TADS2_DEBUG
+
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/object.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+/* forward declarations */
+struct bifcxdef;
+struct toksdef;
+struct toktdef;
+struct tokcxdef;
+
+/* stack frame record */
+struct dbgfdef
+{
+ struct runsdef *dbgfbp; /* base pointer of frame */
+ objnum dbgfself; /* 'self' object (MCMONINV for functions) */
+ objnum dbgftarg; /* actual target object */
+ prpnum dbgfprop; /* property being evalutated */
+ int dbgfargc; /* number of arguments */
+ int dbgfbif; /* set to built-in function number if in built-in */
+ uint dbgffr; /* offset in object of local frame symbol table */
+ uint dbgflin; /* OPCLINE operand of latest line */
+};
+typedef struct dbgfdef dbgfdef;
+
+/* max number of frames to store in debug frame memory */
+#define DBGMAXFRAME 100
+
+/* maximum number of breakpoints set concurrently */
+#define DBGBPMAX 50
+
+/* breakpoint structure */
+struct dbgbpdef
+{
+ objnum dbgbpself; /* the "self" object for the breakpoint */
+ objnum dbgbptarg; /* actual target object for the breakpoint */
+ uint dbgbpofs; /* offset in object of the breakpoint */
+ uint dbgbpflg; /* breakpoint flags */
+#define DBGBPFUSED 0x01 /* breakpoint has been set */
+#define DBGBPFNAME 0x02 /* name of address has been stored */
+#define DBGBPFCOND 0x04 /* breakpoint has a condition attached */
+#define DBGBPFDISA 0x08 /* breakpoint is disabled */
+#define DBGBPFCONDNAME 0x10 /* condition name string has been stored */
+ uint dbgbpnam; /* offset of address name within dbgcxnam buffer */
+ uint dbgbpcondnam; /* offset of condition string within buffer */
+ objnum dbgbpcond; /* object containing compiled condition for bp */
+};
+typedef struct dbgbpdef dbgbpdef;
+
+/* maximum number of watch expressions set concurrently */
+#define DBGWXMAX 30
+
+/* watch expression structure */
+struct dbgwxdef
+{
+ objnum dbgwxobj; /* object containing compiled expression */
+ objnum dbgwxself; /* 'self' for the expression */
+ uint dbgwxnam; /* offset of expression text within dbgcxnam buffer */
+ uint dbgwxflg; /* flags for this watch expression slot */
+#define DBGWXFUSED 0x01 /* watch slot is in use */
+#define DBGWXFNAME 0x02 /* name of watch has been stored */
+};
+typedef struct dbgwxdef dbgwxdef;
+
+/* amount of space for bp names (original address strings from user) */
+#define DBGCXNAMSIZ 2048
+
+/* debug context */
+struct dbgcxdef
+{
+ struct tiocxdef *dbgcxtio; /* text i/o context */
+ struct tokthdef *dbgcxtab; /* symbol table */
+ struct mcmcxdef *dbgcxmem; /* memory cache manager context */
+ struct errcxdef *dbgcxerr; /* error handling context */
+ struct lindef *dbgcxlin; /* chain of line sources */
+ int dbgcxfcn; /* number of frames in use */
+ int dbgcxdep; /* actual depth (if overflow frame buffer) */
+ int dbgcxfid; /* source file serial number */
+ dbgfdef dbgcxfrm[DBGMAXFRAME]; /* stack frames */
+ int dbgcxflg; /* flags for debug session */
+#define DBGCXFSS 0x01 /* single-stepping source lines */
+#define DBGCXFSO 0x02 /* stepping over a function/method call */
+#define DBGCXFOK 0x04 /* debugger is linked in */
+#define DBGCXFIND 0x08 /* in debugger - suppress stack trace on err */
+#define DBGCXFGBP 0x10 /* global breakpoints in effect */
+#define DBGCXFTRC 0x20 /* call tracing activated */
+#define DBGCXFLIN2 0x40 /* new-style line records (line numbers) */
+ int dbgcxsof; /* frame depth at step-over time */
+ dbgbpdef dbgcxbp[DBGBPMAX]; /* breakpoints */
+ dbgwxdef dbgcxwx[DBGWXMAX]; /* watch expressions */
+ struct prscxdef *dbgcxprs; /* parsing context */
+ struct runcxdef *dbgcxrun; /* execution context */
+ uint dbgcxnamf; /* next free byte of dbgcxnam buffer */
+ uint dbgcxnams; /* size of dbgcxnam buffer */
+ char *dbgcxnam; /* space for bp address names */
+ char *dbgcxhstp; /* call history buffer */
+ uint dbgcxhstl; /* history buffer length */
+ uint dbgcxhstf; /* offset of next free byte of history */
+
+ /*
+ * This member is for the use of the user interface code. If the
+ * user interface implementation needs to store additional context,
+ * it can allocate a structure of its own (it should probably do
+ * this in dbguini()) and store a pointer to that structure here.
+ * Since the user interface entrypoints always have the debugger
+ * context passed as a parameter, the user interface code can
+ * recover its extra context information by following this pointer
+ * and casting it to its private structure type. The TADS code
+ * won't do anything with this pointer except initialize it to null
+ * when initializing the debugger context.
+ */
+ void *dbgcxui;
+};
+typedef struct dbgcxdef dbgcxdef;
+
+
+/* ======================================================================== */
+/*
+ * Compiler interface. These routines are called by the compiler to
+ * inform the debug record generator about important events as
+ * compilation proceeds.
+ */
+
+
+/*
+ * Tell the current line source that we're compiling an executable
+ * line, and tell it the object number and offset of the code within the
+ * object.
+ */
+void dbgclin(struct tokcxdef *tokctx, objnum objn, uint ofs);
+
+/* size of information given to line source via lincmpinf method */
+#define DBGLINFSIZ 4
+
+
+
+/* ======================================================================== */
+/*
+ * Run-time interface. These routines are called by the run-time
+ * system to apprise the debugger of important events during execution.
+ */
+
+
+/*
+ * Determine if the debugger is present. Returns true if so, false if
+ * not. This should return false for any stand-alone version of the
+ * executable that isn't linked with the debugger. If this returns
+ * true, dbgucmd() must not have a trivial implementation -- dbgucmd()
+ * must at least let the user quit out of the game.
+ *
+ * This can be switched at either link time or compile time. If DBG_OFF
+ * is defined, we'll force this to return false; otherwise, we'll let
+ * the program define the appropriate implementation through the linker.
+ */
+#ifdef DBG_OFF
+#define dbgpresent() (FALSE)
+#else
+int dbgpresent();
+#endif
+
+
+/* add a debug tracing record */
+/* void dbgenter(dbgcxdef *ctx, runsdef *bp, objnum self, objnum target,
+ prpnum prop, int binum, int argc); */
+
+/* tell debugger where the current line's local frame table is located */
+/* void dbgframe(dbgcxdef *ctx, uint ofsfr, ofslin); */
+
+/*
+ * Single-step interrupt: the run-time has reached a new source line.
+ * ofs is the offset from the start of the object of the line record,
+ * and p is the current execution pointer. *p can be changed upon
+ * return, in which case the run-time will continue from the new
+ * position; however, the new address must be within the same function
+ * or method as it was originally.
+ */
+/* void dbgssi(dbgcxdef *ctx, uint ofs, int instr,
+ int err, uchar *noreg *p); */
+
+/* pop debug trace level */
+/* void dbgleave(dbgcxdef *ctx, int exittype); */
+#define DBGEXRET 0 /* return with no value */
+#define DBGEXVAL 1 /* return with a value */
+#define DBGEXPASS 2 /* use 'pass' to exit a function */
+
+/* dump the stack into text output */
+/* void dbgdump(dbgcxdef *ctx); */
+
+/* reset debug stack (throw away entire contents) */
+/* void dbgrst(dbgcxdef *ctx); */
+
+/* activate debugger if possible; returns TRUE if no debugger is present */
+int dbgstart(dbgcxdef *ctx);
+
+/* add a string to the history buffer */
+void dbgaddhist(dbgcxdef *ctx, char *buf, int bufl);
+
+/*
+ * Find a base pointer, given the object+offset of the frame. If the
+ * frame is not active, this routine signals ERR_INACTFR; otherwise, the
+ * bp value for the frame is returned.
+ */
+struct runsdef *dbgfrfind(dbgcxdef *ctx, objnum frobj, uint frofs);
+
+
+/* ======================================================================== */
+/*
+ * User Interface Support routines. These routines are called by the
+ * user interface layer to get information from the debugger and perform
+ * debugging operations.
+ */
+
+
+/* get a symbol name; returns length of name */
+int dbgnam(dbgcxdef *ctx, char *outbuf, int typ, int val);
+
+/*
+ * Get information about current line. It is assumed that the caller
+ * knows the size of the line information .
+ */
+void dbglget(dbgcxdef *ctx, uchar *buf);
+
+/*
+ * Get information about a line in an enclosing stack frame. Level 0 is
+ * the current line, level 1 is the first enclosing frame, and so on.
+ * Returns 0 on success, non-zero if the frame level is invalid.
+ */
+int dbglgetlvl(dbgcxdef *ctx, uchar *buf, int level);
+
+/*
+ * Set a breakpoint by symbolic address: "function" or
+ * "object.property". The string may contain whitespace characters
+ * around each symbol; it must be null-terminated. If an error occurs,
+ * the error number is returned. bpnum returns with the breakpoint
+ * number if err == 0. If the condition string is given (and is not an
+ * empty string), the condition is compiled in the scope of the
+ * breakpoint and attached as the breakpoint condition.
+ */
+int dbgbpset(dbgcxdef *ctx, char *addr, int *bpnum);
+
+/*
+ * Set a breakpoint at an object + offset location. If 'toggle' is
+ * true, and there's already a breakpoint at the given location, we'll
+ * clear the breakpoint; in this case, *did_set will return false to
+ * indicate that an existing breakpoint was cleared rather than a new
+ * breakpoint created. *did_set will return true if a new breakpoint
+ * was set.
+ */
+int dbgbpat(dbgcxdef *ctx, objnum objn, objnum self,
+ uint ofs, int *bpnum, char *bpname, int toggle,
+ char *condition, int *did_set);
+
+/*
+ * Set a breakpoint at an object + offset location, optionally with a
+ * condition, using an existing breakpoint slot. If the slot is already
+ * in use, we'll return an error.
+ */
+int dbgbpatid(dbgcxdef *ctx, int bpnum, objnum target, objnum self,
+ uint ofs, char *bpname, int toggle, char *cond,
+ int *did_set);
+
+/*
+ * Determine if there's a breakpoint at a given code location. Fills in
+ * *bpnum with the breakpoint identifier and returns true if a
+ * breakpoint is found at the given location; returns false if there are
+ * no breakpoints matching the description.
+ */
+int dbgisbp(dbgcxdef *ctx, objnum target, objnum self, uint ofs, int *bpnum);
+
+/*
+ * Determine if the given breakpoint is enabled
+ */
+int dbgisbpena(dbgcxdef *ctx, int bpnum);
+
+/*
+ * Delete a breakpoint by breakpoint number (as returned from
+ * dbgbpset). Returns error number, or 0 for success.
+ */
+int dbgbpdel(dbgcxdef *ctx, int bpnum);
+
+/* disable or enable a breakpoint, by breakpoint number; returns error num */
+int dbgbpdis(dbgcxdef *ctx, int bpnum, int disable);
+
+/*
+ * Set a new condition for the given breakpoint. Replaces any existing
+ * condition. If an error occurs, we'll leave the old condition as it
+ * was and return a non-zero error code; on success, we'll update the
+ * condition and return zero.
+ */
+int dbgbpsetcond(dbgcxdef *ctx, int bpnum, char *cond);
+
+/* list breakpoints, using user callback to do display */
+void dbgbplist(dbgcxdef *ctx,
+ void (*dispfn)(void *ctx, const char *str, int len),
+ void *dispctx);
+
+/* enumerate breakpoints */
+void dbgbpenum(dbgcxdef *ctx,
+ void (*cbfunc)(void *cbctx, int bpnum, const char *desc,
+ const char *cond, int disabled), void *cbctx);
+
+/* call callback with lindef data for each breakpoint currently set */
+void dbgbpeach(dbgcxdef *ctx,
+ void (*fn)(void *, int, uchar *, uint),
+ void *fnctx);
+
+/*
+ * Get information on a specific breakpoint. Returns zero on success,
+ * non-zero on failure.
+ */
+int dbgbpgetinfo(dbgcxdef *ctx, int bpnum, char *descbuf, size_t descbuflen,
+ char *condbuf, size_t condbuflen);
+
+/*
+ * Evaluate an expression (a text string to be parsed) at a particular
+ * stack context level; returns error number. Invokes the callback
+ * function repeatedly to display the value string, and ends the display
+ * with a newline. If showtype is true, we'll include a type name
+ * prefix, otherwise we'll simply display the value.
+ */
+int dbgeval(dbgcxdef *ctx, char *expr,
+ void (*dispfn)(void *dispctx, const char *str, int strl),
+ void *dispctx, int level, int showtype);
+
+/*
+ * Evaluate an expression, extended version. For aggregate values
+ * (objects, lists), we'll invoke a callback function for each value
+ * contained by the aggregate value, passing the callback the name and
+ * relationship of the subitem. The relationship is simply the operator
+ * that should be used to join the parent expression and the subitem
+ * name to form the full subitem expression; for objects, it's ".", and
+ * for lists it's null (because for lists the subitem names will include
+ * brackets). 'speculative' is passed to dbgcompile; see the comments
+ * there for information on the purpose of this flag.
+ */
+int dbgevalext(dbgcxdef *ctx, char *expr,
+ void (*dispfn)(void *dispctx, const char *str, int strl),
+ void *dispctx, int level, int showtype, dattyp *dat,
+ void (*aggcb)(void *aggctx, const char *subname,
+ int subnamelen, const char *relationship),
+ void *aggctx, int speculative);
+
+/*
+ * enumerate local variables at a given stack context level by calling
+ * the given function once for each local variable
+ */
+void dbgenumlcl(dbgcxdef *ctx, int level,
+ void (*func)(void *ctx, const char *lclnam, size_t lclnamlen),
+ void *cbctx);
+
+/*
+ * Compile an expression in a given frame context. Returns an error
+ * number. Allocates a new object to contain the compiled code, and
+ * returns the object number in *objn; the caller is responsible for
+ * freeing the object when done with it.
+ *
+ * If 'speculative' is set to true, we'll prohibit the expression from
+ * making any assignments or calling any methods or functions. This
+ * mode can be used to try compiling an expression that the user could
+ * conceivably be interested in but has not expressly evaluated; for
+ * example, this can be used to implement "tooltip evaluation," where
+ * the debugger automatically shows a little pop-up window with the
+ * expression under the mouse cursor if the mouse cursor is left
+ * hovering over some text for a few moments. In such cases, since the
+ * user hasn't explicitly requested evaluation, it would be bad to make
+ * any changes to game state, hence the prohibition of assignments or
+ * calls.
+ */
+int dbgcompile(dbgcxdef *ctx, char *expr, dbgfdef *fr, objnum *objn,
+ int speculative);
+
+/* display a stack traceback through a user callback */
+void dbgstktr(dbgcxdef *ctx,
+ void (*dispfn)(void *dispctx, const char *str, int strl),
+ void *dispctx, int level, int toponly, int include_markers);
+
+/* format a display of where execution is stopped into a buffer */
+void dbgwhere(dbgcxdef *ctx, char *buf);
+
+/* set a watch expression; returns error or 0 for success */
+int dbgwxset(dbgcxdef *ctx, char *expr, int *wxnum, int level);
+
+/* delete a watch expression */
+int dbgwxdel(dbgcxdef *ctx, int wxnum);
+
+/* update all watch expressions */
+void dbgwxupd(dbgcxdef *ctx,
+ void (*dispfn)(void *dispctx, const char *txt, int len),
+ void *dispctx);
+
+/* switch to a new active lindef */
+void dbgswitch(struct lindef **linp, struct lindef *newlin);
+
+
+
+/* ======================================================================== */
+/*
+ * User Interface Routines. The routines are called by the debugger
+ * to perform user interaction.
+ */
+
+/*
+ * Debugger user interface initialization, phase one. TADS calls this
+ * routine during startup, before reading the .GAM file, to let the user
+ * interface perform any initialization it requires before the .GAM file
+ * is loaded.
+ */
+void dbguini(dbgcxdef *ctx, const char *game_filename);
+
+/*
+ * Debugger user interface initialization, phase two. TADS calls this
+ * routine during startup, after read the .GAM file. The debugger user
+ * interface code can perform any required initialization that depends
+ * on the .GAM file having been read.
+ */
+void dbguini2(dbgcxdef *ctx);
+
+/*
+ * Determine if the debugger can resume from a run-time error. This
+ * reflects the capabilities of the user interface of the debugger. In
+ * particular, if the UI provides a way to change the instruction
+ * pointer, then the debugger can resume from an error, since the user
+ * can always move past the run-time error and continue execution. If
+ * the UI doesn't let the user change the instruction pointer, resuming
+ * from an error won't work, since the program will keep hitting the
+ * same error and re-entering the debugger. If this returns false, the
+ * run-time will trap to the debugger on an error, but will simply abort
+ * the current command when the debugger returns. If this returns true,
+ * the run-time will trap to the debugger on an error with the
+ * instruction pointer set back to the start of the line containing the
+ * error, and will thus re-try the same line of code when the debugger
+ * returns, unless the debugger explicitly moves the instruction pointer
+ * before returning.
+ */
+int dbgu_err_resume(dbgcxdef *ctx);
+
+/*
+ * Find a source file. origname is the name of the source file as it
+ * appears in the game's debugging information; this routine should
+ * figure out where the file actually is, and put the fully-qualified
+ * path to the file in fullname. The debugger calls this after it
+ * exhausts all of its other methods of finding a source file (such as
+ * searching the include path).
+ *
+ * Return true if the source file should be considered valid, false if
+ * not. Most implementations will simply return true if the file was
+ * found, false if not; however, this approach will cause the debugger
+ * to terminate with an error at start-up if the user hasn't set up the
+ * debugger's include path correctly before running the debugger. Some
+ * implementations, in particular GUI implementations, may wish to wait
+ * to find a file until the file is actually needed, rather than pester
+ * the user with file search dialogs repeatedly at start-up.
+ *
+ * must_find_file specifies how to respond if we can't find the file.
+ * If must_find_file is true, we should always return false if we can't
+ * find the file. If must_find_file is false, however, we can
+ * optionally return true even if we can't find the file. Doing so
+ * indicates that the debugger UI will defer locating the file until it
+ * is actually needed.
+ *
+ * If this routine returns true without actually finding the file, it
+ * should set fullname[0] to '\0' to indicate that fullname doesn't
+ * contain a valid filename.
+ */
+int dbgu_find_src(const char *origname, int origlen,
+ char *fullname, size_t full_len, int must_find_file);
+
+
+/*
+ * Debugger user interface main command loop. If err is non-zero, the
+ * debugger was entered because a run-time error occurred; otherwise, if
+ * bphit is non-zero, it's the number of the breakpoint that was
+ * encountered; otherwise, the debugger was entered through a
+ * single-step of some kind. exec_ofs is the byte offset within the
+ * target object of the next instruction to be executed. This can be
+ * changed upon return, in which case execution will continue from the
+ * new offset, but the offset must be within the same method of the same
+ * object (or within the same function) as it was upon entry.
+ */
+void dbgucmd(dbgcxdef *ctx, int bphit, int err, unsigned int *exec_ofs);
+
+/*
+ * Debugger UI - quitting game. The runtime calls this routine just
+ * before the play loop is about to terminate after the game code has
+ * called the "quit" built-in function. If the debugger wants, it can
+ * take control here (just as with dbgucmd()) for as long as it wants.
+ * If the debugger wants to restart the game, it should call bifrst().
+ * If this routine returns without signalling a RUN_RESTART error, TADS
+ * will terminate. If a RUN_RESTART error is signalled, TADS will
+ * resume the play loop.
+ */
+void dbguquitting(dbgcxdef *ctx);
+
+/*
+ * debugger user interface termination - this routine is called when the
+ * debugger is about to terminate, so that the user interface can close
+ * itself down (close windows, release memory, etc)
+ */
+void dbguterm(dbgcxdef *ctx);
+
+/*
+ * Debugger user interface: display an error. This is called mainly so
+ * that the debugger can display an error using special output
+ * formatting if the error occurs while debugging.
+ */
+void dbguerr(dbgcxdef *ctx, int errnum, char *msg);
+
+/* turn hidden output tracing on/off */
+void trchid(void);
+void trcsho(void);
+
+
+/* ======================================================================== */
+/*
+ * optional debugger macros - these compile to nothing when compiling a
+ * version for use without the debugger
+ */
+
+#ifdef DBG_OFF
+# define dbgenter(ctx, bp, self, target, prop, binum, argc)
+# define dbgleave(ctx, exittype)
+# define dbgdump(ctx)
+# define dbgrst(ctx) ((void)0)
+# define dbgframe(ctx, frofs, linofs)
+# define dbgssi(ctx, ofs, instr, err, p)
+#else /* DBG_OFF */
+# define dbgenter(ctx, bp, self, target, prop, binum, argc) \
+ dbgent(ctx, bp, self, target, prop, binum, argc)
+# define dbgleave(ctx, exittype) dbglv(ctx, exittype)
+# define dbgdump(ctx) dbgds(ctx)
+# define dbgrst(ctx) ((ctx)->dbgcxfcn = (ctx)->dbgcxdep = 0)
+# define dbgframe(ctx, frofs, linofs) \
+ (((ctx)->dbgcxfrm[(ctx)->dbgcxfcn - 1].dbgffr = (frofs)), \
+ ((ctx)->dbgcxfrm[(ctx)->dbgcxfcn - 1].dbgflin = (linofs)))
+# define dbgssi(ctx, ofs, instr, err, p) dbgss(ctx, ofs, instr, err, p)
+#endif /* DBG_OFF */
+
+/* ======================================================================== */
+/* private internal routines */
+
+void dbgent(dbgcxdef *ctx, struct runsdef *bp, objnum self, objnum target,
+ prpnum prop, int binum, int argc);
+
+void dbglv(dbgcxdef *ctx, int exittype);
+
+void dbgds(dbgcxdef *ctx);
+
+void dbgss(dbgcxdef *ctx, uint ofs, int instr, int err, uchar *noreg *p);
+
+void dbgpval(struct dbgcxdef *ctx, struct runsdef *val,
+ void (*dispfn)(void *, const char *, int),
+ void *dispctx, int showtype);
+
+int dbgtabsea(struct toktdef *tab, char *name, int namel, int hash,
+ struct toksdef *ret);
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/error.cpp b/engines/glk/tads/tads2/error.cpp
new file mode 100644
index 0000000000..793ddccb1d
--- /dev/null
+++ b/engines/glk/tads/tads2/error.cpp
@@ -0,0 +1,32 @@
+/* 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/error.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/error.h b/engines/glk/tads/tads2/error.h
new file mode 100644
index 0000000000..96831068d7
--- /dev/null
+++ b/engines/glk/tads/tads2/error.h
@@ -0,0 +1,390 @@
+/* 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.
+ *
+ */
+
+/* Error handling
+ * This package defines a set of macros that allows code to raise and
+ * handle exceptions.A macro is provided which signals an error, which
+ * does a non - local goto to the innermost enclosing exception handler.
+ * A set of macros sets up exception handling code.
+ *
+ * To catch exceptions that occur inside a block of code(i.e., in the
+ * code or in any subroutines called by the code), begin the block with
+ * ERRBEGIN.At the end of the protected code, place the exception
+ * handler, which starts with ERRCATCH.At the end of the exception
+ * handler, place ERREND.If no exception occurs, execution goes
+ * through the protected code, then resumes at the code following
+ * the ERREND.
+ *
+ * The exception handler can signal another error, which will cause
+ * the next enclosing frame to catch the error.Alternatively, if
+ * the exception handler doesn't signal an error or return, execution
+ * continues at the code following the ERREND.Exceptions that are
+ * signalled during exception handling will be caught by the next
+ * enclosing frame, unless the exception handler code is itself
+ * protected by another ERRBEGIN - ERREND block.
+ *
+ * To signal an error, use errsig().
+ *
+ * To use a string argument in a signalled error, cover the string
+ * with errstr(ctx, str, len); for example:
+ *
+ * errsig1(ctx, ERR_XYZ, ERRTSTR, errstr(ctx, buf, strlen(buf)));
+ *
+ * This copies the string into a buffer that is unaffected by
+ * stack resetting during error signalling.
+ */
+
+#ifndef GLK_TADS_TADS2_ERROR
+#define GLK_TADS_TADS2_ERROR
+
+#include "glk/tads/tads2/lib.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/*
+ * for compatility with old facility-free mechanism, signal with
+ * facility "TADS"
+ */
+#define errsig(ctx, err) errsigf(ctx, "TADS", err)
+#define errsig1(c, e, t, a) errsigf1(c,"TADS",e,t,a)
+#define errsig2(c, e, t1, a1, t2, a2) errsigf2(c,"TADS",e,t1,a1,t2,a2)
+#define errlog(c, e) errlogf(c, "TADS", e)
+#define errlog1(c, e, t, a) errlogf1(c,"TADS",e,t,a)
+#define errlog2(c, e, t1, a1, t2, a2) errlogf2(c,"TADS",e,t1,a1,t2,a2)
+
+
+
+/*---------------------------- TADS Error Codes ----------------------------*/
+/* memory/cache manager errors */
+#define ERR_NOMEM 1 /* out of memory */
+#define ERR_FSEEK 2 /* error seeking in file */
+#define ERR_FREAD 3 /* error reading from file */
+#define ERR_NOPAGE 4 /* no more page slots */
+#define ERR_REALCK 5 /* attempting to reallocate a locked object */
+#define ERR_SWAPBIG 6 /* swapfile limit reached - out of virtual memory */
+#define ERR_FWRITE 7 /* error writing file */
+#define ERR_SWAPPG 8 /* exceeded swap page table limit */
+#define ERR_CLIUSE 9 /* requested client object number already in use */
+#define ERR_CLIFULL 10 /* client mapping table is full */
+#define ERR_NOMEM1 11 /* swapping/garbage collection failed to find enuf */
+#define ERR_NOMEM2 12 /* no memory to resize (expand) an object */
+#define ERR_OPSWAP 13 /* unable to open swap file */
+#define ERR_NOHDR 14 /* can't get a new object header */
+#define ERR_NOLOAD 15 /* mcm cannot find object to load (internal error) */
+#define ERR_LCKFRE 16 /* attempting to free a locked object (internal) */
+#define ERR_INVOBJ 17 /* invalid object */
+#define ERR_BIGOBJ 18 /* object too big - exceeds memory allocation limit */
+
+/* lexical analysis errors */
+#define ERR_INVTOK 100 /* invalid token */
+#define ERR_STREOF 101 /* end of file while scanning string */
+#define ERR_TRUNC 102 /* symbol too long - truncated */
+#define ERR_NOLCLSY 103 /* no space in local symbol table */
+#define ERR_PRPDIR 104 /* invalid preprocessor (#) directive */
+#define ERR_INCNOFN 105 /* no filename in #include directive */
+#define ERR_INCSYN 106 /* invalid #include syntax */
+#define ERR_INCSEAR 107 /* can't find included file */
+#define ERR_INCMTCH 108 /* no matching delimiter in #include filename */
+#define ERR_MANYSYM 109 /* out of space for symbol table */
+#define ERR_LONGLIN 110 /* line too long */
+#define ERR_INCRPT 111 /* header file already included - ignored */
+#define ERR_PRAGMA 112 /* unknown pragma (ignored) */
+#define ERR_BADPELSE 113 /* unexpected #else */
+#define ERR_BADENDIF 114 /* unexpected #endif */
+#define ERR_BADELIF 115 /* unexpected #elif */
+#define ERR_MANYPIF 116 /* #if nesting too deep */
+#define ERR_DEFREDEF 117 /* #define symbol already defined */
+#define ERR_PUNDEF 118 /* #undef symbol not defined */
+#define ERR_NOENDIF 119 /* missing #endif */
+#define ERR_MACNEST 120 /* macros nested too deeply */
+#define ERR_BADISDEF 121 /* invalid argument for defined() operator */
+#define ERR_PIF_NA 122 /* #if is not implemented */
+#define ERR_PELIF_NA 123 /* #elif is not implemented */
+#define ERR_P_ERROR 124 /* error directive: %s */
+#define ERR_LONG_FILE_MACRO 125 /* __FILE__ expansion too long */
+#define ERR_LONG_LINE_MACRO 126 /* __LINE__ expansion too long */
+
+/* undo errors */
+#define ERR_UNDOVF 200 /* operation is too big for undo log */
+#define ERR_NOUNDO 201 /* no more undo information */
+#define ERR_ICUNDO 202 /* incomplete undo (no previous savepoint) */
+
+/* parser errors */
+#define ERR_REQTOK 300 /* expected token (arg 1) - found something else */
+#define ERR_REQSYM 301 /* expected symbol */
+#define ERR_REQPRP 302 /* expected a property name */
+#define ERR_REQOPN 303 /* expected operand */
+#define ERR_REQARG 304 /* expected comma or closing paren (arg list) */
+#define ERR_NONODE 305 /* no space for new parse node */
+#define ERR_REQOBJ 306 /* epxected object name */
+#define ERR_REQEXT 307 /* redefining symbol as external function */
+#define ERR_REQFCN 308 /* redefining symbol as function */
+#define ERR_NOCLASS 309 /* can't use CLASS with function/external function */
+#define ERR_REQUNO 310 /* required unary operator */
+#define ERR_REQBIN 311 /* required binary operator */
+#define ERR_INVBIN 312 /* invalid binary operator */
+#define ERR_INVASI 313 /* invalid assignment */
+#define ERR_REQVAR 314 /* required variable name */
+#define ERR_LCLSYN 315 /* required comma or semicolon in local list */
+#define ERR_REQRBR 316 /* required right brace (eof before end of group) */
+#define ERR_BADBRK 317 /* 'break' without 'while' */
+#define ERR_BADCNT 318 /* 'continue' without 'while' */
+#define ERR_BADELS 319 /* 'else' without 'if' */
+#define ERR_WEQASI 320 /* warning: possible use of '=' where ':=' intended */
+#define ERR_EOF 321 /* unexpected end of file */
+#define ERR_SYNTAX 322 /* general syntax error */
+#define ERR_INVOP 323 /* invalid operand type */
+#define ERR_NOMEMLC 324 /* no memory for new local symbol table */
+#define ERR_NOMEMAR 325 /* no memory for argument symbol table */
+#define ERR_FREDEF 326 /* redefining a function which is already defined */
+#define ERR_NOSW 327 /* 'case' or 'default' and not in switch block */
+#define ERR_SWRQCN 328 /* constant required in switch case value */
+#define ERR_REQLBL 329 /* label required for 'goto' */
+#define ERR_NOGOTO 330 /* 'goto' label never defined */
+#define ERR_MANYSC 331 /* too many superclasses for object */
+#define ERR_OREDEF 332 /* redefining symbol as object */
+#define ERR_PREDEF 333 /* property being redefined in object */
+#define ERR_BADPVL 334 /* invalid property value */
+#define ERR_BADVOC 335 /* bad vocabulary property value */
+#define ERR_BADTPL 336 /* bad template property value (need sstring) */
+#define ERR_LONGTPL 337 /* template base property name too long */
+#define ERR_MANYTPL 338 /* too many templates (internal compiler limit) */
+#define ERR_BADCMPD 339 /* bad value for compound word (sstring required) */
+#define ERR_BADFMT 340 /* bad value for format string (sstring needed) */
+#define ERR_BADSYN 341 /* invalid value for synonym (sstring required) */
+#define ERR_UNDFSYM 342 /* undefined symbol */
+#define ERR_BADSPEC 343 /* bad special word */
+#define ERR_NOSELF 344 /* "self" not valid in this context */
+#define ERR_STREND 345 /* warning: possible unterminated string */
+#define ERR_MODRPLX 346 /* modify/replace not allowed with external func */
+#define ERR_MODFCN 347 /* modify not allowed with function */
+#define ERR_MODFWD 348 /* modify/replace not allowed with forward func */
+#define ERR_MODOBJ 349 /* modify can only be used with a defined object */
+#define ERR_RPLSPEC 350 /* warning - replacing specialWords */
+#define ERR_SPECNIL 351 /* nil only allowed with modify specialWords */
+#define ERR_BADLCL 353 /* 'local' statement must precede executable code */
+#define ERR_IMPPROP 354 /* implied verifier '%s' is not a property */
+#define ERR_BADTPLF 355 /* invalid command template flag */
+#define ERR_NOTPLFLG 356 /* flags are not allowed with old file format */
+#define ERR_AMBIGBIN 357 /* warning: operator '%s' could be binary */
+#define ERR_PIA 358 /* warning: possibly incorrect assignment */
+#define ERR_BADSPECEXPR 359 /* invalid speculation evaluation */
+
+/* code generation errors */
+#define ERR_OBJOVF 400 /* object cannot grow any bigger - code too big */
+#define ERR_NOLBL 401 /* no more temporary labels/fixups */
+#define ERR_LBNOSET 402 /* (internal error) label never set */
+#define ERR_INVLSTE 403 /* invalid datatype for list element */
+#define ERR_MANYDBG 404 /* too many debugger line records (internal limit) */
+
+/* vocabulary setup errors */
+#define ERR_VOCINUS 450 /* vocabulary being redefined for object */
+#define ERR_VOCMNPG 451 /* too many vocwdef pages (internal limit) */
+#define ERR_VOCREVB 452 /* redefining same verb */
+#define ERR_VOCREVB2 453 /* redefining same verb - two arguments */
+
+/* set-up errors */
+#define ERR_LOCNOBJ 500 /* location of object %s is not an object */
+#define ERR_CNTNLST 501 /* contents of object %s is not list */
+#define ERR_SUPOVF 502 /* overflow trying to build contents list */
+#define ERR_RQOBJNF 503 /* required object %s not found */
+#define ERR_WRNONF 504 /* warning - object %s not found */
+#define ERR_MANYBIF 505 /* too many built-in functions (internal error) */
+
+/* fio errors */
+#define ERR_OPWGAM 600 /* unable to open game for writing */
+#define ERR_WRTGAM 601 /* error writing to game file */
+#define ERR_FIOMSC 602 /* too many sc's for writing in fiowrt */
+#define ERR_UNDEFF 603 /* undefined function */
+#define ERR_UNDEFO 604 /* undefined object */
+#define ERR_UNDEF 605 /* undefined symbols found */
+#define ERR_OPRGAM 606 /* unable to open game for reading */
+#define ERR_RDGAM 607 /* error reading game file */
+#define ERR_BADHDR 608 /* file has invalid header - not TADS game file */
+#define ERR_UNKRSC 609 /* unknown resource type in .gam file */
+#define ERR_UNKOTYP 610 /* unknown object type in OBJ resource */
+#define ERR_BADVSN 611 /* file saved by different incompatible version */
+#define ERR_LDGAM 612 /* error loading object on demand */
+#define ERR_LDBIG 613 /* object too big for load region (prob. internal) */
+#define ERR_UNXEXT 614 /* did not expect external function */
+#define ERR_WRTVSN 615 /* compiler cannot write the requested version */
+#define ERR_VNOCTAB 616 /* format version cannot be used with -ctab */
+#define ERR_BADHDRRSC 617 /* invalid resource file header in file %s */
+#define ERR_RDRSC 618 /* error reading resource file "xxx" */
+
+/* character mapping errors */
+#define ERR_CHRNOFILE 700 /* unable to load character mapping file */
+
+/* user interrupt */
+#define ERR_USRINT 990 /* user requested cancel of current operation */
+
+/* run-time errors */
+#define ERR_STKOVF 1001 /* stack overflow */
+#define ERR_HPOVF 1002 /* heap overflow */
+#define ERR_REQNUM 1003 /* numeric value required */
+#define ERR_STKUND 1004 /* stack underflow */
+#define ERR_REQLOG 1005 /* logical value required */
+#define ERR_INVCMP 1006 /* invalid datatypes for magnitude comparison */
+#define ERR_REQSTR 1007 /* string value required */
+#define ERR_INVADD 1008 /* invalid datatypes for '+' operator */
+#define ERR_INVSUB 1009 /* invalid datatypes for binary '-' operator */
+#define ERR_REQVOB 1010 /* require object value */
+#define ERR_REQVFN 1011 /* required function pointer */
+#define ERR_REQVPR 1012 /* required property number value */
+
+/* non-error conditions: run-time EXIT, ABORT, ASKIO, ASKDO */
+#define ERR_RUNEXIT 1013 /* 'exit' statement executed */
+#define ERR_RUNABRT 1014 /* 'abort' statement executed */
+#define ERR_RUNASKD 1015 /* 'askdo' statement executed */
+#define ERR_RUNASKI 1016 /* 'askio' executed; int arg 1 is prep */
+#define ERR_RUNQUIT 1017 /* 'quit' executed */
+#define ERR_RUNRESTART 1018 /* 'reset' executed */
+#define ERR_RUNEXITOBJ 1019 /* 'exitobj' executed */
+
+#define ERR_REQVLS 1020 /* list value required */
+#define ERR_LOWINX 1021 /* index value too low (must be >= 1) */
+#define ERR_HIGHINX 1022 /* index value too high (must be <= length(list)) */
+#define ERR_INVTBIF 1023 /* invalid type for built-in function */
+#define ERR_INVVBIF 1024 /* invalid value for built-in function */
+#define ERR_BIFARGC 1025 /* wrong number of arguments to built-in */
+#define ERR_ARGC 1026 /* wrong number of arguments to user function */
+#define ERR_FUSEVAL 1027 /* string/list not allowed for fuse/daemon arg */
+#define ERR_BADSETF 1028 /* internal error in setfuse/setdaemon/notify */
+#define ERR_MANYFUS 1029 /* too many fuses */
+#define ERR_MANYDMN 1030 /* too many daemons */
+#define ERR_MANYNFY 1031 /* too many notifiers */
+#define ERR_NOFUSE 1032 /* fuse not found in remfuse */
+#define ERR_NODMN 1033 /* daemon not found in remdaemon */
+#define ERR_NONFY 1034 /* notifier not found in unnotify */
+#define ERR_BADREMF 1035 /* internal error in remfuse/remdaemon/unnotify */
+#define ERR_DMDLOOP 1036 /* load-on-demand loop: property not being set */
+#define ERR_UNDFOBJ 1037 /* undefined object in vocabulary tree */
+#define ERR_BIFCSTR 1038 /* c-string conversion overflows buffer */
+#define ERR_INVOPC 1039 /* invalid opcode */
+#define ERR_RUNNOBJ 1040 /* runtime error: property taken of non-object */
+#define ERR_EXTLOAD 1041 /* unable to load external function "%s" */
+#define ERR_EXTRUN 1042 /* error executing external function "%s" */
+#define ERR_CIRCSYN 1043 /* circular synonym */
+#define ERR_DIVZERO 1044 /* divide by zero */
+#define ERR_BADDEL 1045 /* can only delete objects created with "new" */
+#define ERR_BADNEWSC 1046 /* superclass for "new" cannot be a new object */
+#define ERR_VOCSTK 1047 /* insufficient space in parser stack */
+#define ERR_BADFILE 1048 /* invalid file handle */
+
+#define ERR_RUNEXITPRECMD 1049 /* exited from preCommand */
+
+/* run-time parser errors */
+#define ERR_PRS_SENT_UNK 1200 /* sentence structure not recognized */
+#define ERR_PRS_VERDO_FAIL 1201 /* verDoVerb failed */
+#define ERR_PRS_VERIO_FAIL 1202 /* verIoVerb failed */
+#define ERR_PRS_NO_VERDO 1203 /* no verDoVerb for direct object */
+#define ERR_PRS_NO_VERIO 1204 /* no verIoVerb for direct object */
+#define ERR_PRS_VAL_DO_FAIL 1205 /* direct object validation failed */
+#define ERR_PRS_VAL_IO_FAIL 1206 /* indirect object validation failed */
+
+/* compiler/runtime/debugger driver errors */
+#define ERR_USAGE 1500 /* invalid usage */
+#define ERR_OPNINP 1501 /* error opening input file */
+#define ERR_NODBG 1502 /* game not compiled for debugging */
+#define ERR_ERRFIL 1503 /* unable to open error capture file */
+#define ERR_PRSCXSIZ 1504 /* parse pool + local size too large */
+#define ERR_STKSIZE 1505 /* stack size too large */
+#define ERR_OPNSTRFIL 1506 /* error opening string capture file */
+#define ERR_INVCMAP 1507 /* invalid character map file */
+
+/* debugger errors */
+#define ERR_BPSYM 2000 /* symbol not found for breakpoint */
+#define ERR_BPPROP 2002 /* breakpoint symbol is not a property */
+#define ERR_BPFUNC 2003 /* breakpoint symbol is not a function */
+#define ERR_BPNOPRP 2004 /* property is not defined for object */
+#define ERR_BPPRPNC 2005 /* property is not code */
+#define ERR_BPSET 2006 /* breakpoint already set at this location */
+#define ERR_BPNOTLN 2007 /* breakpoint is not at a line (OPCLINE instr) */
+#define ERR_MANYBP 2008 /* too many breakpoints */
+#define ERR_BPNSET 2009 /* breakpoint to be deleted was not set */
+#define ERR_DBGMNSY 2010 /* too many symbols in debug expression (int lim) */
+#define ERR_NOSOURC 2011 /* unable to find source file %s */
+#define ERR_WTCHLCL 2012 /* illegal to assign to local in watch expr */
+#define ERR_INACTFR 2013 /* inactive frame (expression value not available) */
+#define ERR_MANYWX 2014 /* too many watch expressions */
+#define ERR_WXNSET 2015 /* watchpoint not set */
+#define ERR_EXTRTXT 2016 /* extraneous text at end of command */
+#define ERR_BPOBJ 2017 /* breakpoint symbol is not an object */
+#define ERR_DBGINACT 2018 /* debugger is not active */
+#define ERR_BPINUSE 2019 /* breakpoint is already used */
+#define ERR_RTBADSPECEXPR 2020 /* invalid speculative expression */
+#define ERR_NEEDLIN2 2021 /* -ds2 information not found - must recompile */
+
+/* usage error messages */
+#define ERR_TCUS1 3000 /* first tc usage message */
+#define ERR_TCUSL 3024 /* last tc usage message */
+#define ERR_TCTGUS1 3030 /* first tc toggle message */
+#define ERR_TCTGUSL 3032
+#define ERR_TCZUS1 3040 /* first tc -Z suboptions usage message */
+#define ERR_TCZUSL 3041
+#define ERR_TC1US1 3050 /* first tc -1 suboptions usage message */
+#define ERR_TC1USL 3058
+#define ERR_TCMUS1 3070 /* first tc -m suboptions usage message */
+#define ERR_TCMUSL 3076
+#define ERR_TCVUS1 3080 /* first -v suboption usage message */
+#define ERR_TCVUSL 3082
+#define ERR_TRUSPARM 3099
+#define ERR_TRUS1 3100 /* first tr usage message */
+#define ERR_TRUSL 3117
+#define ERR_TRUSFT1 3118 /* first tr "footer" message */
+#define ERR_TRUSFTL 3119 /* last tr "footer" message */
+#define ERR_TRSUS1 3150 /* first tr -s suboptions usage message */
+#define ERR_TRSUSL 3157
+#define ERR_TDBUSPARM 3199
+#define ERR_TDBUS1 3200 /* first tdb usage message */
+#define ERR_TDBUSL 3214 /* last tdb usage message */
+
+/* TR 16-bit MSDOS-specific usage messages */
+#define ERR_TRUS_DOS_1 3300
+#define ERR_TRUS_DOS_L 3300
+
+/* TR 32-bit MSDOS console mode usage messages */
+#define ERR_TRUS_DOS32_1 3310
+#define ERR_TRUS_DOS32_L 3312
+
+/* TADS/Graphic errors */
+#define ERR_GNOFIL 4001 /* can't find graphics file %s */
+#define ERR_GNORM 4002 /* can't find room %s */
+#define ERR_GNOOBJ 4003 /* can't find hot spot object %s */
+#define ERR_GNOICN 4004 /* can't find icon object %s */
+
+
+/*
+ * Special error flag - this is returned from execmd() when preparseCmd
+ * returns a command list. This indicates to voc1cmd that it should try
+ * the command over again, using the words in the new list.
+ */
+#define ERR_PREPRSCMDREDO 30000 /* preparseCmd returned a list */
+#define ERR_PREPRSCMDCAN 30001 /* preparseCmd returned 'nil' to cancel */
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/error_handling.cpp b/engines/glk/tads/tads2/error_handling.cpp
new file mode 100644
index 0000000000..a2300ee834
--- /dev/null
+++ b/engines/glk/tads/tads2/error_handling.cpp
@@ -0,0 +1,32 @@
+/* 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 "error_handling.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/error_handling.h b/engines/glk/tads/tads2/error_handling.h
new file mode 100644
index 0000000000..a225bbea74
--- /dev/null
+++ b/engines/glk/tads/tads2/error_handling.h
@@ -0,0 +1,338 @@
+/* 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.
+ *
+ */
+
+/* Library error handling definitions
+ * All of the functions and macros in here are named ERRxxx because
+ * this file was based on the TADS err.h, which used the ERRxxx naming
+ * convention, and it would be a lot of trouble to change.
+ *
+ * This package defines a set of macros that allows code to raise and
+ * handle exceptions. A macro is provided which signals an error, which
+ * does a non-local goto to the innermost enclosing exception handler.
+ * A set of macros sets up exception handling code.
+ *
+ * To catch exceptions that occur inside a block of code (i.e., in the
+ * code or in any subroutines called by the code), begin the block with
+ * ERRBEGIN. At the end of the protected code, place the exception
+ * handler, which starts with ERRCATCH. At the end of the exception
+ * handler, place ERREND. If no exception occurs, execution goes
+ * through the protected code, then resumes at the code following
+ * the ERREND.
+ *
+ * The exception handler can signal another error, which will cause
+ * the next enclosing frame to catch the error. Alternatively, if
+ * the exception handler doesn't signal an error or return, execution
+ * continues at the code following the ERREND. Exceptions that are
+ * signalled during exception handling will be caught by the next
+ * enclosing frame, unless the exception handler code is itself
+ * protected by another ERRBEGIN-ERREND block.
+ *
+ * To signal an error, use errsig().
+ *
+ * To use a string argument in a signalled error, cover the string
+ * with errstr(ctx, str, len); for example:
+ *
+ * errsig1(ctx, ERR_XYZ, ERRTSTR, errstr(ctx, buf, strlen(buf)));
+ *
+ * This copies the string into a buffer that is unaffected by
+ * stack resetting during error signalling.
+ */
+
+#ifndef GLK_TADS_TADS2_ERROR_HANDLING
+#define GLK_TADS_TADS2_ERROR_HANDLING
+
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/osfrobtads.h"
+
+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
+ */
+#define ERRFACMAX 6
+
+union erradef {
+ int erraint; /* integer argument */
+ char *errastr; /* text string argument */
+};
+
+struct errdef {
+ struct errdef *errprv; /* previous error frame */
+ int errcode; /* error code of exception being handled */
+ 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 */
+};
+
+#define ERRBUFSIZ 512
+
+/**
+ * Seek location record for an error message by number
+ */
+struct errmfdef {
+ uint errmfnum; /* error number */
+ ulong errmfseek; /* seek location of this message */
+};
+typedef struct errmfdef errmfdef;
+
+struct errcxdef {
+ errdef *errcxptr; /* current error frame */
+ void (*errcxlog)(void *, char *fac, int err, int argc, erradef *);
+ /* error logging callback function */
+ void *errcxlgc; /* context for error logging callback */
+ int errcxofs; /* offset in argument buffer */
+ char errcxbuf[ERRBUFSIZ]; /* space for argument strings */
+ osfildef *errcxfp; /* message file, if one is being used */
+ errmfdef *errcxseek; /* seek locations of messages in file */
+ uint errcxsksz; /* size of errcxseek array */
+ ulong errcxbase; /* offset in physical file of logical error file */
+ struct appctxdef *errcxappctx; /* host application context */
+};
+
+/**
+ * Begin protected code
+ */
+#define ERRBEGIN(ctx) \
+ { \
+ errdef fr_; \
+ if ((fr_.errcode = setjmp(fr_.errbuf)) == 0) \
+ { \
+ fr_.errprv = (ctx)->errcxptr; \
+ (ctx)->errcxptr = &fr_;
+
+/**
+ * End protected code, begin error handler
+ */
+#define ERRCATCH(ctx, e) \
+ assert(1==1 && (ctx)->errcxptr != fr_.errprv); \
+ (ctx)->errcxptr = fr_.errprv; \
+ } \
+ else \
+ { \
+ assert(2==2 && (ctx)->errcxptr != fr_.errprv); \
+ (e) = fr_.errcode; \
+ (ctx)->errcxptr = fr_.errprv;
+
+/* retrieve argument (int, string) in current error frame */
+#define errargint(argnum) (fr_.erraav[argnum].erraint)
+#define errargstr(argnum) (fr_.erraav[argnum].errastr)
+
+
+#define ERREND(ctx) \
+ } \
+ }
+
+/* end protected code, begin cleanup (no handling; just cleaning up) */
+#define ERRCLEAN(ctx) \
+ assert((ctx)->errcxptr != fr_.errprv); \
+ (ctx)->errcxptr = fr_.errprv; \
+ } \
+ else \
+ { \
+ assert((ctx)->errcxptr != fr_.errprv); \
+ (ctx)->errcxptr = fr_.errprv;
+
+#define ERRENDCLN(ctx) \
+ errrse(ctx); \
+ } \
+ }
+
+
+
+/* argument types for errors with arguments */
+#define ERRTINT erraint
+#define ERRTSTR errastr
+
+/* set argument count in error frame */
+#define errargc(ctx,cnt) ((ctx)->errcxptr->erraac=(cnt))
+
+/* enter string argument; returns pointer to argument used in errargv */
+#ifdef ERR_NO_MACRO
+char *errstr(errcxdef *ctx, const char *str, int len);
+#else /* ERR_NO_MACRO */
+
+#define errstr(ctx,str,len) \
+ ((memcpy(&(ctx)->errcxbuf[(ctx)->errcxofs],str,(size_t)len), \
+ (ctx)->errcxofs += (len), \
+ (ctx)->errcxbuf[(ctx)->errcxofs++] = '\0'), \
+ &(ctx)->errcxbuf[(ctx)->errcxofs-(len)-1])
+
+#endif /* ERR_NO_MACRO */
+
+/* set argument in error frame argument vector */
+#define errargv(ctx,index,typ,arg) \
+ ((ctx)->errcxptr->erraav[index].typ=(arg))
+
+/* signal an error with argument count already set */
+#ifdef ERR_NO_MACRO
+void errsign(errcxdef *ctx, int e, char *facility);
+#else /* ERR_NO_MACRO */
+# ifdef DEBUG
+void errjmp(jmp_buf buf, int e);
+# define errsign(ctx, e, fac) \
+ (strncpy((ctx)->errcxptr->errfac, fac, ERRFACMAX),\
+ (ctx)->errcxptr->errfac[ERRFACMAX]='\0',\
+ (ctx)->errcxofs=0, errjmp((ctx)->errcxptr->errbuf, e))
+# else /* DEBUG */
+# define errsign(ctx, e, fac) \
+ (strncpy((ctx)->errcxptr->errfac, fac, ERRFACMAX),\
+ (ctx)->errcxptr->errfac[ERRFACMAX]='\0',\
+ (ctx)->errcxofs=0, longjmp((ctx)->errcxptr->errbuf, e))
+# endif /* DEBUG */
+#endif /* ERR_NO_MACRO */
+
+
+/* signal an error with no arguments */
+#ifdef ERR_NO_MACRO
+void errsigf(errcxdef *ctx, char *facility, int err);
+#else /* ERR_NO_MACRO */
+#define errsigf(ctx, fac, e) (errargc(ctx,0),errsign(ctx,e,fac))
+#endif /* ERR_NO_MACRO */
+
+/* signal an error with one argument */
+#define errsigf1(ctx, fac, e, typ1, arg1) \
+ (errargv(ctx,0,typ1,arg1),errargc(ctx,1),errsign(ctx,e,fac))
+
+/* signal an error with two arguments */
+#define errsigf2(ctx, fac, e, typ1, arg1, typ2, arg2) \
+ (errargv(ctx,0,typ1,arg1), errargv(ctx,1,typ2,arg2), \
+ errargc(ctx,2), errsign(ctx,e,fac))
+
+/* resignal the current error - only usable within exception handlers */
+#ifdef ERR_NO_MACRO
+void errrse1(errcxdef *ctx, errdef *fr);
+# define errrse(ctx) errrse1(ctx, &fr_)
+#else /* ERR_NO_MACRO */
+
+/* void errrse(errcxdef *ctx); */
+# define errrse(ctx) \
+ (errargc(ctx, fr_.erraac),\
+ memcpy((ctx)->errcxptr->erraav, fr_.erraav, \
+ (size_t)(fr_.erraac*sizeof(erradef))),\
+ errsign(ctx, fr_.errcode, fr_.errfac))
+
+#endif /* ERR_NO_MACRO */
+
+/*
+ * For use in an error handler (ERRCATCH..ERREND) only: Copy the
+ * parameters from the error currently being handled to the enclosing
+ * frame. This is useful when "keeping" an error being handled - i.e.,
+ * the arguments will continue to be used outside of the
+ * ERRCATCH..ERREND code.
+ */
+/* void errkeepargs(errcxdef *ctx); */
+#define errkeepargs(ctx) errcopyargs(ctx, &fr_)
+
+/*
+ * copy the parameters for an error from another frame into the current
+ * frame - this can be used when we want to be able to display an error
+ * that occurred in an inner frame within code that is protected by a
+ * new enclosing error frame
+ */
+/* void errcopyargs(errcxdef *ctx, errdef *fr); */
+#define errcopyargs(ctx, fr) \
+ (errargc((ctx), (fr)->erraac), \
+ memcpy((ctx)->errcxptr->erraav, (fr)->erraav, \
+ (size_t)((fr)->erraac*sizeof(erradef))))
+
+/* log error that's been caught, using arguments already caught */
+#define errclog(ctx) \
+ ((*(ctx)->errcxlog)((ctx)->errcxlgc,fr_.errfac,fr_.errcode,\
+ fr_.erraac,fr_.erraav))
+
+/* log an error that's been set up but not signalled yet */
+#define errprelog(ctx, err) \
+ ((*(ctx)->errcxlog)((ctx)->errcxlgc,(ctx)->errcxptr->errfac,\
+ err,(ctx)->errcxptr->erraac,\
+ (ctx)->errcxptr->erraav))
+
+/* log an error (no signalling, just reporting) */
+#ifdef ERR_NO_MACRO
+void errlogn(errcxdef *ctx, int err, char *facility);
+#else /* ERR_NO_MACRO */
+
+#define errlogn(ctx,err,fac) \
+ ((ctx)->errcxofs=0,\
+ (*(ctx)->errcxlog)((ctx)->errcxlgc,fac,err,(ctx)->errcxptr->erraac,\
+ (ctx)->errcxptr->erraav))
+
+#endif /* ERR_NO_MACRO */
+
+/* log an error with no arguments */
+#ifdef ERR_NO_MACRO
+void errlogf(errcxdef *ctx, char *facility, int err);
+#else /* ERR_NO_MACRO */
+
+/* void errlogf(errcxdef *ctx, char *facility, int err); */
+#define errlogf(ctx,fac,err) (errargc(ctx,0),errlogn(ctx,err,fac))
+
+#endif /* ERR_NO_MACRO */
+
+/* log an error with one argument */
+#define errlogf1(ctx, fac, e, typ1, arg1) \
+ (errargv(ctx,0,typ1,arg1),errargc(ctx,1),errlogn(ctx,e,fac))
+
+/* log an error with two arguments */
+#define errlogf2(ctx, fac, e, typ1, arg1, typ2, arg2) \
+ (errargv(ctx,0,typ1,arg1),errargv(ctx,1,typ2,arg2),\
+ errargc(ctx,2),errlogn(ctx,e,fac))
+
+
+/*
+ * Format an error message, sprintf-style, using arguments in an
+ * erradef array (which is passed to the error-logging callback).
+ * Returns the length of the output string, even if the actual
+ * output string was truncated because the outbuf was too short.
+ * (If called with outbufl == 0, nothing will be written out, but
+ * the size of the buffer needed, minus the terminating null byte,
+ * will be computed and returned.)
+ */
+int errfmt(char *outbuf, int outbufl, char *fmt, int argc,
+ erradef *argv);
+
+/* get the text of an error */
+void errmsg(errcxdef *ctx, char *outbuf, uint outbufl, uint err);
+
+/* initialize error subsystem, opening error message file if necessary */
+void errini(errcxdef *ctx, osfildef *fp);
+
+/* allocate and initialize error context, free error context */
+errcxdef *lerini();
+void lerfre(errcxdef *ctx);
+
+/* error message structure - number + text */
+struct errmdef {
+ uint errmerr; /* error number */
+ char *errmtxt; /* text of error message */
+};
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/file_io.cpp b/engines/glk/tads/tads2/file_io.cpp
new file mode 100644
index 0000000000..581601fbf0
--- /dev/null
+++ b/engines/glk/tads/tads2/file_io.cpp
@@ -0,0 +1,32 @@
+/* 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/file_io.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/file_io.h b/engines/glk/tads/tads2/file_io.h
new file mode 100644
index 0000000000..8599c4909f
--- /dev/null
+++ b/engines/glk/tads/tads2/file_io.h
@@ -0,0 +1,137 @@
+/* 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.
+ *
+ */
+
+/*
+ * file i/o interface
+ */
+
+#ifndef GLK_TADS_TADS2_FILE_IO
+#define GLK_TADS_TADS2_FILE_IO
+
+#include "glk/tads/tads2/lib.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* forward declarations */
+struct voccxdef;
+
+/* load-on-demand context (passed in by mcm in load callback) */
+typedef struct fiolcxdef fiolcxdef;
+struct fiolcxdef
+{
+ osfildef *fiolcxfp; /* file pointer of load file */
+ errcxdef *fiolcxerr; /* error handling context */
+ ulong fiolcxst; /* starting offset in file */
+ uint fiolcxflg; /* flags from original load file */
+ uint fiolcxseed; /* fioxor seed */
+ uint fiolcxinc; /* fioxor increment */
+};
+
+/* write game to binary file */
+void fiowrt(struct mcmcxdef *mctx, voccxdef *vctx,
+ struct tokcxdef *tokctx, struct tokthdef *tab,
+ uchar *fmts, uint fmtl, char *fname, uint flags, objnum preinit,
+ int extc, uint prpcnt, char *filever);
+
+/* flag values for use with fiowrt */
+#define FIOFSYM 0x01 /* include symbol table in output file */
+#define FIOFLIN 0x02 /* include source file tracking information */
+#define FIOFPRE 0x04 /* preinit needs to be run after reading game */
+#define FIOFCRYPT 0x08 /* "encrypt" objects prior to writing them */
+#define FIOFBIN 0x10 /* writing precompiled header */
+#define FIOFFAST 0x20 /* fast-load records are in file */
+#define FIOFCASE 0x40 /* case folding was turned on in original compile */
+#define FIOFLIN2 0x80 /* new-style line records */
+
+/* read game from binary file; sets up loader callback context */
+void fiord(struct mcmcxdef *mctx, voccxdef *vctx,
+ struct tokcxdef *tctx,
+ char *fname, char *exename,
+ struct fiolcxdef *setupctx, objnum *preinit, uint *flagp,
+ struct tokpdef *path, uchar **fmtsp, uint *fmtlp,
+ uint *pcntptr, int flags, struct appctxdef *appctx,
+ char *argv0);
+
+/* shut down load-on-demand subsystem, close load file */
+void fiorcls(fiolcxdef *ctx);
+
+/* loader callback - load an object on demand */
+void OS_LOADDS fioldobj(void *ctx, mclhd handle, uchar *ptr,
+ ushort siz);
+
+/*
+ * Save a game - returns TRUE on failure. We'll save the file to
+ * 'fname'. 'game_fname' is the name of the game file; if this is not
+ * null, we'll save it to the saved game file so that the player can
+ * later start the game by specifying only the saved game file to the
+ * run-time. 'game_fname' can be null, in which case we'll omit the
+ * game file information.
+ */
+int fiosav(voccxdef *vctx, char *fname, char *game_fname);
+
+/*
+ * fiorso() result codes
+ */
+#define FIORSO_SUCCESS 0 /* success */
+#define FIORSO_FILE_NOT_FOUND 1 /* file not found */
+#define FIORSO_NOT_SAVE_FILE 2 /* not a saved game file */
+#define FIORSO_BAD_FMT_VSN 3 /* incompatible file format version */
+#define FIORSO_BAD_GAME_VSN 4 /* file saved by another game or version */
+#define FIORSO_READ_ERROR 5 /* error reading from the file */
+#define FIORSO_NO_PARAM_FILE 6 /* no parameter file (for restore(nil)) */
+
+/* restore a game - returns TRUE on failure */
+int fiorso(voccxdef *vctx, char *fname);
+
+/*
+ * Look in a saved game file to determine if it has information on which
+ * GAM file created it. If the GAM file information is available, this
+ * routine returns true and stores the game file name in the given
+ * buffer; if the information isn't available, we'll return false.
+ */
+int fiorso_getgame(char *saved_file, char *buf, size_t buflen);
+
+/* encrypt/decrypt an object */
+void fioxor(uchar *p, uint siz, uint seed, uint inc);
+
+/* strings stored in binary game file for identification and validation */
+
+/* file header string */
+#define FIOFILHDR "TADS2 bin\012\015\032"
+
+/* resource file header string */
+#define FIOFILHDRRSC "TADS2 rsc\012\015\032"
+
+/* CURRENT file format version string */
+#define FIOVSNHDR "v2.2.0"
+
+/* other file format versions that can be READ by this version */
+#define FIOVSNHDR2 "v2.0.0"
+#define FIOVSNHDR3 "v2.0.1"
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/ler.cpp b/engines/glk/tads/tads2/ler.cpp
deleted file mode 100644
index d996977f0e..0000000000
--- a/engines/glk/tads/tads2/ler.cpp
+++ /dev/null
@@ -1,152 +0,0 @@
-/* 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 "ler.h"
-
-namespace Glk {
-namespace TADS {
-namespace TADS2 {
-
-#define TRDLOGERR_PREFIX "\n[An error has occurred within TADS: "
-
-int errcxdef::errfmt(char *outbuf, int outbufl, char *fmt, int argc, erradef *argv) {
- int outlen = 0;
- int argi = 0;
- int len;
- char buf[20];
- const char *p = nullptr;
- char fmtchar;
-
- while (*fmt != '\0' && outbufl > 1) {
- switch(*fmt) {
- case '\\':
- ++fmt;
- len = 1;
- switch(*fmt) {
- case '\0':
- --fmt;
- break;
- case '\n':
- p = "\n";
- break;
- case '\t':
- p = "\t";
- break;
- default:
- p = fmt;
- break;
- }
- break;
-
- case '%':
- ++fmt;
- fmtchar = *fmt;
- if (argi >= argc) fmtchar = 1; // too many - ignore it
- switch(fmtchar) {
- case '\0':
- --fmt;
- len = 0;
- break;
-
- case '%':
- p = "%";
- len = 1;
- break;
-
- case 'd':
- sprintf(buf, "%d", argv[argi].erraint);
- len = strlen(buf);
- p = buf;
- break;
-
- case 'u':
- sprintf(buf, "%u", argv[argi].erraint);
- len = strlen(buf);
- p = buf;
- break;
-
- case 's':
- p = argv[argi].errastr;
- len = strlen(p);
- break;
-
- default:
- p = "";
- len = 0;
- --argi;
- break;
- }
- ++argi;
- break;
-
- default:
- p = fmt;
- len = 1;
- break;
- }
-
- /* copy output that was set up above */
- if (len != 0) {
- if (outbufl >= len) {
- memcpy(outbuf, p, (size_t)len);
- outbufl -= len;
- outbuf += len;
- } else if (outbufl > 1) {
- memcpy(outbuf, p, (size_t)outbufl - 1);
- outbufl = 1;
- }
- outlen += len;
- }
- ++fmt;
- }
-
- // add a null terminator
- if (outbufl != 0)
- *outbuf++ = '\0';
-
- // return the length
- return outlen;
-}
-
-void errcxdef::errcxlog(void *ctx0, char *fac, int err, int argc, erradef *argv) {
-#ifdef TODO
- errcxdef *ctx = (errcxdef *)ctx0;
- char buf[256];
- char msg[256];
-
- // display the prefix message to the console and log file
- sprintf(buf, TRDLOGERR_PREFIX, fac, err);
- trdptf("%s", buf);
- out_logfile_print(buf, false);
-
- /* display the error message text to the console and log file */
- errmsg(ctx, msg, (uint)sizeof(msg), err);
- errfmt(buf, (int)sizeof(buf), msg, argc, argv);
- trdptf("%s]\n", buf);
- out_logfile_print(buf, false);
- out_logfile_print("]", true);
-#endif
-}
-
-} // End of namespace TADS2
-} // End of namespace TADS
-} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/ler.h b/engines/glk/tads/tads2/ler.h
deleted file mode 100644
index 57e3e63c2c..0000000000
--- a/engines/glk/tads/tads2/ler.h
+++ /dev/null
@@ -1,617 +0,0 @@
-/* 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.
- *
- */
-
-#ifndef GLK_TADS_TADS2_LER
-#define GLK_TADS_TADS2_LER
-
-#include "common/scummsys.h"
-#include "common/stream.h"
-#include "common/algorithm.h"
-
-namespace Glk {
-namespace TADS {
-namespace TADS2 {
-
-// maximum length of a facility identifier
-#define ERRFACMAX 6
-
-enum ErrorCode {
- /* memory/cache manager errors */
- ERR_NOMEM = 1, /* out of memory */
- ERR_FSEEK = 2, /* error seeking in file */
- ERR_FREAD = 3, /* error reading from file */
- ERR_NOPAGE = 4, /* no more page slots */
- ERR_REALCK = 5, /* attempting to reallocate a locked object */
- ERR_SWAPBIG = 6, /* swapfile limit reached - out of virtual memory */
- ERR_FWRITE = 7, /* error writing file */
- ERR_SWAPPG = 8, /* exceeded swap page table limit */
- ERR_CLIUSE = 9, /* requested client object number already in use */
- ERR_CLIFULL = 10, /* client mapping table is full */
- ERR_NOMEM1 = 11, /* swapping/garbage collection failed to find enuf */
- ERR_NOMEM2 = 12, /* no memory to resize (expand) an object */
- ERR_OPSWAP = 13, /* unable to open swap file */
- ERR_NOHDR = 14, /* can't get a new object header */
- ERR_NOLOAD = 15, /* mcm cannot find object to load (internal error) */
- ERR_LCKFRE = 16, /* attempting to free a locked object (internal) */
- ERR_INVOBJ = 17, /* invalid object */
- ERR_BIGOBJ = 18, /* object too big - exceeds memory allocation limit */
-
- /* lexical analysis errors */
- ERR_INVTOK = 100, /* invalid token */
- ERR_STREOF = 101, /* end of file while scanning string */
- ERR_TRUNC = 102, /* symbol too long - truncated */
- ERR_NOLCLSY = 103, /* no space in local symbol table */
- ERR_PRPDIR = 104, /* invalid preprocessor (#) directive */
- ERR_INCNOFN = 105, /* no filename in #include directive */
- ERR_INCSYN = 106, /* invalid #include syntax */
- ERR_INCSEAR = 107, /* can't find included file */
- ERR_INCMTCH = 108, /* no matching delimiter in #include filename */
- ERR_MANYSYM = 109, /* out of space for symbol table */
- ERR_LONGLIN = 110, /* line too long */
- ERR_INCRPT = 111, /* header file already included - ignored */
- ERR_PRAGMA = 112, /* unknown pragma (ignored) */
- ERR_BADPELSE = 113, /* unexpected #else */
- ERR_BADENDIF = 114, /* unexpected #endif */
- ERR_BADELIF = 115, /* unexpected #elif */
- ERR_MANYPIF = 116, /* #if nesting too deep */
- ERR_DEFREDEF = 117, /* symbol already defined */
- ERR_PUNDEF = 118, /* #undef symbol not defined */
- ERR_NOENDIF = 119, /* missing #endif */
- ERR_MACNEST = 120, /* macros nested too deeply */
- ERR_BADISDEF = 121, /* invalid argument for defined() operator */
- ERR_PIF_NA = 122, /* #if is not implemented */
- ERR_PELIF_NA = 123, /* #elif is not implemented */
- ERR_P_ERROR = 124, /* error directive: %s */
- ERR_LONG_FILE_MACRO = 125, /* __FILE__ expansion too long */
- ERR_LONG_LINE_MACRO = 126, /* __LINE__ expansion too long */
-
- /* undo errors */
- ERR_UNDOVF = 200, /* operation is too big for undo log */
- ERR_NOUNDO = 201, /* no more undo information */
- ERR_ICUNDO = 202, /* incomplete undo (no previous savepoint) */
-
- /* parser errors */
- ERR_REQTOK = 300, /* expected token (arg 1) - found something else */
- ERR_REQSYM = 301, /* expected symbol */
- ERR_REQPRP = 302, /* expected a property name */
- ERR_REQOPN = 303, /* expected operand */
- ERR_REQARG = 304, /* expected comma or closing paren (arg list) */
- ERR_NONODE = 305, /* no space for new parse node */
- ERR_REQOBJ = 306, /* epxected object name */
- ERR_REQEXT = 307, /* redefining symbol as external function */
- ERR_REQFCN = 308, /* redefining symbol as function */
- ERR_NOCLASS = 309, /* can't use CLASS with function/external function */
- ERR_REQUNO = 310, /* required unary operator */
- ERR_REQBIN = 311, /* required binary operator */
- ERR_INVBIN = 312, /* invalid binary operator */
- ERR_INVASI = 313, /* invalid assignment */
- ERR_REQVAR = 314, /* required variable name */
- ERR_LCLSYN = 315, /* required comma or semicolon in local list */
- ERR_REQRBR = 316, /* required right brace (eof before end of group) */
- ERR_BADBRK = 317, /* 'break' without 'while' */
- ERR_BADCNT = 318, /* 'continue' without 'while' */
- ERR_BADELS = 319, /* 'else' without 'if' */
- ERR_WEQASI = 320, /* warning: possible use of '=' where ':=' intended */
- ERR_EOF = 321, /* unexpected end of file */
- ERR_SYNTAX = 322, /* general syntax error */
- ERR_INVOP = 323, /* invalid operand type */
- ERR_NOMEMLC = 324, /* no memory for new local symbol table */
- ERR_NOMEMAR = 325, /* no memory for argument symbol table */
- ERR_FREDEF = 326, /* redefining a function which is already defined */
- ERR_NOSW = 327, /* 'case' or 'default' and not in switch block */
- ERR_SWRQCN = 328, /* constant required in switch case value */
- ERR_REQLBL = 329, /* label required for 'goto' */
- ERR_NOGOTO = 330, /* 'goto' label never defined */
- ERR_MANYSC = 331, /* too many superclasses for object */
- ERR_OREDEF = 332, /* redefining symbol as object */
- ERR_PREDEF = 333, /* property being redefined in object */
- ERR_BADPVL = 334, /* invalid property value */
- ERR_BADVOC = 335, /* bad vocabulary property value */
- ERR_BADTPL = 336, /* bad template property value (need sstring) */
- ERR_LONGTPL = 337, /* template base property name too long */
- ERR_MANYTPL = 338, /* too many templates (internal compiler limit) */
- ERR_BADCMPD = 339, /* bad value for compound word (sstring required) */
- ERR_BADFMT = 340, /* bad value for format string (sstring needed) */
- ERR_BADSYN = 341, /* invalid value for synonym (sstring required) */
- ERR_UNDFSYM = 342, /* undefined symbol */
- ERR_BADSPEC = 343, /* bad special word */
- ERR_NOSELF = 344, /* "self" not valid in this context */
- ERR_STREND = 345, /* warning: possible unterminated string */
- ERR_MODRPLX = 346, /* modify/replace not allowed with external func */
- ERR_MODFCN = 347, /* modify not allowed with function */
- ERR_MODFWD = 348, /* modify/replace not allowed with forward func */
- ERR_MODOBJ = 349, /* modify can only be used with a defined object */
- ERR_RPLSPEC = 350, /* warning - replacing specialWords */
- ERR_SPECNIL = 351, /* nil only allowed with modify specialWords */
- ERR_BADLCL = 353, /* 'local' statement must precede executable code */
- ERR_IMPPROP = 354, /* implied verifier '%s' is not a property */
- ERR_BADTPLF = 355, /* invalid command template flag */
- ERR_NOTPLFLG = 356, /* flags are not allowed with old file format */
- ERR_AMBIGBIN = 357, /* warning: operator '%s' could be binary */
- ERR_PIA = 358, /* warning: possibly incorrect assignment */
- ERR_BADSPECEXPR = 359, /* invalid speculation evaluation */
-
- /* code generation errors */
- ERR_OBJOVF = 400, /* object cannot grow any bigger - code too big */
- ERR_NOLBL = 401, /* no more temporary labels/fixups */
- ERR_LBNOSET = 402, /* (internal error) label never set */
- ERR_INVLSTE = 403, /* invalid datatype for list element */
- ERR_MANYDBG = 404, /* too many debugger line records (internal limit) */
-
- /* vocabulary setup errors */
- ERR_VOCINUS = 450, /* vocabulary being redefined for object */
- ERR_VOCMNPG = 451, /* too many vocwdef pages (internal limit) */
- ERR_VOCREVB = 452, /* redefining same verb */
- ERR_VOCREVB2 = 453, /* redefining same verb - two arguments */
-
- /* set-up errors */
- ERR_LOCNOBJ = 500, /* location of object %s is not an object */
- ERR_CNTNLST = 501, /* contents of object %s is not list */
- ERR_SUPOVF = 502, /* overflow trying to build contents list */
- ERR_RQOBJNF = 503, /* required object %s not found */
- ERR_WRNONF = 504, /* warning - object %s not found */
- ERR_MANYBIF = 505, /* too many built-in functions (internal error) */
-
- /* fio errors */
- ERR_OPWGAM = 600, /* unable to open game for writing */
- ERR_WRTGAM = 601, /* error writing to game file */
- ERR_FIOMSC = 602, /* too many sc's for writing in fiowrt */
- ERR_UNDEFF = 603, /* undefined function */
- ERR_UNDEFO = 604, /* undefined object */
- ERR_UNDEF = 605, /* undefined symbols found */
- ERR_OPRGAM = 606, /* unable to open game for reading */
- ERR_RDGAM = 607, /* error reading game file */
- ERR_BADHDR = 608, /* file has invalid header - not TADS game file */
- ERR_UNKRSC = 609, /* unknown resource type in .gam file */
- ERR_UNKOTYP = 610, /* unknown object type in OBJ resource */
- ERR_BADVSN = 611, /* file saved by different incompatible version */
- ERR_LDGAM = 612, /* error loading object on demand */
- ERR_LDBIG = 613, /* object too big for load region (prob. internal) */
- ERR_UNXEXT = 614, /* did not expect external function */
- ERR_WRTVSN = 615, /* compiler cannot write the requested version */
- ERR_VNOCTAB = 616, /* format version cannot be used with -ctab */
- ERR_BADHDRRSC = 617, /* invalid resource file header in file %s */
- ERR_RDRSC = 618, /* error reading resource file "xxx" */
-
- /* character mapping errors */
- ERR_CHRNOFILE = 700, /* unable to load character mapping file */
-
- /* user interrupt */
- ERR_USRINT = 990, /* user requested cancel of current operation */
-
- /* run-time errors */
- ERR_STKOVF = 1001, /* stack overflow */
- ERR_HPOVF = 1002, /* heap overflow */
- ERR_REQNUM = 1003, /* numeric value required */
- ERR_STKUND = 1004, /* stack underflow */
- ERR_REQLOG = 1005, /* logical value required */
- ERR_INVCMP = 1006, /* invalid datatypes for magnitude comparison */
- ERR_REQSTR = 1007, /* string value required */
- ERR_INVADD = 1008, /* invalid datatypes for '+' operator */
- ERR_INVSUB = 1009, /* invalid datatypes for binary '-' operator */
- ERR_REQVOB = 1010, /* require object value */
- ERR_REQVFN = 1011, /* required function pointer */
- ERR_REQVPR = 1012, /* required property number value */
-
- /* non-error conditions: run-time EXIT, ABORT, ASKIO, ASKDO */
- ERR_RUNEXIT = 1013, /* 'exit' statement executed */
- ERR_RUNABRT = 1014, /* 'abort' statement executed */
- ERR_RUNASKD = 1015, /* 'askdo' statement executed */
- ERR_RUNASKI = 1016, /* 'askio' executed; int arg 1 is prep */
- ERR_RUNQUIT = 1017, /* 'quit' executed */
- ERR_RUNRESTART = 1018, /* 'reset' executed */
- ERR_RUNEXITOBJ = 1019, /* 'exitobj' executed */
-
- ERR_REQVLS = 1020, /* list value required */
- ERR_LOWINX = 1021, /* index value too low (must be >= 1) */
- ERR_HIGHINX = 1022, /* index value too high (must be <= length(list)) */
- ERR_INVTBIF = 1023, /* invalid type for built-in function */
- ERR_INVVBIF = 1024, /* invalid value for built-in function */
- ERR_BIFARGC = 1025, /* wrong number of arguments to built-in */
- ERR_ARGC = 1026, /* wrong number of arguments to user function */
- ERR_FUSEVAL = 1027, /* string/list not allowed for fuse/daemon arg */
- ERR_BADSETF = 1028, /* internal error in setfuse/setdaemon/notify */
- ERR_MANYFUS = 1029, /* too many fuses */
- ERR_MANYDMN = 1030, /* too many daemons */
- ERR_MANYNFY = 1031, /* too many notifiers */
- ERR_NOFUSE = 1032, /* fuse not found in remfuse */
- ERR_NODMN = 1033, /* daemon not found in remdaemon */
- ERR_NONFY = 1034, /* notifier not found in unnotify */
- ERR_BADREMF = 1035, /* internal error in remfuse/remdaemon/unnotify */
- ERR_DMDLOOP = 1036, /* load-on-demand loop: property not being set */
- ERR_UNDFOBJ = 1037, /* undefined object in vocabulary tree */
- ERR_BIFCSTR = 1038, /* c-string conversion overflows buffer */
- ERR_INVOPC = 1039, /* invalid opcode */
- ERR_RUNNOBJ = 1040, /* runtime error: property taken of non-object */
- ERR_EXTLOAD = 1041, /* unable to load external function "%s" */
- ERR_EXTRUN = 1042, /* error executing external function "%s" */
- ERR_CIRCSYN = 1043, /* circular synonym */
- ERR_DIVZERO = 1044, /* divide by zero */
- ERR_BADDEL = 1045, /* can only delete objects created with "new" */
- ERR_BADNEWSC = 1046, /* superclass for "new" cannot be a new object */
- ERR_VOCSTK = 1047, /* insufficient space in parser stack */
- ERR_BADFILE = 1048, /* invalid file handle */
-
- ERR_RUNEXITPRECMD = 1049, /* exited from preCommand */
-
- /* run-time parser errors */
- ERR_PRS_SENT_UNK = 1200, /* sentence structure not recognized */
- ERR_PRS_VERDO_FAIL = 1201, /* verDoVerb failed */
- ERR_PRS_VERIO_FAIL = 1202, /* verIoVerb failed */
- ERR_PRS_NO_VERDO = 1203, /* no verDoVerb for direct object */
- ERR_PRS_NO_VERIO = 1204, /* no verIoVerb for direct object */
- ERR_PRS_VAL_DO_FAIL = 1205, /* direct object validation failed */
- ERR_PRS_VAL_IO_FAIL = 1206, /* indirect object validation failed */
-
- /* compiler/runtime/debugger driver errors */
- ERR_USAGE = 1500, /* invalid usage */
- ERR_OPNINP = 1501, /* error opening input file */
- ERR_NODBG = 1502, /* game not compiled for debugging */
- ERR_ERRFIL = 1503, /* unable to open error capture file */
- ERR_PRSCXSIZ = 1504, /* parse pool + local size too large */
- ERR_STKSIZE = 1505, /* stack size too large */
- ERR_OPNSTRFIL = 1506, /* error opening string capture file */
- ERR_INVCMAP = 1507, /* invalid character map file */
-
- /* debugger errors */
- ERR_BPSYM = 2000, /* symbol not found for breakpoint */
- ERR_BPPROP = 2002, /* breakpoint symbol is not a property */
- ERR_BPFUNC = 2003, /* breakpoint symbol is not a function */
- ERR_BPNOPRP = 2004, /* property is not defined for object */
- ERR_BPPRPNC = 2005, /* property is not code */
- ERR_BPSET = 2006, /* breakpoint already set at this location */
- ERR_BPNOTLN = 2007, /* breakpoint is not at a line (OPCLINE instr) */
- ERR_MANYBP = 2008, /* too many breakpoints */
- ERR_BPNSET = 2009, /* breakpoint to be deleted was not set */
- ERR_DBGMNSY = 2010, /* too many symbols in debug expression (int lim) */
- ERR_NOSOURC = 2011, /* unable to find source file %s */
- ERR_WTCHLCL = 2012, /* illegal to assign to local in watch expr */
- ERR_INACTFR = 2013, /* inactive frame (expression value not available) */
- ERR_MANYWX = 2014, /* too many watch expressions */
- ERR_WXNSET = 2015, /* watchpoint not set */
- ERR_EXTRTXT = 2016, /* extraneous text at end of command */
- ERR_BPOBJ = 2017, /* breakpoint symbol is not an object */
- ERR_DBGINACT = 2018, /* debugger is not active */
- ERR_BPINUSE = 2019, /* breakpoint is already used */
- ERR_RTBADSPECEXPR = 2020, /* invalid speculative expression */
- ERR_NEEDLIN2 = 2021, /* -ds2 information not found - must recompile */
-
- /* usage error messages */
- ERR_TCUS1 = 3000, /* first tc usage message */
- ERR_TCUSL = 3024, /* last tc usage message */
- ERR_TCTGUS1 = 3030, /* first tc toggle message */
- ERR_TCTGUSL = 3032,
- ERR_TCZUS1 = 3040, /* first tc -Z suboptions usage message */
- ERR_TCZUSL = 3041,
- ERR_TC1US1 = 3050, /* first tc -1 suboptions usage message */
- ERR_TC1USL = 3058,
- ERR_TCMUS1 = 3070, /* first tc -m suboptions usage message */
- ERR_TCMUSL = 3076,
- ERR_TCVUS1 = 3080, /* first -v suboption usage message */
- ERR_TCVUSL = 3082,
- ERR_TRUSPARM = 3099,
- ERR_TRUS1 = 3100, /* first tr usage message */
- ERR_TRUSL = 3117,
- ERR_TRUSFT1 = 3118, /* first tr "footer" message */
- ERR_TRUSFTL = 3119, /* last tr "footer" message */
- ERR_TRSUS1 = 3150, /* first tr -s suboptions usage message */
- ERR_TRSUSL = 3157,
- ERR_TDBUSPARM = 3199,
- ERR_TDBUS1 = 3200, /* first tdb usage message */
- ERR_TDBUSL = 3214, /* last tdb usage message */
-
- /* TR 16-bit MSDOS-specific usage messages */
- ERR_TRUS_DOS_1 = 3300,
- ERR_TRUS_DOS_L = 3300,
-
- /* TR 32-bit MSDOS console mode usage messages */
- ERR_TRUS_DOS32_1 = 3310,
- ERR_TRUS_DOS32_L = 3312,
-
- /* TADS/Graphic errors */
- ERR_GNOFIL = 4001, /* can't find graphics file %s */
- ERR_GNORM = 4002, /* can't find room %s */
- ERR_GNOOBJ = 4003, /* can't find hot spot object %s */
- ERR_GNOICN = 4004 /* can't find icon object %s */
-};
-
-/*
- * Special error flag - this is returned from execmd() when preparseCmd
- * returns a command list. This indicates to voc1cmd that it should try
- * the command over again, using the words in the new list.
- */
-#define ERR_PREPRSCMDREDO 30000 /* preparseCmd returned a list */
-#define ERR_PREPRSCMDCAN 30001 /* preparseCmd returned 'nil' to cancel */
-
-union erradef {
- int erraint; // integer argument
- char *errastr; // text string argument
-};
-
-struct errdef {
- errdef * errprv; // previous error frame
- int errcode; // error code of exception being handled
- 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
-};
-
-#define ERRBUFSIZ 512
-
-class TADS2;
-
-// seek location record for an error message by number
-struct errmfdef {
- uint errmfnum; // error number
- size_t errmfseek; // seek location of this message
-};
-
-class errcxdef {
-public:
- errdef *errcxptr; // current error frame
- void *errcxlgc; // context for error logging callback
- int errcxofs; // offset in argument buffer
- char errcxbuf[ERRBUFSIZ]; // space for argument strings
- Common::SeekableReadStream *errcxfp; // message file, if one is being used
- errmfdef *errcxseek; // seek locations of messages in file
- uint errcxsksz; // size of errcxseek array
- size_t errcxbase; // offset in physical file of logical error file
- TADS2 * errcxappctx; // host application context
-public:
- /**
- * Format an error message, sprintf-style, using arguments in an
- * erradef array (which is passed to the error-logging callback).
- * Returns the length of the output string, even if the actual
- * output string was truncated because the outbuf was too short.
- * (If called with outbufl == 0, nothing will be written out, but
- * the size of the buffer needed, minus the terminating null byte,
- * will be computed and returned.)
- */
- static int errfmt(char *outbuf, int outbufl, char *fmt, int argc, erradef *argv);
- public:
- errcxdef() : errcxptr(nullptr), errcxlgc(nullptr), errcxofs(0),
- errcxseek(nullptr), errcxsksz(0), errcxbase(0), errcxappctx(nullptr) {
- Common::fill(&errcxbuf[0], &errcxbuf[ERRBUFSIZ], '\0');
- }
-
- /**
- * Error logging method
- */
- void errcxlog(void *ctx0, char *fac, int err, int argc, erradef *argv);
-};
-
-// begin protected code
-#define ERRBEGIN(ctx) \
- { \
- errdef fr_; \
- if ((fr_.errcode = setjmp(fr_.errbuf)) == 0) \
- { \
- fr_.errprv = (ctx)->errcxptr; \
- (ctx)->errcxptr = &fr_;
-
-// end protected code, begin error handler
-#define ERRCATCH(ctx, e) \
- assert(1==1 && (ctx)->errcxptr != fr_.errprv); \
- (ctx)->errcxptr = fr_.errprv; \
- } \
- else \
- { \
- assert(2==2 && (ctx)->errcxptr != fr_.errprv); \
- (e) = fr_.errcode; \
- (ctx)->errcxptr = fr_.errprv;
-
-// retrieve argument (int, string) in current error frame
-#define errargint(argnum) (fr_.erraav[argnum].erraint)
-#define errargstr(argnum) (fr_.erraav[argnum].errastr)
-
-
-#define ERREND(ctx) \
- } \
- }
-
-// end protected code, begin cleanup (no handling; just cleaning up)
-#define ERRCLEAN(ctx) \
- assert((ctx)->errcxptr != fr_.errprv); \
- (ctx)->errcxptr = fr_.errprv; \
- } \
- else \
- { \
- assert((ctx)->errcxptr != fr_.errprv); \
- (ctx)->errcxptr = fr_.errprv;
-
-#define ERRENDCLN(ctx) \
- errrse(ctx); \
- } \
- }
-
-
-
-// argument types for errors with arguments
-#define ERRTINT erraint
-#define ERRTSTR errastr
-
-// set argument count in error frame
-#define errargc(ctx,cnt) ((ctx)->errcxptr->erraac=(cnt))
-
-// enter string argument; returns pointer to argument used in errargv
-#ifdef ERR_NO_MACRO
-char *errstr(errcxdef *ctx, const char *str, int len);
-#else /* ERR_NO_MACRO */
-
-#define errstr(ctx,str,len) \
- ((memcpy(&(ctx)->errcxbuf[(ctx)->errcxofs],str,(size_t)len), \
- (ctx)->errcxofs += (len), \
- (ctx)->errcxbuf[(ctx)->errcxofs++] = '\0'), \
- &(ctx)->errcxbuf[(ctx)->errcxofs-(len)-1])
-
-#endif /* ERR_NO_MACRO */
-
-/* set argument in error frame argument vector */
-#define errargv(ctx,index,typ,arg) \
- ((ctx)->errcxptr->erraav[index].typ=(arg))
-
-// signal an error with argument count already set
-#ifdef ERR_NO_MACRO
-void errsign(errcxdef *ctx, int e, char *facility);
-#else /* ERR_NO_MACRO */
-# ifdef DEBUG
-void errjmp(jmp_buf buf, int e);
-# define errsign(ctx, e, fac) \
- (strncpy((ctx)->errcxptr->errfac, fac, ERRFACMAX),\
- (ctx)->errcxptr->errfac[ERRFACMAX]='\0',\
- (ctx)->errcxofs=0, errjmp((ctx)->errcxptr->errbuf, e))
-# else /* DEBUG */
-# define errsign(ctx, e, fac) \
- (strncpy((ctx)->errcxptr->errfac, fac, ERRFACMAX),\
- (ctx)->errcxptr->errfac[ERRFACMAX]='\0',\
- (ctx)->errcxofs=0, longjmp((ctx)->errcxptr->errbuf, e))
-# endif /* DEBUG */
-#endif /* ERR_NO_MACRO */
-
-
-// signal an error with no arguments
-#ifdef ERR_NO_MACRO
-void errsigf(errcxdef *ctx, char *facility, int err);
-#else /* ERR_NO_MACRO */
-#define errsigf(ctx, fac, e) (errargc(ctx,0),errsign(ctx,e,fac))
-#endif /* ERR_NO_MACRO */
-
-// signal an error with one argument
-#define errsigf1(ctx, fac, e, typ1, arg1) \
- (errargv(ctx,0,typ1,arg1),errargc(ctx,1),errsign(ctx,e,fac))
-
-// signal an error with two arguments
-#define errsigf2(ctx, fac, e, typ1, arg1, typ2, arg2) \
- (errargv(ctx,0,typ1,arg1), errargv(ctx,1,typ2,arg2), \
- errargc(ctx,2), errsign(ctx,e,fac))
-
-// resignal the current error - only usable within exception handlers
-#ifdef ERR_NO_MACRO
-void errrse1(errcxdef *ctx, errdef *fr);
-# define errrse(ctx) errrse1(ctx, &fr_)
-#else /* ERR_NO_MACRO */
-
-// void errrse(errcxdef *ctx);
-# define errrse(ctx) \
- (errargc(ctx, fr_.erraac),\
- memcpy((ctx)->errcxptr->erraav, fr_.erraav, \
- (size_t)(fr_.erraac*sizeof(erradef))),\
- errsign(ctx, fr_.errcode, fr_.errfac))
-
-#endif /* ERR_NO_MACRO */
-
-/**
- * For use in an error handler (ERRCATCH..ERREND) only: Copy the
- * parameters from the error currently being handled to the enclosing
- * frame. This is useful when "keeping" an error being handled - i.e.,
- * the arguments will continue to be used outside of the
- * ERRCATCH..ERREND code.
- */
-#define errkeepargs(ctx) errcopyargs(ctx, &fr_)
-
-/**
- * copy the parameters for an error from another frame into the current
- * frame - this can be used when we want to be able to display an error
- * that occurred in an inner frame within code that is protected by a
- * new enclosing error frame
- */
-#define errcopyargs(ctx, fr) \
- (errargc((ctx), (fr)->erraac), \
- memcpy((ctx)->errcxptr->erraav, (fr)->erraav, \
- (size_t)((fr)->erraac*sizeof(erradef))))
-
-// log error that's been caught, using arguments already caught
-#define errclog(ctx) \
- ((*(ctx)->errcxlog)((ctx)->errcxlgc,fr_.errfac,fr_.errcode,\
- fr_.erraac,fr_.erraav))
-
-// log an error that's been set up but not signalled yet
-#define errprelog(ctx, err) \
- ((*(ctx)->errcxlog)((ctx)->errcxlgc,(ctx)->errcxptr->errfac,\
- err,(ctx)->errcxptr->erraac,\
- (ctx)->errcxptr->erraav))
-
-// log an error (no signalling, just reporting)
-#ifdef ERR_NO_MACRO
-void errlogn(errcxdef *ctx, int err, char *facility);
-#else /* ERR_NO_MACRO */
-
-#define errlogn(ctx,err,fac) \
- ((ctx)->errcxofs=0,\
- (*(ctx)->errcxlog)((ctx)->errcxlgc,fac,err,(ctx)->errcxptr->erraac,\
- (ctx)->errcxptr->erraav))
-
-#endif /* ERR_NO_MACRO */
-
-// log an error with no arguments
-#ifdef ERR_NO_MACRO
-void errlogf(errcxdef *ctx, char *facility, int err);
-#else /* ERR_NO_MACRO */
-
-// void errlogf(errcxdef *ctx, char *facility, int err);
-#define errlogf(ctx,fac,err) (errargc(ctx,0),errlogn(ctx,err,fac))
-
-#endif /* ERR_NO_MACRO */
-
-// log an error with one argument
-#define errlogf1(ctx, fac, e, typ1, arg1) \
- (errargv(ctx,0,typ1,arg1),errargc(ctx,1),errlogn(ctx,e,fac))
-
-// log an error with two arguments
-#define errlogf2(ctx, fac, e, typ1, arg1, typ2, arg2) \
- (errargv(ctx,0,typ1,arg1),errargv(ctx,1,typ2,arg2),\
- errargc(ctx,2),errlogn(ctx,e,fac))
-
-/**
- * For compatility with old facility-free mechanism, signal with facility "TADS"
- */
-#define errsig(ctx, err) errsigf(ctx, "TADS", err)
-#define errsig1(c, e, t, a) errsigf1(c,"TADS",e,t,a)
-//#define errsig2(c, e, t1, a1, t2, a2) errsigf2(c,"TADS",e,t1,a1,t2,a2)
-#define errlog(c, e) errlogf(c, "TADS", e)
-#define errlog1(c, e, t, a) errlogf1(c,"TADS",e,t,a)
-#define errlog2(c, e, t1, a1, t2, a2) errlogf2(c,"TADS",e,t1,a1,t2,a2)
-
-#define errsig2(c, e, t1, a1, t2, a2) error("Error occurred")
-
-// get the text of an error
-void errmsg(errcxdef *ctx, char *outbuf, uint outbufl, uint err);
-
-// initialize error subsystem, opening error message file if necessary
-void errini(errcxdef *ctx, Common::SeekableReadStream *fp);
-
-// allocate and initialize error context, free error context
-errcxdef *lerini();
-void lerfre(errcxdef *ctx);
-
-// error message structure - number + text
-struct errmdef {
- uint errmerr; // error number
- char *errmtxt; // text of error message
-};
-
-} // End of namespace TADS2
-} // End of namespace TADS
-} // End of namespace Glk
-
-#endif
diff --git a/engines/glk/tads/tads2/lib.h b/engines/glk/tads/tads2/lib.h
new file mode 100644
index 0000000000..605f072390
--- /dev/null
+++ b/engines/glk/tads/tads2/lib.h
@@ -0,0 +1,164 @@
+/* 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.
+ *
+ */
+
+#ifndef GLK_TADS_TADS2_LIB
+#define GLK_TADS_TADS2_LIB
+
+#include "common/scummsys.h"
+#include "common/algorithm.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* short-hand for various types */
+
+typedef unsigned char uchar;
+typedef unsigned short ushort;
+typedef unsigned long ulong;
+
+typedef byte ub1;
+typedef signed char sb1;
+typedef char b1;
+typedef unsigned int ub2;
+typedef signed int sb2;
+typedef int b2;
+typedef unsigned long ub4;
+typedef signed long sb4;
+typedef long b4;
+typedef int eword;
+typedef int32 int32_t;
+typedef uint32 uint32_t;
+
+
+/* maximum/minimum portable values for various types */
+#define UB4MAXVAL 0xffffffffUL
+#define UB2MAXVAL 0xffffU
+#define UB1MAXVAL 0xffU
+#define SB4MAXVAL 0x7fffffffL
+#define SB2MAXVAL 0x7fff
+#define SB1MAXVAL 0x7f
+#define SB4MINVAL (-(0x7fffffff)-1)
+#define SB2MINVAL (-(0x7fff)-1)
+#define SB1MINVAL (-(0x7f)-1)
+
+/* clear a struture */
+#define CLRSTRUCT(x) memset(&(x), 0, (size_t)sizeof(x))
+#define CPSTRUCT(dst,src) memcpy(&(dst), &(src), (size_t)sizeof(dst))
+
+/* TRUE and FALSE */
+#define TRUE true
+#define FALSE false
+
+
+/* bitwise operations */
+#define bit(va, bt) ((va) & (bt))
+#define bis(va, bt) ((va) |= (bt))
+#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.
+ */
+#ifdef OSANSI
+# define noreg volatile
+# define NOREG(arglist)
+#else /* OSANSI */
+# define noreg
+# define NOREG(arglist) osnoreg arglist ;
+#endif /* OSANSI */
+
+/*
+ * Linting directives. You can define these before including this file
+ * if you have a fussy compiler.
+ */
+#ifdef LINT
+# ifndef NOTREACHED
+# define NOTREACHED return
+# endif
+# ifndef NOTREACHEDV
+# define NOTREACHEDV(t) return((t)0)
+# endif
+# ifndef VARUSED
+# define VARUSED(v) varused(v)
+# endif
+void varused();
+#else /* LINT */
+# ifndef NOTREACHED
+# define NOTREACHED
+# endif
+# ifndef NOTREACHEDV
+# define NOTREACHEDV(t)
+# endif
+# ifndef VARUSED
+# define VARUSED(v)
+# endif
+#endif /* LINT */
+
+/* conditionally compile code if debugging is enabled */
+#ifdef DEBUG
+# define IF_DEBUG(x) x
+#else /* DEBUG */
+# define IF_DEBUG(x)
+#endif /* DEBUG */
+
+#ifndef offsetof
+# define offsetof(s_name, m_name) (size_t)&(((s_name *)0)->m_name)
+#endif /* offsetof */
+
+/*
+ * Define our own version of isspace(), so that we don't try to interpret
+ * anything outside of the normal ASCII set as spaces.
+ */
+#define t_isspace(c) \
+ (((unsigned char)(c)) <= 127 && 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
+
+#endif
diff --git a/engines/glk/tads/tads2/line_source.h b/engines/glk/tads/tads2/line_source.h
new file mode 100644
index 0000000000..a7e5454c60
--- /dev/null
+++ b/engines/glk/tads/tads2/line_source.h
@@ -0,0 +1,129 @@
+/* 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.
+ *
+ */
+
+/* Line source definitions
+ *
+ * A line source is a mechanism for reading source text. The tokenizer
+ * reads its input from a line source. This is the basic class
+ * definition; individual line sources will define the functions and
+ * class data needed.
+ */
+
+#ifndef GLK_TADS_TADS2_LINE_SOURCE
+#define GLK_TADS_TADS2_LINE_SOURCE
+
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/object.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+/**
+ * Line source superclass structure
+ */
+struct lindef {
+ int (*lingetp)(lindef *lin); /* get next line */
+ void (*linclsp)(lindef *lin); /* close line source */
+ void (*linppos)(lindef *lin, char *buf, uint buflen);
+ /* write printable rep of position to buf (for error reporting) */
+ void (*linglop)(lindef *lin, uchar *buf);
+ /* generate a line record for an OPCLINE instruction */
+ int (*linwrtp)(lindef *lin, osfildef *fp);
+ /* write line source information to binary file; TRUE ==> error */
+ void (*lincmpp)(lindef *lin, uchar *buf);
+ /* give location of compiled code for current line to line source */
+ void (*linactp)(lindef *lin); /* activate for dbg */
+ void (*lindisp)(lindef *lin); /* disactivate */
+ void (*lintellp)(lindef *lin, uchar *pos); /* get position */
+ void (*linseekp)(lindef *lin, uchar *pos); /* seek */
+ int (*linreadp)(lindef *lin, uchar *buf, uint siz); /* fread */
+ void (*linpaddp)(lindef *lin, uchar *pos, long delta);
+ /* add an offset to a position value */
+ int (*linqtopp)(lindef *lin, uchar *pos); /* at top? */
+ int (*lingetsp)(lindef *lin, uchar *buf, uint siz); /* get line */
+ void (*linnamp)(lindef *lin, char *buf); /* get source name */
+ void (*linfindp)(lindef *lin, char *buf, objnum *objp,
+ uint *ofsp); /* find nearest line record */
+ void (*lingotop)(lindef *lin, int where); /* seek global */
+ long (*linofsp)(lindef *lin); /* byte offset in line source */
+ void (*linrenp)(lindef *lin, objnum oldnum, objnum newnum);
+ /* renumber an object (for "modify") */
+ void (*lindelp)(lindef *lin, objnum objn);
+ /* delete an object (for "replace") */
+ ulong (*linlnump)(lindef *lin); /* get the current line number */
+#define LINGOTOP OSFSK_SET /* go to top of line source */
+#define LINGOEND OSFSK_END /* go to end of line source */
+ lindef *linpar; /* parent of current line source */
+ lindef *linnxt; /* next line in line source chain */
+ int linid; /* serial number of line source (for debugger) */
+ char *linbuf; /* pointer to current line */
+ ushort linflg; /* flags */
+#define LINFEOF 0x01 /* line source is at end of file */
+#define LINFMORE 0x02 /* there's more to the line than linlen */
+#define LINFDBG 0x04 /* debug record already generated for line */
+#define LINFNOINC 0x08 /* ignore # directives from this line source */
+#define LINFCMODE 0x10 /* line source is parsed in C-mode */
+ ushort linlen; /* length of the line */
+ ushort linlln; /* length of line record generated by lingloc */
+};
+
+/**
+ * Maximum allowed value for linlln, in bytes. This allows subsystems
+ * that need to maintain local copies of seek locations to know how big
+ * an area to allocate for them.
+ */
+#define LINLLNMAX 20
+
+/* macros to cover calls to functions */
+#define linget(lin) ((*((lindef *)(lin))->lingetp)((lindef *)(lin)))
+#define lincls(lin) ((*((lindef *)(lin))->linclsp)((lindef *)(lin)))
+#define linppos(lin, buf, buflen) \
+ ((*((lindef *)(lin))->linppos)((lindef *)(lin), buf, buflen))
+#define linglop(lin, buf) ((*((lindef *)(lin))->linglop)(lin, buf))
+#define linwrt(lin, fp) ((*((lindef *)(lin))->linwrtp)(lin, fp))
+#define lincmpinf(lin, buf) ((*((lindef *)(lin))->lincmpp)(lin, buf))
+#define linactiv(lin) ((*((lindef *)(lin))->linactp)(lin))
+#define lindisact(lin) ((*((lindef *)(lin))->lindisp)(lin))
+#define lintell(lin, pos) ((*((lindef *)(lin))->lintellp)(lin, pos))
+#define linseek(lin, pos) ((*((lindef *)(lin))->linseekp)(lin, pos))
+#define linread(lin, buf, siz) ((*((lindef *)(lin))->linreadp)(lin, buf, siz))
+#define linpadd(lin, pos, delta) \
+ ((*((lindef *)(lin))->linpaddp)(lin, pos, delta))
+#define linqtop(lin, pos) ((*((lindef *)(lin))->linqtopp)(lin, pos))
+#define lingets(lin, buf, siz) ((*((lindef *)(lin))->lingetsp)(lin, buf, siz))
+#define linnam(lin, buf) ((*((lindef *)(lin))->linnamp)(lin, buf))
+#define linlnum(lin) ((*((lindef *)(lin))->linlnump)(lin))
+#define linfind(lin, buf, objp, ofsp) \
+ ((*((lindef *)(lin))->linfindp)(lin, buf, objp, ofsp))
+#define lingoto(lin, where) ((*((lindef *)(lin))->lingotop)(lin, where))
+#define linofs(lin) ((*((lindef *)(lin))->linofsp)(lin))
+#define linrenum(lin, oldnum, newnum) \
+ ((*((lindef *)(lin))->linrenp)(lin, oldnum, newnum))
+#define lindelnum(lin, objn) ((*((lindef *)(lin))->lindelp)(lin, objn))
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/memory_cache.cpp b/engines/glk/tads/tads2/memory_cache.cpp
new file mode 100644
index 0000000000..3bab3b034b
--- /dev/null
+++ b/engines/glk/tads/tads2/memory_cache.cpp
@@ -0,0 +1,31 @@
+/* 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/memory_cache.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/memory_cache.h b/engines/glk/tads/tads2/memory_cache.h
new file mode 100644
index 0000000000..ec0383b7e1
--- /dev/null
+++ b/engines/glk/tads/tads2/memory_cache.h
@@ -0,0 +1,403 @@
+/* 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.
+ *
+ */
+
+/*
+ * Memory cache manager
+ */
+
+#ifndef GLK_TADS_TADS2_MEMORY_CACHE
+#define GLK_TADS_TADS2_MEMORY_CACHE
+
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/memory_cache_loader.h"
+#include "glk/tads/tads2/memory_cache_swap.h"
+#include "glk/tads/osfrobtads.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* if the os layer doesn't want long mcm macros, we don't either */
+#ifdef OS_MCM_NO_MACRO
+# define MCM_NO_MACRO
+#endif
+
+/*
+ * mcmon - cache object number. Each object in the cache is referenced
+ * by an object number, which is a number of this type.
+ */
+typedef ushort mcmon;
+
+/* invalid object number - used to indicate N/A or no object */
+#define MCMONINV ((mcmon)~0)
+
+/* invalid page number */
+#define MCMPINV ((uint)~0)
+
+union mcmodefloc {
+ mcsseg mcmolocs; /* swap segment handle */
+ mclhd mcmolocl; /* load file object handle */
+};
+
+/*
+ * mcmodef - cache object definition. Each cache object is managed by
+ * this structure. Pointers for a doubly-linked list are provided;
+ * these are used to maintain a recent-use list for objects in memory,
+ * and to maintain a free list for cache object entries not in use. A
+ * set of flag bits is provided, as is the size of the object and its
+ * location (which is a memory pointer for objects in memory, a swap
+ * file handle for swapped-out objects, or a load-file handle for
+ * objects that are unloaded).
+ */
+struct mcmodef {
+ uchar *mcmoptr; /* memory pointer (if object is present) */
+ mcmodefloc mcmoloc; /* object's external location */
+#define mcmoswh mcmoloc.mcmolocs
+#define mcmoldh mcmoloc.mcmolocl
+ mcmon mcmonxt; /* next object in this list */
+ mcmon mcmoprv; /* previous object in this list */
+ ushort mcmoflg; /* flags for this cache object */
+#define MCMOFDIRTY 0x01 /* object has been written */
+#define MCMOFNODISC 0x02 /* not in load file (can't be discarded) */
+#define MCMOFLOCK 0x04 /* object is locked */
+#define MCMOFPRES 0x08 /* object is present in memory */
+#define MCMOFLRU 0x10 /* object is in LRU chain */
+#define MCMOFPAGE 0x20 /* object is a cache manager page */
+#define MCMOFNOSWAP 0x40 /* object cannot be swapped out */
+#define MCMOFFREE 0x80 /* entry refers to a free block of memory */
+#define MCMOFREVRT 0x100 /* call revert callback upon loading */
+ uchar mcmolcnt; /* lock count */
+ ushort mcmosiz; /* size of the object */
+};
+
+/* heap header - allocate one of these in each heap */
+struct mcmhdef {
+ mcmhdef *mcmhnxt; /* next heap in this chain */
+};
+
+/* GLOBAL cache manager context: tracks cache manager state */
+struct mcmcx1def {
+ mcmodef **mcmcxtab; /* page table for the cache */
+ errcxdef *mcmcxerr; /* error handling context */
+ mcmhdef *mcmcxhpch; /* heap chain pointer */
+ mcscxdef mcmcxswc; /* swap manager context */
+ mclcxdef mcmcxldc; /* loader context */
+ ulong mcmcxmax; /* maximum amount of actual heap we can ever alloc */
+ mcmon mcmcxlru; /* least recently used object still in memory */
+ mcmon mcmcxmru; /* most recently used object */
+ mcmon mcmcxfre; /* head of free list */
+ mcmon mcmcxunu; /* head of unused list */
+ ushort mcmcxpage; /* last page table slot used */
+ ushort mcmcxpgmx; /* maximum number of pages we can allocate */
+ void (*mcmcxcsw)(mcmcx1def *, mcmon, mcsseg, mcsseg);
+ /* change swap handle in object to new swap handle */
+};
+
+/* CLIENT cache manager context: used by client to request mcm services */
+struct mcmcxdef {
+ mcmcx1def *mcmcxgl; /* global cache manager context */
+ uint mcmcxflg; /* flags */
+ uint mcmcxmsz; /* maximum size of mapping table */
+ void (*mcmcxldf)(void *ctx, mclhd handle, uchar *ptr,
+ ushort siz); /* callback to load objects */
+ void *mcmcxldc; /* context for load callback */
+ void (*mcmcxrvf)(void *ctx, mcmon objn); /* revert object */
+ void *mcmcxrvc; /* context for revert callback */
+ mcmon *mcmcxmtb[1]; /* mapping table */
+};
+
+/* context flags */
+#define MCMCXF_NO_PRP_DEL 0x0001 /* PRPFDEL is invalid in this game file */
+
+/* convert from a client object number to a global object number */
+/* mcmon mcmc2g(mcmcxdef *ctx, mcmon objn); */
+#define mcmc2g(ctx, objn) ((ctx)->mcmcxmtb[(objn)>>8][(objn)&255])
+
+/*
+ * FREE LIST: this is a list, headed by context->mcmcxfre and chained
+ * forward and back by mcmonxt and mcmoprv, consisting of free memory
+ * blocks. These refer to blocks in the heap that are not used by any
+ * client objects.
+ */
+/*
+ * UNUSED LIST: this is a list, headed by context->mcmcxunu and chained
+ * forward by mcmonxt (not back, because it's never necessary to take
+ * anything out of the list except at the head, nor to search the list
+ * backwards), of unused cache object entries. These entries are not
+ * associated with any client object or with any heap memory. This list
+ * is used to get a new cache object header, and deleted cache objects
+ * are placed onto this list.
+ */
+/*
+ * LRU LIST: this is a list of in-memory blocks in ascending order of
+ * recency of use by the client. Each time a client unlocks a block,
+ * the block is moved to the most recent position in the list (the end
+ * of the list). To make it fast to add a new object, we keep a pointer
+ * to the end of the list as well as to the beginning. The start of the
+ * list is at context->mcmcxlru, and is the least recently unlocked
+ * block still in memory. The end of the list is at context->mcmcxmru,
+ * and is the most recently unlocked block.
+ */
+
+/*
+ * initialize the cache manager, returning a context for cache manager
+ * operations; a null pointer is returned if insufficient heap memory is
+ * available for initialization. The 'max' argument specifies the
+ * maximum amount of actual low-level heap memory that the cache manager
+ * can ever allocate on behalf of this context (of course, it can
+ * overcommit the heap through swapping). If 'max' is less than the
+ * size of a single heap allocation, it is adjusted upwards to that
+ * minimum.
+ */
+mcmcx1def *mcmini(ulong max, uint pages, ulong swapsize,
+ osfildef *swapfp, char *swapfilename, errcxdef *errctx);
+
+/* terminate the cache manager - frees the structure and all cache memory */
+void mcmterm(mcmcx1def *ctx);
+
+/* allocate a client context */
+mcmcxdef *mcmcini(mcmcx1def *globalctx, uint pages,
+ void (*loadfn)(void *, mclhd, uchar *, ushort),
+ void *loadctx,
+ void (*revertfn)(void *, mcmon), void *revertctx);
+
+/* terminate a client context - frees the structure memory */
+void mcmcterm(mcmcxdef *ctx);
+
+
+/*
+ * Lock a cache object, bringing it into memory if necessary. Returns
+ * a pointer to the memory containing the object. A null pointer is
+ * returned in case of error. The object remains fixed in memory at the
+ * returned location until unlocked. Locks are stacked; that is, if
+ * an object is locked twice in succession, it needs to be unlocked
+ * twice in succession before it is actually unlocked.
+ */
+#ifdef MCM_NO_MACRO
+uchar *mcmlck(mcmcxdef *ctx, mcmon objnum);
+#else /* MCM_NO_MACRO */
+
+/* uchar *mcmlck(mcmcxdef *ctx, mcmon objnum); */
+#define mcmlck(ctx,num) \
+ ((mcmobje(ctx,num)->mcmoflg & MCMOFPRES ) ? \
+ ((mcmobje(ctx,num)->mcmoflg|=MCMOFLOCK), \
+ ++(mcmobje(ctx,num)->mcmolcnt), mcmobje(ctx,num)->mcmoptr) \
+ : mcmload(ctx,num))
+
+#endif /* MCM_NO_MACRO */
+
+/*
+ * Unlock a cache object, allowing it to be moved and swapped.
+ * Unlocking an object moves it to the end (i.e., most recently used)
+ * position on the LRU chain, making it the least favorable to swap out
+ * or discard. This happens at unlock time (rather than lock time)
+ * because presumably the client has been using the object the entire
+ * time it was locked. For this reason, and to keep memory unfragmented
+ * as much as possible, objects should not be kept locked except when
+ * actually in use. Note that locks nest; if an object is locked three
+ * times without an intervening unlock, it must be unlocked three times
+ * in a row. An object can be unlocked even if it's not locked; doing
+ * so has no effect.
+ */
+#ifdef MCM_NO_MACRO
+void mcmunlck(mcmcxdef *ctx, mcmon objnum);
+#else /* MCM_NO_MACRO */
+
+/* void mcmunlck(mcmcxdef *ctx, mcmon objnum); */
+#define mcmunlck(ctx,obj) \
+ ((mcmobje(ctx,obj)->mcmoflg & MCMOFLOCK) ? \
+ (--(mcmobje(ctx,obj)->mcmolcnt) ? (void)0 : \
+ ((mcmobje(ctx,obj)->mcmoflg&=(~MCMOFLOCK)), \
+ mcmuse((ctx)->mcmcxgl,mcmc2g(ctx,obj)))) : (void)0)
+
+#endif /* MCM_NO_MACRO */
+
+/*
+ * Allocate a new cache object. The new object is locked upon return.
+ * A pointer to the memory for the new object is returned, and
+ * the object number is returned at *nump. A null pointer is returned
+ * if the object cannot be allocated.
+ */
+/* uchar *mcmalo(mcmcxdef *ctx, ushort siz, mcmon *nump); */
+#define mcmalo(ctx, siz, nump) mcmalo0(ctx, siz, nump, MCMONINV, FALSE)
+
+/*
+ * Reserve space for an object, giving it a particular client object
+ * number. This doesn't actually allocate any space for the object, but
+ * just sets it up so that it can be loaded by the client when it's
+ * needed.
+ */
+void mcmrsrv(mcmcxdef *ctx, ushort siz, mcmon clinum, mclhd loadhd);
+
+/*
+ * Allocate a new cache object, and associate it with a particular
+ * client object number. An error is signalled if the client object
+ * number is already in use.
+ */
+/* uchar *mcmalonum(mcmcxdef *ctx, ushort siz, mcmon num); */
+#define mcmalonum(ctx, siz, num) mcmalo0(ctx, siz, (mcmon *)0, num, FALSE)
+
+/*
+ * Reallocate an existing object. The object's size is increased
+ * or reduced according to newsize. The object is locked if it is
+ * not already, and the address of the object's memory is returned.
+ * Note that the object can move when reallocated, even if it was
+ * locked before the call.
+ */
+uchar *mcmrealo(mcmcxdef *ctx, mcmon objnum, ushort newsize);
+
+/*
+ * Touch a cache object (rendering it dirty). When an object is
+ * written, the client must touch it to ensure that the version in
+ * memory is not discarded. The cache manager attempts to optimize
+ * activity by not writing objects that can be reconstructed from the
+ * load or swap file. Touching the object informs the cache manager
+ * that the object is different from any version it has in a swap or
+ * load file.
+ */
+/* void mcmtch(mcmcxdef *ctx, mcmon objnum); */
+#define mcmtch(ctx,obj) \
+ (mcmobje(ctx,obj)->mcmoflg |= MCMOFDIRTY)
+/* was: (mcmobje(ctx,obj)->mcmoflg |= (MCMOFDIRTY | MCMOFNODISC)) */
+
+/* get size of a cache manager object - object need not be locked */
+/* ushort mcmobjsiz(mcmcxdef *ctx, mcmon objn); */
+#define mcmobjsiz(ctx, objn) (mcmobje(ctx, objn)->mcmosiz)
+
+/* determine if object has ever been touched */
+/* int mcmobjdirty(mcmcxdef *ctx, mcmon objn); */
+#define mcmobjdirty(ctx, objn) \
+ (mcmobje(ctx, objn)->mcmoflg & (MCMOFDIRTY | MCMOFNODISC))
+
+/* get object's memory pointer - object must be locked for valid result */
+/* uchar *mcmobjptr(mcmcxdef *ctx, mcmon objn); */
+#define mcmobjptr(ctx, objn) (mcmobje(ctx, objn)->mcmoptr)
+
+/*
+ * Free an object. The memory occupied by the object is discarded, and
+ * the object may no longer be referenced.
+ */
+void mcmfre(mcmcxdef *ctx, mcmon obj);
+
+/*
+ * "Revert" an object - convert it back to original state. This
+ * routine just invokes a client callback to do the actual reversion
+ * work. The callback is called immediately if the object is already
+ * present in memory, but is deferred until the object is loaded/swapped
+ * in if the object is not in memory.
+ */
+/* void mcmrevert(mcmcxdef *ctx, mcmon objn); */
+#define mcmrevert(ctx, objn) \
+ ((mcmobje(ctx, objn)->mcmoflg & MCMOFPRES) ? \
+ ((*(ctx)->mcmcxrvf)((ctx)->mcmcxrvc, objn), DISCARD 0) \
+ : DISCARD (mcmobje(ctx, objn)->mcmoflg |= MCMOFREVRT))
+
+/* get current size of object cache */
+ulong mcmcsiz(mcmcxdef *ctx);
+
+/* change an object's swap handle (used by swapper) */
+/* void mcmcsw(mcmcx1def *ctx, ushort objn, mcsseg swapn, mcsseg oldswn); */
+#define mcmcsw(ctx, objn, swapn, oldswapn) \
+ ((*(ctx)->mcmcxcsw)(ctx, objn, swapn, oldswapn))
+
+/* ------------------------------- PRIVATE ------------------------------- */
+
+/* Unlock an object by its global handle */
+#ifdef MCM_NO_MACRO
+void mcmgunlck(mcmcx1def *ctx, mcmon objnum);
+#else /* MCM_NO_MACRO */
+
+/* void mcmgunlck(mcmcx1def *ctx, mcmon objnum); */
+#define mcmgunlck(ctx,obj) \
+ ((mcmgobje(ctx,obj)->mcmoflg & MCMOFLOCK) ? \
+ (--(mcmgobje(ctx,obj)->mcmolcnt) ? (void)0 : \
+ ((mcmgobje(ctx,obj)->mcmoflg&=(~MCMOFLOCK)), mcmuse(ctx,obj))) : \
+ (void)0)
+
+#endif /* MCM_NO_MACRO */
+
+/* real memory allocator; clients use cover macros */
+uchar *mcmalo0(mcmcxdef *ctx, ushort siz, mcmon *nump, mcmon clinum,
+ int noclitrans);
+
+/* free an object by global object number */
+void mcmgfre(mcmcx1def *ctx, mcmon obj);
+
+/* "use" an object (move to most-recent position in LRU chain) */
+void mcmuse(mcmcx1def *ctx, mcmon n);
+
+/*
+ * Load or swap in a cache object which is currently unloaded, locking
+ * it before returning. Returns a pointer to the memory containing
+ * the object, or a null pointer in case of error. The object
+ * remains fixed in memory at the returned location until unlocked.
+ */
+uchar *mcmload(mcmcxdef *ctx, mcmon objnum);
+
+/*
+ * Size of each chunk of memory we'll request from the heap manager.
+ * To cut down on wasted memory from the heap manager, we'll always make
+ * our requests in this large size, then sub-allocate out of these
+ * chunks.
+ */
+#define MCMCHUNK 32768
+
+/*
+ * number of cache entries in a page - make this a power of 2 to keep
+ * the arithmetic to find a cache object entry simple
+ */
+#define MCMPAGECNT 256
+
+/*
+ * size of a page, in bytes
+ */
+#define MCMPAGESIZE (MCMPAGECNT * sizeof(mcmodef))
+
+/*
+ * When allocating memory, and we find a free block satisfying the
+ * request, we will split the free block if doing so would result in
+ * enough space in the second block. MCMSPLIT specifies the minimum
+ * size left over that will allow a split to occur.
+ */
+#define MCMSPLIT 64
+
+/* get an object cache entyr given a GLOBAL object number */
+#define mcmgobje(ctx,num) (&((ctx)->mcmcxtab[(num)>>8][(num)&255]))
+
+/* get an object cache entry given a CLIENT object number */
+/* mcmodef *mcmobje(mcmcxdef *ctx, mcmon objnum) */
+#define mcmobje(ctx,num) mcmgobje((ctx)->mcmcxgl,mcmc2g(ctx,num))
+
+/* allocate a block that will be locked for its entire lifetime */
+void *mcmptralo(mcmcxdef *ctx, ushort siz);
+
+/* free a block allocated with mcmptralo */
+void mcmptrfre(mcmcxdef *ctx, void *block);
+
+/* change an object's swap handle */
+void mcmcswf(mcmcx1def *ctx, mcmon objn, mcsseg swapn, mcsseg oldswapn);
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/memory_cache_heap.cpp b/engines/glk/tads/tads2/memory_cache_heap.cpp
new file mode 100644
index 0000000000..fae3c5e080
--- /dev/null
+++ b/engines/glk/tads/tads2/memory_cache_heap.cpp
@@ -0,0 +1,51 @@
+/* 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/memory_cache_heap.h"
+#include "glk/tads/tads2/error.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* global to keep track of all allocations */
+IF_DEBUG(ulong mchtotmem;)
+
+uchar *mchalo(errcxdef *ctx, size_t siz, char *comment) {
+ uchar *ret;
+
+ VARUSED(comment);
+ IF_DEBUG(mchtotmem += siz;)
+
+ ret = (uchar *)osmalloc(siz);
+ if (ret)
+ return(ret);
+ else {
+ errsig(ctx, ERR_NOMEM);
+ NOTREACHEDV(uchar *);
+ return 0;
+ }
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/memory_cache_heap.h b/engines/glk/tads/tads2/memory_cache_heap.h
new file mode 100644
index 0000000000..269cdec3ad
--- /dev/null
+++ b/engines/glk/tads/tads2/memory_cache_heap.h
@@ -0,0 +1,60 @@
+/* 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.
+ *
+ */
+
+/*
+ * Memory cache heap manager
+ *
+ * This is the low-level heap manager, which maintains a list of non-relocatable,
+ * non-swappable blocks of memory. The cache manager uses the heap manager for
+ * its basic storage needs.
+ */
+
+#ifndef GLK_TADS_TADS2_MEMORY_CACHE_HEAP
+#define GLK_TADS_TADS2_MEMORY_CACHE_HEAP
+
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/error_handling.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/**
+ * Allocate a block of memory; returns pointer to the block.
+ * An out-of-memory error is signalled if insufficient memory
+ * is available. The comment is for debugging purposes only.
+ */
+uchar *mchalo(errcxdef *ctx, size_t siz, char *comment);
+
+/* allocate a structure */
+#define MCHNEW(errctx, typ, comment) \
+ ((typ *)mchalo(errctx, sizeof(typ), comment))
+
+/* free a block of memory */
+/* void mchfre(uchar *ptr); */
+#define mchfre(ptr) (osfree(ptr))
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/memory_cache_loader.cpp b/engines/glk/tads/tads2/memory_cache_loader.cpp
new file mode 100644
index 0000000000..f403299167
--- /dev/null
+++ b/engines/glk/tads/tads2/memory_cache_loader.cpp
@@ -0,0 +1,31 @@
+/* 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/memory_cache_loader.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/memory_cache_loader.h b/engines/glk/tads/tads2/memory_cache_loader.h
new file mode 100644
index 0000000000..c7ec22db57
--- /dev/null
+++ b/engines/glk/tads/tads2/memory_cache_loader.h
@@ -0,0 +1,57 @@
+/* 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.
+ *
+ */
+
+/*
+ * Memory cache swap manager
+ *
+ * The cache swap manager provides swap file services to the memory
+ * cache manager. The cache manager calls the swap manager to write
+ * objects to the swap file and read in previously swapped-out objects.
+ */
+
+#ifndef GLK_TADS_TADS2_MEMORY_CACHE_LOADER
+#define GLK_TADS_TADS2_MEMORY_CACHE_LOADER
+
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/error_handling.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/**
+ * Loader context
+ */
+struct mclcxdef {
+ errcxdef *mclcxerr; /* error handling context */
+};
+
+/**
+ * Loader handle
+ */
+typedef ulong mclhd; /* essentially a seek address */
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/memory_cache_swap.cpp b/engines/glk/tads/tads2/memory_cache_swap.cpp
new file mode 100644
index 0000000000..114a7258e9
--- /dev/null
+++ b/engines/glk/tads/tads2/memory_cache_swap.cpp
@@ -0,0 +1,31 @@
+/* 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/memory_cache_swap.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/memory_cache_swap.h b/engines/glk/tads/tads2/memory_cache_swap.h
new file mode 100644
index 0000000000..f1b9849aec
--- /dev/null
+++ b/engines/glk/tads/tads2/memory_cache_swap.h
@@ -0,0 +1,122 @@
+/* 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.
+ *
+ */
+
+/*
+ * Memory cache swap manager
+ *
+ * The cache swap manager provides swap file services to the memory
+ * cache manager. The cache manager calls the swap manager to write
+ * objects to the swap file and read in previously swapped-out objects.
+ */
+
+#ifndef GLK_TADS_TADS2_MEMORY_CACHE_SWAP
+#define GLK_TADS_TADS2_MEMORY_CACHE_SWAP
+
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/error_handling.h"
+#include "glk/tads/osfrobtads.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* Forward declarations */
+struct mcmcx1def;
+
+/**
+ * swap segment descriptor
+ */
+struct mcsdsdef {
+ ulong mcsdsptr; /* seek pointer in swap file */
+ ushort mcsdssiz; /* size of this swap segment */
+ ushort mcsdsosz; /* size of object written to segment */
+ uint mcsdsobj; /* client object ID */
+ ushort mcsdsflg; /* flags */
+#define MCSDSFINUSE 0x01 /* segment is in use */
+};
+
+/**
+ * mcsseg - swap segment handle. All swap-file segments are addressed
+ * through this handle type.
+ */
+typedef ushort mcsseg;
+
+/**
+ * Swap manager context
+ */
+struct mcscxdef {
+ osfildef *mcscxfp; /* swap file handle */
+ char *mcscxfname; /* name of swap file */
+ errcxdef *mcscxerr; /* error handling context */
+ ulong mcscxtop; /* top of swap file allocated so far */
+ ulong mcscxmax; /* maximum size of swap file we're allowed */
+ mcsdsdef **mcscxtab; /* swap descriptor page table */
+ mcsseg mcscxmsg; /* maximum segment allocated so far */
+ mcmcx1def *mcscxmem; /* memory manager context */
+};
+
+#define MCSSEGINV ((mcsseg)~0) /* invalid segment ID - error indicator */
+
+/* initialize swapper - returns 0 for success, other for error */
+void mcsini(struct mcscxdef *ctx, struct mcmcx1def *gmemctx, ulong maxsiz,
+ osfildef *fp, char *swapfilename, struct errcxdef *errctx);
+
+/* close swapper (release memory areas) */
+void mcsclose(struct mcscxdef *ctx);
+
+/**
+ * Swap an object out. The caller specifies the location and size of
+ * the object, as well as a unique handle (arbitrary, up to the caller;
+ * the only requirement is that it be unique among all caller objects
+ * and always the same for a particular caller's object) and the
+ * previous swap handle if the object ever had one. If the object is
+ * not dirty (it hasn't been written since being swapped in), and the
+ * swap manager hasn't reused the swap slot, the swap manager doesn't
+ * need to write the memory, since it already has a copy on disk;
+ * instead, it can just mark the slot as back in use. If the caller
+ * doesn't wish to take advantage of this optimization, always pass in
+ * dirty == TRUE, which will force a write regardless of the object ID.
+ */
+mcsseg mcsout(struct mcscxdef *ctx, uint objid, uchar *objptr,
+ ushort objsize, mcsseg oldswapseg, int dirty);
+
+/* Swap an object in */
+void mcsin(struct mcscxdef *ctx, mcsseg swapseg, uchar *objptr, ushort size);
+
+
+/* number of page pointers in page table (max number of pages) */
+#define MCSPAGETAB 256
+
+/* number of swap descriptors in a page */
+#define MCSPAGECNT 256
+
+/* find swap descriptor corresponding to swap segment number */
+#define mcsdsc(ctx,seg) (&(ctx)->mcscxtab[(seg)>>8][(seg)&255])
+
+/* write out a swap segment */
+void mcswrt(mcscxdef *ctx, mcsdsdef *desc, uchar *buf, ushort bufl);
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/object.cpp b/engines/glk/tads/tads2/object.cpp
new file mode 100644
index 0000000000..2ba1161d86
--- /dev/null
+++ b/engines/glk/tads/tads2/object.cpp
@@ -0,0 +1,35 @@
+/* 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/object.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/object.h b/engines/glk/tads/tads2/object.h
new file mode 100644
index 0000000000..5e2499ccc2
--- /dev/null
+++ b/engines/glk/tads/tads2/object.h
@@ -0,0 +1,396 @@
+/* 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.
+ *
+ */
+
+#ifndef GLK_TADS_TADS2_OBJECT
+#define GLK_TADS_TADS2_OBJECT
+
+#include "glk/tads/tads.h"
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/memory_cache.h"
+#include "glk/tads/tads2/property.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/**
+ * object number
+ */
+typedef ushort objnum;
+
+/**
+ * For non-class objects, we'll leave some space free in the object so
+ * that a few properties can be added without having to resize the
+ * object. Class objects will probably never have anything added, so
+ * there's no need for extra space.
+ */
+#define OBJEXTRA 64
+
+# define OBJFCLS 0x01 /* object is a class */
+
+/**
+ * The object structure is actually laid out portably, using unaligned
+ * 2-byte arrays, stored least significant byte first, for each ushort
+ * (including the objnum array for the superclasses). The actual
+ * entries are at these offsets on all machines:
+ *
+ * objws 0
+ * objflg 2
+ * objnsc 4
+ * objnprop 6
+ * objfree 8
+ * objrst 10
+ * objstat 12
+ * objsc[0] 14
+ * objsc[1] 16
+ * etc
+ *
+ * If the OBJFINDEX flag is set, the object has a property index.
+ * The index occurs after the last superclass (so it's where the
+ * property data would go if there were no index), and the property
+ * data follows. Each index entry consists of a pair of two-byte
+ * entries: the first is the property number, and the second is
+ * its offset within the object. For performance reasons, an index
+ * is only built on a class object -- whenever a property is changed
+ * within an object, the entire index must be rebuilt, because the
+ * locations of many properties within the object can be changed by
+ * a single property change in the object. The index is ordered by
+ * property number, so it can be searched using a binary search.
+ * Furthermore, "ignored" properties are excluded from the index;
+ * only the active instance of a particular property is stored.
+ * The index must be maintained by all routines that can change
+ * property information: setp, delp, revert, etc.
+ *
+ * Preceding the index table is a two-byte entry that gives the
+ * offset of the properties. Since the properties immediately
+ * follow the index, this can be used to deduce how large a space
+ * is available for the index itself.
+ */
+typedef uchar objdef;
+#define OBJDEFSIZ 14 /* "sizeof(objdef)" - size of object header w/o sc's */
+
+/* object flags */
+#define OBJFCLASS 1 /* object is a class */
+#define OBJFINDEX 2 /* object has a property index */
+#define OBJFMOD 4 /* object has been modified by a newer definition */
+
+/* undo context */
+struct objucxdef {
+ mcmcxdef *objucxmem; /* cache manager context */
+ errcxdef *objucxerr; /* error context */
+ ushort objucxsiz; /* size of the undo buffer */
+ ushort objucxhead; /* head (position of next write) */
+ ushort objucxtail; /* tail (position of oldest record) */
+ ushort objucxprv; /* previous head pointer */
+ ushort objucxtop; /* highest head value written */
+ void (*objucxcun)(void *ctx, uchar *data);
+ /* apply a client undo record */
+ ushort (*objucxcsz)(void *ctx, uchar *data);
+ /* get size of a client undo record */
+ void *objucxccx; /* client undo context */
+ uchar objucxbuf[1]; /* undo buffer */
+};
+
+/*
+ * Undo records are kept in a circular buffer allocated as part of an
+ * undo context. Offsets within the buffer are kept for the head, tail,
+ * and previous head records. The head always points to the byte at
+ * which the next undo record will be written. The previous head points
+ * to the most recently written undo record; it contains a back link to
+ * the undo record before that, and so forth back through the entire
+ * chain. (These reverse links are necessary because undo records vary
+ * in size depending on the data contained within.) The tail points to
+ * the oldest undo record that's still in the buffer. Conceptually, the
+ * head is always "above" the tail in the buffer; since the buffer is
+ * circular, the tail may have a higher address, but this just means
+ * that the buffer wraps around at the top. When the head bumps into
+ * the tail (i.e., the head address is physically below or equal to the
+ * tail address, and the head is then advanced so that its address
+ * becomes higher than the tail's), the tail is advanced by discarding
+ * as many of the least recent undo records as necessary to make room
+ * for the new head position. When the head and the previous head point
+ * to the same place, we have no undo records in the buffer.
+ */
+/**
+ * The first byte of an undo record specifies what action is to be
+ * undone. If a property was added, it is undone merely by deleting the
+ * property. If a property was changed, it is undone by setting the
+ * property back to its old value. An additional special flag indicates
+ * a "savepoint." Normally, all changes back to a savepoint will be
+ * undone.
+ */
+#define OBJUADD 1 /* a property was added (undo by deleting) */
+#define OBJUCHG 2 /* a property was changed (change back to old value) */
+#define OBJUSAV 3 /* savepoint marker (no property information) */
+#define OBJUOVR 4 /* override original property (set orig to IGNORE) */
+#define OBJUCLI 5 /* client undo record (any client data) */
+
+/*
+ * After the control byte (OBJUxxx), the object number, property
+ * number, datatype, and data value will follow; some or all of these
+ * may be omitted, depending on the control byte.
+ */
+
+/* get object flags */
+#define objflg(o) ((ushort)osrp2(((char *)(o)) + 2))
+
+/* get object flags */
+#define objsflg(o, val) oswp2(((char *)(o)) + 2, val)
+
+/* given an object pointer, get a pointer to the first prpdef */
+/* prpdef *objprp(objdef *objptr); */
+#define objprp(o) ((prpdef *)(objsc(o) + 2*objnsc(o)))
+
+/* given an object pointer, get number of properties in the prpdef */
+/* int objnprop(objdef *objptr); */
+#define objnprop(o) ((ushort)osrp2(((char *)(o)) + 6))
+
+/* set number of properties */
+/* void objsnp(objdef *objptr, int newnum); */
+#define objsnp(o,n) oswp2(((char *)(o)) + 6, n)
+
+/* given an object pointer, get offset of free space */
+/* int objfree(objdef *objptr); */
+#define objfree(o) ((ushort)osrp2(((char *)(o)) + 8))
+
+/* set free space pointer */
+/* void objsfree(objdef *objptr, int newfree); */
+#define objsfree(o,n) oswp2(((char *)(o)) + 8, n)
+
+/* get number of static properties */
+/* ushort objstat(objdef *objptr); */
+#define objstat(o) ((ushort)osrp2(((char *)(o)) + 10))
+
+/* set number of static properties */
+/* void objsetst(objdef *objptr, int newstat); */
+#define objsetst(o,n) oswp2(((char *)(o)) + 10, n)
+
+/* get reset size (size of static properties) */
+/* ushort objrst(objdef *objptr); */
+#define objrst(o) ((ushort)osrp2(((char *)(o)) + 12))
+
+/* set reset size */
+/* void objsetrst(objdef *objptr, uint newrst); */
+#define objsetrst(o,n) oswp2(((char *)(o)) + 12, n)
+
+/* given an object pointer, get first superclass pointer */
+/* uchar *objsc(objdef *objptr); */
+#define objsc(o) (((uchar *)(o)) + OBJDEFSIZ)
+
+/* given an object pointer, get number of superclasses */
+/* int objnsc(objdef *objptr); */
+#define objnsc(o) ((ushort)osrp2(((char *)(o)) + 4))
+
+/* set number of superclasses */
+/* void objsnsc(objdef *objptr, int num); */
+#define objsnsc(o,n) oswp2(((char *)(o)) + 4, n)
+
+/* given a prpdef, get the next prpdef */
+/* prpdef *objpnxt(prpdef *p); */
+#define objpnxt(p) \
+ ((prpdef *)(((uchar *)(p)) + PRPHDRSIZ + prpsize(p)))
+
+/* get pointer to free prpdef */
+/* prpdef *objpfre(objdef *objptr); */
+#define objpfre(o) ((prpdef *)(((uchar *)(o)) + objfree(o)))
+
+/* given a prpdef and an object pointer, compute the prpdef offset */
+/* uint objpofs(objdef *objptr, prpdef *propptr); */
+#define objpofs(o,p) ((uint)((p) ? (((uchar *)(p)) - ((uchar *)(o))) : 0))
+
+/* given an object pointer and a property offset, get prpdef pointer */
+/* prpdef *objofsp(objdef *objptr, uint propofs); */
+#define objofsp(o,ofs) ((prpdef *)((ofs) ? (((uchar *)(o)) + (ofs)) : 0))
+
+/*
+ * Get the first superclass of an object. If it doesn't have any
+ * superclasses, return invalid.
+ */
+objnum objget1sc(mcmcxdef *ctx, objnum objn);
+
+/*
+ * Get an object's property WITHOUT INHERITANCE. If the object has the
+ * indicated property set, the byte OFFSET of the prpdef within the
+ * object is returned. The offset will remain valid until any type of
+ * operation that sets a property in the object (such as objdelp,
+ * objsetp, or an undo operation). An offset of zero means that the
+ * property was not set in the object.
+ */
+uint objgetp(mcmcxdef *ctx, objnum objn, prpnum prop,
+ dattyp *typptr);
+
+/*
+ * Get the *ending* offset of the given property's value, without any
+ * inheritance. Returns the byte offset one past the end of the
+ * property's data.
+ */
+uint objgetp_end(mcmcxdef *ctx, objnum objn, prpnum prop);
+
+/*
+ * Get a property of an object, either from the object or from a
+ * superclass (inherited). If the inh flag is TRUE, we do not look
+ * at all in the object itself, but restrict our search to inherited
+ * properties only. We return the byte OFFSET of the prpdef within
+ * the object in which the prpdef is found; the superclass object
+ * itself is NOT locked upon return, but we will NOT unlock the
+ * object passed in. If the offset is zero, the property was not
+ * found. The offset returned is valid until any operation that
+ * sets a property in the object (such as objdelp, objsetp, or an
+ * undo operation).
+ */
+uint objgetap(mcmcxdef *ctx, noreg objnum objn, prpnum prop,
+ objnum *orn, int inh);
+
+/*
+ * expand an object by a requested amount, returning a pointer to the
+ * object's new location if it must be moved. The object will be
+ * unlocked and relocked by this call. On return, the actual amount
+ * of space ADDED to the object will be returned.
+ */
+objdef *objexp(mcmcxdef *ctx, objnum obj, ushort *siz);
+
+/*
+ * Set an object's property, deleting the original value of the
+ * property if it existed. If an undo context is provided, write an
+ * undo record for the change; if the undo context pointer is null, no
+ * undo information is retained.
+ */
+void objsetp(mcmcxdef *ctx, objnum obj, prpnum prop,
+ dattyp typ, void *val, objucxdef *undoctx);
+
+/*
+ * Delete a property. If mark_only is true, we'll only mark the
+ * property as deleted without actually reclaiming its space; this is
+ * necessary when removing a code property (type DAT_CODE) any time
+ * other code properties may follow, because p-code is not entirely
+ * self-relative and thus can't always be relocated within an object.
+ */
+void objdelp(mcmcxdef *mctx, objnum objn, prpnum prop, int mark_only);
+
+/*
+ * Set up for emitting code into an object. Writes a property header
+ * of type 'code', and returns the offset of the next free byte in the
+ * object. Call objendemt when done. The datatype argument is
+ * provided so that list generation can be done through the same
+ * mechanism, since parser lists must be converted to run-time
+ * lists via the code generator.
+ */
+uint objemt(mcmcxdef *ctx, objnum objn, prpnum prop, dattyp typ);
+
+/* done emitting code into property, finish setting object info */
+void objendemt(mcmcxdef *ctx, objnum objn, prpnum prop, uint endofs);
+
+/*
+ * Determine if undo records should be kept. Undo records should be
+ * kept only if a savepoint is present in the undo log. If no savepoint
+ * is present, adding undo records would be useless, since it will not
+ * be possible to apply the undo information.
+ */
+int objuok(objucxdef *undoctx);
+
+/*
+ * Reserve space in an undo buffer, deleting old records as needed.
+ * Returns a pointer to the reserved space.
+ */
+uchar *objures(objucxdef *undoctx, uchar cmd, ushort siz);
+
+/* advance the tail pointer in an undo buffer over the record it points to */
+void objutadv(objucxdef *undoctx);
+
+/* apply one undo record, and remove it from undo list */
+void obj1undo(mcmcxdef *mctx, objucxdef *undoctx);
+
+/*
+ * Undo back to the most recent savepoint. If there is no savepoint in
+ * the undo list, NOTHING will be undone. This prevents reaching an
+ * inconsistent state in which some, but not all, of the operations
+ * between two savepoints are undone: either all operations between two
+ * savepoints will be undone, or none will.
+ */
+void objundo(mcmcxdef *mctx, objucxdef *undoctx);
+
+/* set an undo savepoint */
+void objusav(objucxdef *undoctx);
+
+/* initialize undo context */
+objucxdef *objuini(mcmcxdef *memctx, ushort undosiz,
+ void (*undocb)(void *ctx, uchar *data),
+ ushort (*sizecb)(void *ctx, uchar *data),
+ void *callctx);
+
+/* free the undo context - releases memory allocated by objuini() */
+void objuterm(objucxdef *undoctx);
+
+/* discard all undo context (for times such as restarting) */
+void objulose(objucxdef *undoctx);
+
+/*
+ * Allocate and initialize a new object. The caller specifies the
+ * number of superclasses to be allocated, and the amount of space (in
+ * bytes) for the object's property data. The caller must fill in the
+ * superclass array. Upon return, the object is allocated and locked,
+ * and is initialized with no properties. A pointer to the object's
+ * memory is returned, and *objnptr receives the object number.
+ */
+objdef *objnew(mcmcxdef *mctx, int sccnt, ushort propspace,
+ objnum *objnptr, int classflg);
+
+/* initialize an already allocated object */
+void objini(mcmcxdef *mctx, int sccnt, objnum objn, int classflg);
+
+/*
+ * Add space for additional superclasses to an object. The object can
+ * already have some properties set (if it doesn't, it can just be
+ * reinitialized).
+ */
+void objaddsc(mcmcxdef *mctx, int sccnt, objnum objn);
+
+/*
+ * Delete an object's properties and superclasses. The 'mindel'
+ * parameter specifies the minimum property number to be deleted.
+ * Properties below this are considered "system" properties that are not
+ * to be deleted. This could be used by a development environment to
+ * store the source for an object as a special system property in the
+ * object; when the object is recompiled, all of the object's properties
+ * and superclasses must be deleted except the source property, which is
+ * retained even after recompilation.
+ */
+void objclr(mcmcxdef *mctx, objnum objn, prpnum mindel);
+
+/* Build or rebuild an object's property index */
+void objindx(mcmcxdef *mctx, objnum objn);
+
+/* set up just-compiled object: mark static part and original properties */
+void objcomp(mcmcxdef *mctx, objnum objn, int for_debug);
+
+/* revert an object to original post-compilation state */
+void objrevert(void *mctx, mcmon objn);
+
+/* reset 'ignore' flags for a newly reconstructed object */
+void objsetign(mcmcxdef *mctx, objnum objn);
+
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/opcode_defs.h b/engines/glk/tads/tads2/opcode_defs.h
new file mode 100644
index 0000000000..32519ae8f7
--- /dev/null
+++ b/engines/glk/tads/tads2/opcode_defs.h
@@ -0,0 +1,218 @@
+/* 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.
+ *
+ */
+
+#ifndef GLK_TADS_TADS2_OPCODE_DEFS
+#define GLK_TADS_TADS2_OPCODE_DEFS
+
+/*
+ * Opcode definitions
+ *
+ * Lifted largely from the old TADS, since the basic run - time interpreter's
+ * operation is essentially the same.
+ */
+
+#include "glk/tads/tads.h"
+#include "glk/tads/tads2/data.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+#define OPCPUSHNUM 1 /* push a constant numeric value */
+#define OPCPUSHOBJ 2 /* push an object */
+#define OPCNEG 3 /* unary negation */
+#define OPCNOT 4 /* logical negation */
+#define OPCADD 5 /* addition/list concatenation */
+#define OPCSUB 6 /* subtraction/list difference */
+#define OPCMUL 7 /* multiplication */
+#define OPCDIV 8 /* division */
+#define OPCAND 9 /* logical AND */
+#define OPCOR 10 /* logical OR */
+#define OPCEQ 11 /* equality */
+#define OPCNE 12 /* inequality */
+#define OPCGT 13 /* greater than */
+#define OPCGE 14 /* greater or equal */
+#define OPCLT 15 /* less than */
+#define OPCLE 16 /* less or equal */
+#define OPCCALL 17 /* call a function */
+#define OPCGETP 18 /* get property */
+#define OPCGETPDATA 19 /* get a property, allowing only data values */
+#define OPCGETLCL 20 /* get a local variable's value */
+#define OPCPTRGETPDATA 21 /* get property via pointer; only allow data */
+#define OPCRETURN 22 /* return without a value */
+#define OPCRETVAL 23 /* return a value */
+#define OPCENTER 24 /* enter a function */
+#define OPCDISCARD 25 /* discard top of stack */
+#define OPCJMP 26 /* unconditional jump */
+#define OPCJF 27 /* jump if false */
+#define OPCPUSHSELF 28 /* push current object */
+#define OPCSAY 29 /* implicit printout for doublequote strings */
+#define OPCBUILTIN 30 /* call a built-in function */
+#define OPCPUSHSTR 31 /* push a string */
+#define OPCPUSHLST 32 /* push a list */
+#define OPCPUSHNIL 33 /* push the NIL value */
+#define OPCPUSHTRUE 34 /* push the TRUE value */
+#define OPCPUSHFN 35 /* push the address of a function */
+#define OPCGETPSELFDATA 36 /* push property of self; only allow data */
+
+#define OPCPTRCALL 38 /* call function pointed to by top of stack */
+#define OPCPTRINH 39 /* inherit pointer to property (stack=prop) */
+#define OPCPTRGETP 40 /* get property by pointer (stack=obj,prop) */
+
+#define OPCPASS 41 /* pass to inherited handler */
+#define OPCEXIT 42 /* exit turn, but continue with fuses/daemons */
+#define OPCABORT 43 /* abort turn, skipping fuses/daemons */
+#define OPCASKDO 44 /* ask for a direct object */
+#define OPCASKIO 45 /* ask for indirect object and set preposition */
+
+/* explicit superclass inheritance opcodes */
+#define OPCEXPINH 46 /* "inherited <superclass>.<property>" */
+#define OPCEXPINHPTR 47 /* "inherited <superclass>.<prop-pointer>" */
+
+/*
+ * Special opcodes for peephole optimization. These are essentially
+ * pairs of operations that occur frequently so have been collapsed into
+ * a single instruction.
+ */
+#define OPCCALLD 48 /* call function and discard value */
+#define OPCGETPD 49 /* evaluate property and discard any value */
+#define OPCBUILTIND 50 /* call built-in function and discard value */
+
+#define OPCJE 51 /* jump if equal */
+#define OPCJNE 52 /* jump if not equal */
+#define OPCJGT 53 /* jump if greater than */
+#define OPCJGE 54 /* jump if greater or equal */
+#define OPCJLT 55 /* jump if less than */
+#define OPCJLE 56 /* jump if less or equal */
+#define OPCJNAND 57 /* jump if not AND */
+#define OPCJNOR 58 /* jump if not OR */
+#define OPCJT 59 /* jump if true */
+
+#define OPCGETPSELF 60 /* get property of the 'self' object */
+#define OPCGETPSLFD 61 /* get property of 'self' and discard result */
+#define OPCGETPOBJ 62 /* get property of a given object */
+ /* note: differs from GETP in that object is */
+ /* encoded into the instruction */
+#define OPCGETPOBJD 63 /* get property of an object and discard result */
+#define OPCINDEX 64 /* get an indexed entry from a list */
+
+#define OPCPUSHPN 67 /* push a property number */
+
+#define OPCJST 68 /* jump and save top-of-stack if true */
+#define OPCJSF 69 /* jump and save top-of-stack if false */
+#define OPCJMPD 70 /* discard stack and then jump unconditionally */
+
+#define OPCINHERIT 71 /* inherit a property from superclass */
+#define OPCCALLEXT 72 /* call external function */
+#define OPCDBGRET 73 /* return to debugger (no stack frame leaving) */
+
+#define OPCCONS 74 /* construct list from top two stack elements */
+#define OPCSWITCH 75 /* switch statement */
+
+#define OPCARGC 76 /* get argument count */
+#define OPCCHKARGC 77 /* check actual arguments against formal count */
+
+#define OPCLINE 78 /* line record */
+#define OPCFRAME 79 /* local variable frame record */
+#define OPCBP 80 /* breakpoint - replaces an OPCLINE instruction */
+#define OPCGETDBLCL 81 /* get debugger local */
+#define OPCGETPPTRSELF 82 /* get property pointer from self */
+#define OPCMOD 83 /* modulo */
+#define OPCBAND 84 /* binary AND */
+#define OPCBOR 85 /* binary OR */
+#define OPCXOR 86 /* binary XOR */
+#define OPCBNOT 87 /* binary negation */
+#define OPCSHL 88 /* bit shift left */
+#define OPCSHR 89 /* bit shift right */
+
+#define OPCNEW 90 /* create new object */
+#define OPCDELETE 91 /* delete object */
+
+
+/* ----- opcodes 192 and above are reserved for assignment operations ----- */
+
+/*
+ASSIGNMENT OPERATIONS
+ When (opcode & 0xc0 == 0xc0), we have an assignment operation.
+ (Note that this means that opcodes from 0xc0 up are all reserved
+ for assignment operations.) The low six bits of the opcode
+ specify exactly what kind of operation is to be performed:
+
+ bits 0-1: specifies destination type:
+ 00 2-byte operand is local number
+ 01 2-byte operand is property to set in obj at tos
+ 10 tos is index, [sp-1] is list to be indexed and set
+ 11 tos is property pointer, [sp-1] is object
+
+ bits 2-4: specifies assignment operation:
+ 000 := (direct assignment)
+ 001 += (add tos to destination)
+ 010 -= (subtract tos from destination)
+ 011 *= (multiply destination by tos)
+ 100 /= (divide destination by tos)
+ 101 ++ (increment tos)
+ 110 -- (decrement tos)
+ 111 *reserved*
+
+ bit 5: specifies what to do with value computed by assignment
+ 0 leave on stack (implies pre increment/decrement)
+ 1 discard (implies post increment/decrement)
+*/
+#define OPCASI_MASK 0xc0 /* assignment instruction */
+
+#define OPCASIDEST_MASK 0x03 /* mask to get destination field */
+#define OPCASILCL 0x00 /* assign to a local */
+#define OPCASIPRP 0x01 /* assign to an object.property */
+#define OPCASIIND 0x02 /* assign to an element of a list */
+#define OPCASIPRPPTR 0x03 /* assign property via pointer */
+
+#define OPCASITYP_MASK 0x1c /* mask to get assignment type field */
+#define OPCASIDIR 0x00 /* direct assignment */
+#define OPCASIADD 0x04 /* assign and add */
+#define OPCASISUB 0x08 /* assign and subtract */
+#define OPCASIMUL 0x0c /* assign and multiply */
+#define OPCASIDIV 0x10 /* assign and divide */
+#define OPCASIINC 0x14 /* increment */
+#define OPCASIDEC 0x18 /* decrement */
+#define OPCASIEXT 0x1c /* other - extension flag */
+
+/* extended assignment flags - next byte when OPCASIEXT is used */
+#define OPCASIMOD 1 /* modulo and assign */
+#define OPCASIBAND 2 /* binary AND and assign */
+#define OPCASIBOR 3 /* binary OR and assign */
+#define OPCASIXOR 4 /* binary XOR and assign */
+#define OPCASISHL 5 /* shift left and assign */
+#define OPCASISHR 6 /* shift right and assign */
+
+
+#define OPCASIPRE_MASK 0x20 /* mask for pre/post field */
+#define OPCASIPOST 0x00 /* increment after push */
+#define OPCASIPRE 0x20 /* increment before push */
+
+/* some composite opcodes for convenience */
+#define OPCSETLCL (OPCASI_MASK | OPCASILCL | OPCASIDIR)
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/os.cpp b/engines/glk/tads/tads2/os.cpp
index 15a291a87b..e51a4d938e 100644
--- a/engines/glk/tads/tads2/os.cpp
+++ b/engines/glk/tads/tads2/os.cpp
@@ -26,6 +26,7 @@ namespace Glk {
namespace TADS {
namespace TADS2 {
+#if 0
OS::OS(OSystem *syst, const GlkGameDescription &gameDesc) : TADS(syst, gameDesc),
status_mode(0) {
Common::fill(&status_left[0], &status_left[OSS_STATUS_STRING_LEN], '\0');
@@ -345,6 +346,7 @@ int OS::oss_getc_from_window(winid_t win) {
buffered_char = oss_convert_keystroke_to_tads(ev.val1);
return 0;
}
+#endif
} // End of namespace TADS2
} // End of namespace TADS
diff --git a/engines/glk/tads/tads2/os.h b/engines/glk/tads/tads2/os.h
index 45ab804f95..5dcc83c935 100644
--- a/engines/glk/tads/tads2/os.h
+++ b/engines/glk/tads/tads2/os.h
@@ -20,218 +20,5136 @@
*
*/
+/* Portable interfaces to OS-specific functions
+ *
+ * This file defines interfaces to certain functions that must be called
+ * from portable code, but which must have system-specific implementations.
+ */
+
#ifndef GLK_TADS_TADS2_OS
#define GLK_TADS_TADS2_OS
-#include "glk/tads/tads.h"
-#include "glk/tads/tads2/types.h"
+#include "common/system.h"
+#include "glk/tads/osfrobtads.h"
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/appctx.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
-/**
- * Operating system compatibility layer
- */
-class OS : public TADS {
-protected:
- char status_left[OSS_STATUS_STRING_LEN];
- char status_right[OSS_STATUS_STRING_LEN];
- int status_mode;
-protected:
- /**
- * Constructor
- */
- OS(OSystem *syst, const GlkGameDescription &gameDesc);
-
- /**
- * Terminates the game
- */
- void os_terminate(int rc);
-
- /**
- * \defgroup Type Conversions
- * @{
- */
-
- /**
- * Change a TADS prompt type (OS_AFP_*) into a Glk prompt type.
- */
- uint oss_convert_prompt_type(int type);
-
- /**
- * Change a TADS file type (OSFT*) into a Glk file type.
- */
- uint oss_convert_file_type(int type);
-
- /**
- * Change a fileref ID (frefid_t) to a special string and put it in the
- * buffer which is passed to it. The string is given by
- * OSS_FILEREF_STRING_PREFIX + 'nnnnn' + OSS_FILEREF_STRING_SUFFIX
- * where 'nnnnn' is the frefid_t pointer converted into a string of decimal
- * numbers. This is only really practical for 32-bit pointers; if we use
- * 64-bit pointers I'll have to start using a hash table or use hex
- * numbers.
- */
- uint oss_convert_fileref_to_string(frefid_t file_to_convert, char *buffer, int buf_len);
-
- /**
- * Turn a filename or a special fileref string into an actual fileref.
- * Notice that, since Glk doesn't know paths, we take this opportunity to
- * call oss_check_path, which should do the OS-dependent path changing
- * in the event that the filename contains path information
- */
- frefid_t oss_convert_string_to_fileref(char *buffer, uint usage);
-
- /**
- * Tell us if the passed string is a hashed fileref or not
- */
- bool oss_is_string_a_fileref(char *buffer);
-
- /**
- * Change a Glk key into a TADS one, using the CMD_xxx codes
- */
- unsigned char oss_convert_keystroke_to_tads(uint key);
-
- /**@}*/
-
- /**
- * \defgroup Directory/File methods
- * @{
- */
-
- /**
- * If a filename contains path information, change dirs to that path.
- * Returns true if the path was fiddled with
- */
- bool oss_check_path(char *filename);
-
- /**
- * In case we changed directories in oss_check_path, change back to the
- * original executable directory
- */
- void oss_revert_path();
-
- /**
- * Open a stream, given a string, usage, and a filemode. tadsusage is the
- * TADS filemode (OSFT*); tbusage is either fileusage_TextMode or
- * fileusage_BinaryMode (from Glk).
- */
- osfildef *oss_open_stream(char *buffer, uint tadsusage, uint tbusage,
- uint fmode, uint rock);
-
- /**
- * Get a pointer to the root name portion of a filename. This is the part
- * of the filename after any path or directory prefix. For example, on
- * Unix, given the string "/home/mjr/deep.gam", this function should return
- * a pointer to the 'd' in "deep.gam". If the filename doesn't appear to
- * have a path prefix, it should simply return the argument unchanged.
- */
- const char *os_get_root_name(const char *buf) const { return buf; }
-
- /**
- * Open a file for access
- */
- osfildef *osfoprb(const char *fname, uint typ = 0);
-
- /**
- * Receive notification that a character mapping file has been loaded. We
- * don't need to do anything with this information, since we we're relying
- * on the Glk layer and ScummVM backend to handle all that
- */
- void os_advise_load_charmap(const char *id, const char *ldesc, const char *sysinfo) {
- // No implementation needed
- }
-
- /**
- * Generate a filename for a character mapping table. On Windows, the
- * filename is always simply "win" plus the internal ID plus ".tcp".
- */
- void os_gen_charmap_filename(char *filename, const char *internal_id,
- const char *argv0);
-
- /**@}*/
-
- /**
- * \defgroup The main text area print routines
- * @{
- */
-
- /**
- * Process hilighting codes while printing a string
- */
- void oss_put_string_with_hilite(winid_t win, const char *str, size_t len);
-
- /**
- * Status line handling
- */
- void oss_draw_status_line();
-
- void oss_change_status_string(char *dest, const char *src, size_t len);
- void oss_change_status_left(const char *str, size_t len);
- void oss_change_status_right(const char *str);
- int os_get_status() const { return status_mode; }
-
- /**
- * Flush the output
- */
- void os_flush();
-
- /**
- * Print a null terminated string
- */
- void os_printz(const char *str) { os_print(str, strlen(str)); }
-
- /**
- * Print a string
- */
- void os_print(const char *str, size_t len);
-
- /**@}*/
-
- /**
- * \defgroup Memory routines
- * @{
- */
-
- /**
- * Compare two strings
- */
- int memicmp(const char *s1, const char *s2, int len);
-
- /**@}*/
-
- /**
- * \defgroup Input routines
- * @{
- */
-
- /**
- * Wait for a key to be hit
- */
- void os_waitc(void) { os_getc(); }
-
- /**
- * Get a character from the keyboard. For extended characters, return 0,
- * then return the extended key at the next call to this function
- */
- int os_getc() {
- return oss_getc_from_window(story_win);
- }
-
- /**
- * Accept a keystroke in the passed window
- */
- int oss_getc_from_window(winid_t win);
-
-
- /**
- * Print a message (with os_print) and wait for a key
- */
- void os_expause();
-
- /**@}*/
+
+/* ------------------------------------------------------------------------ */
+/*
+ * A note on character sets:
+ *
+ * Except where noted, all character strings passed to and from the
+ * osxxx functions defined herein use the local operating system
+ * representation. On a Windows machine localized to Eastern Europe,
+ * for example, the character strings passed to and from the osxxx
+ * functions would use single-byte characters in the Windows code page
+ * 1250 representation.
+ *
+ * Callers that use multiple character sets must implement mappings to
+ * and from the local character set when calling the osxxx functions.
+ * The osxxx implementations are thus free to ignore any issues related
+ * to character set conversion or mapping.
+ *
+ * The osxxx implementations are specifically not permitted to use
+ * double-byte Unicode as the native character set, nor any other
+ * character set where a null byte could appear as part of a non-null
+ * character. In particular, callers may assume that null-terminated
+ * strings passed to and from the osxxx functions contain no embedded
+ * null bytes. Multi-byte character sets (i.e., character sets with
+ * mixed single-byte and double-byte characters) may be used as long as
+ * a null byte is never part of any multi-byte character, since this
+ * would guarantee that a null byte could always be taken as a null
+ * character without knowledge of the encoding or context.
+ */
+
+/* ------------------------------------------------------------------------ */
+/*
+ * "Far" Pointers. Most platforms can ignore this. For platforms with
+ * mixed-mode addressing models, where pointers of different sizes can
+ * be used within a single program and hence some pointers require
+ * qualification to indicate that they use a non-default addressing
+ * model, the keyword OSFAR should be defined to the appropriate
+ * compiler-specific extension keyword.
+ *
+ * If you don't know what I'm talking about here, you should just ignore
+ * it, because your platform probably doesn't have anything this
+ * sinister. As of this writing, this applies only to MS-DOS, and then
+ * only to 16-bit implementations that must interact with other 16-bit
+ * programs via dynamic linking or other mechanisms.
+ */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * ANSI C99 exact-size integer types.
+ *
+ * C99 defines a set of integer types with exact bit sizes, named intXX_t
+ * for a signed integer with XX bits, and uintXX_t for unsigned. XX can be
+ * 8, 16, 32, or 64. TADS uses the 16- and 32-bit sizes, so each platform
+ * is responsible for defining the following types:
+ *
+ *. int16_t - a signed integer type storing EXACTLY 16 bits
+ *. uint16_t - an unsigned integer type storing EXACTLY 16 bits
+ *. int32_t - a signed integer type storing EXACTLY 32 bits
+ *. uint32_t - an unsigned integer type storing EXACTLY 32 bits
+ *
+ * Many modern compilers provide definitions for these types via the
+ * standard header stdint.h. Where stdint.h is provided, the platform code
+ * can merely #include <stdint.h>.
+ *
+ * For compilers where stdint.h isn't available, you must provide suitable
+ * typedefs. Note that the types must be defined with the exact bit sizes
+ * specified; it's not sufficient to use a bigger type, because we depend
+ * in some cases on overflow and sign extension behavior at the specific
+ * bit size.
+ */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Thread-local storage (TLS).
+ *
+ * When TADS is compiled with threading support, it requires some variables
+ * to be "thread-local". This means that the variables have global scope
+ * (so they're not stored in "auto" variables on the stack), but each
+ * thread has a private copy of each such variable.
+ *
+ * Nearly all systems that support threads also support thread-local
+ * storage. Like threading support itself, though, TLS support is at
+ * present implemented only in non-portable OS APIs rather than standard C
+ * language features. TLS is a requirement if TADS is compiled with
+ * threading, but it's not needed for non-threaded builds. TADS only
+ * requires threading at present (version 3.1) for its network features;
+ * since these features are optional, systems that don't have threading and
+ * TLS support will simply need to disable the network features, which will
+ * allow all of the threading and TLS definitions in osifc to be omitted.
+ *
+ * There appear to be two common styles of TLS programming models. The
+ * first provides non-standard compiler syntax for declarative creation of
+ * thread-local variables. The Microsoft (on Windows) and Gnu compilers
+ * (on Linux and Unix) do this: they provide custom storage class modifiers
+ * for declaring thread locals (__declspec(thread) for MSVC, __thread for
+ * gcc). Compilers that support declarative thread locals handle the
+ * implementation details through code generation, so the program merely
+ * needs to add the special TLS storage class qualifier to an otherwise
+ * ordinary global variable declaration, and then can access the thread
+ * local as though it were an ordinary global.
+ *
+ * The second programming model is via explicit OS API calls to create,
+ * initialize, and access thread locals. pthreads provides such an API, as
+ * does Win32. In fact, when you use the declarative syntax with MSVC or
+ * gcc, the compiler generates the appropriate API calls, but the details
+ * are transparent to the program; in contrast, when using pthreads
+ * directly, the program must actively call the relevant APIs.
+ *
+ * It's probably the case that every system that has compiler-level support
+ * for declarative thread local creation also has procedural APIs, so the
+ * simplest way to abstract the platform differences would be to do
+ * everything in terms of APIs. However, it seems likely that compilers
+ * with declarative syntax might be able to generate more efficient code,
+ * since optimizers always benefit from declarative information. So we'd
+ * like to use declarative syntax whenever it's available, but fall back on
+ * explicit API calls when it's not. So our programming model is a union
+ * of the two styles:
+ *
+ * 1. For each thread local, declare the thread local:
+ *. OS_DECL_TLS(char *, my_local);
+ *
+ * 2. At main program startup (for the main thread only), initialize each
+ * thread local:
+ *. os_tls_create(my_local);
+ *
+ * 3. Never get or set the value of a thread local directly; instead, use
+ * the get/set functions:
+ *. char *x = os_tls_get(char *, my_local);
+ *. os_tls_set(my_local, "hello");
+ *
+ * One key feature of this implementation is that each thread local is
+ * stored as a (void *) value. We do it this way to allow a simple direct
+ * mapping to the pthreads APIs, since that's going to be the most common
+ * non-declarative implementation. This means that a thread local variable
+ * can contain any pointer type, but *only* a pointer type. The standard
+ * pattern for dealing with anything more ocmplex is the same as in
+ * pthreads: gather up the data into a structure, malloc() an instance of
+ * that structure at entry to each thread (including the main thread), and
+ * os_tls_set() the variable to contain a pointer to that structure. From
+ * then on, use os_tls_set(my_struct *, my_local)->member to access the
+ * member variables in the structure. And finally, each thread must delete
+ * the structure at thread exit.
+ */
+
+/*
+ *
+ * Declare a thread local.
+ *
+ * - For compilers that support declarative TLS variables, the local OS
+ * headers should use the compiler support by #defining OS_DECL_TLS to the
+ * appropriate local declarative keyword.
+ *
+ * - For systems without declarative TLS support but with TLS APIs, the
+ * global declared by this macro actually stores the slot ID (what pthreads
+ * calls the "key") for the variable. This macro should therefore expand
+ * to a declaration of the appropriate API type for a slot ID; for example,
+ * on pthreads, #define OS_DECL_TLS(t, v) pthread_key_t v.
+ *
+ * - For builds with no thread support, simply #define this to declare the
+ * variable as an ordinary global: #define OS_DECL_TLS(t, v) t v.
+ */
+/* #define OS_DECL_TLS(typ, varname) __thread typ varname */
+
+/*
+ * For API-based systems without declarative support in the compiler, the
+ * main program startup code must explicitly create a slot for each thread-
+ * local variable by calling os_tls_create(). The API returns a slot ID,
+ * which is shared among threads and therefore can be stored in an ordinary
+ * global variable. OS_DECL_TLS will have declared the global variable
+ * name in this case as an ordinary global of the slot ID type. The
+ * os_tls_create() macro should therefore expand to a call to the slot
+ * creation API, storing the new slot ID in the global.
+ *
+ * Correspondingly, before the main thread exits, it should delete each
+ * slot it created, b calling os_tls_delete().
+ *
+ * For declarative systems, there's no action required here, so these
+ * macros can be defined to empty.
+ */
+/* #define os_tls_create(varname) pthread_key_create(&varname, NULL) */
+/* #define os_tls_delete(varname) pthread_key_delete(varname) */
+
+
+/*
+ * On API-based systems, each access to get or set the thread local
+ * requires an API call, using the slot ID stored in the actual global to
+ * get the per-thread instance of the variable's storage.
+ *. #define os_tls_get(typ, varname) ((typ)pthread_getspecific(varname))
+ *. #define os_tls_set(varname, val) pthread_setspecific(varname, val)
+ *
+ * On declarative systems, the global variable itself is the thread local,
+ * so get/set can be implemented as direct access to the variable.
+ *. #define os_tls_get(typ, varname) varname
+ *. #define os_tls_set(varname, val) varname = (val)
+ */
+
+/*
+ * Common TLS definitions - declarative thread locals
+ *
+ * For systems with declarative TLS support in the compiler, the OS header
+ * can #define OS_DECLARATIVE_TLS to pick up suitable definitions for the
+ * os_tls_xxx() macros. The OS header must separately define OS_DECL_TLS
+ * as appropriate for the local system.
+ */
+#ifdef OS_DECLARATIVE_TLS
+#define os_tls_create(varname)
+#define os_tls_delete(varname)
+#define os_tls_get(typ, varname) varname
+#define os_tls_set(varname, val) varname = (val)
+#endif
+
+/*
+ * Common TLS definitions - pthreads
+ *
+ * For pthreads systems without declarative TLS support in the compiler,
+ * the OS header can simply #define OS_PTHREAD_TLS to pick up the standard
+ * definitions below.
+ */
+#ifdef OS_PTHREAD_TLS
+#include <pthread.h>
+#define OS_DECL_TLS(typ, varname) pthread_key_t varname
+#define os_tls_create(varname) pthread_key_create(&varname, NULL)
+#define os_tls_delete(varname) pthread_key_delete(varname)
+#define os_tls_get(typ, varname) ((typ)pthread_getspecific(varname))
+#define os_tls_set(varname, val) pthread_setspecific(varname, val)
+#endif
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * <time.h> definitions.
+ *
+ * os_time() should act like Unix time(), returning the number of seconds
+ * elapsed since January 1, 1970 at midnight UTC.
+ *
+ * The original Unix <time.h> package defined time_t as a 32-bit signed
+ * int, and many subsequent C compilers on other platforms followed suit.
+ * A signed 32-bit time_t has the well-known year-2038 problem; some later
+ * C compilers tried to improve matters by using an unsigned 32-bit time_t
+ * instead, but for many purposes this is even worse since it can't
+ * represent any date before 1/1/1970. *Most* modern compilers solve the
+ * problem once and for all (for 300 billion years in either direction of
+ * 1/1/1970, anyway - enough to represent literally all of eternity in most
+ * current cosmological models) by defining time_t as a signed 64-bit int.
+ * But some compilers stubbornly stick to the old 32-bit time_t even in
+ * newer versions, for the sake of compatibility with older code that might
+ * be lax about mixing time_t's with ordinary int's. E.g., MSVC2003 does
+ * this. Fortunately, some of these compilers (such as MSVC2003 again)
+ * also define a parallel, transitional set of 64-bit time functions that
+ * you can use by replacing all references to the standard time_t and
+ * related names with the corresponding 64-bit names.
+ *
+ * We'd really like to use a 64-bit time_t wherever we can - the TADS
+ * release cycle can be a bit slow, and we don't want 2038 to sneak up on
+ * us and catch us unawares. So for those compilers that offer a choice of
+ * 32 or 64 bits, we'd like to select the 64 bit version. To facilitate
+ * this, we define covers here for the time.h types and functions that we
+ * use. On platforms where the regular time_t is already 64 bits, or where
+ * there's no 64-bit option at all, you can simply do nothing - the
+ * defaults defined here use the standard time_t typedef and functions, so
+ * that's what you'll get if you don't define these in the OS-specific
+ * headers for your platform. For compilers that provide both a 32-bit
+ * time_t and a 64-bit other_time_t, the OS headers should #define these
+ * macros in terms of those compiler-specific 64-bit names.
+ */
+#ifndef os_time_t
+# define os_time_t TimeDate
+# define os_gmtime(t) gmtime(t)
+# define os_localtime(t) localtime(t)
+# define os_time(t) time(t)
+#endif
+
+/*
+ * Initialize the time zone. This routine is meant to take care of any
+ * work that needs to be done prior to calling localtime() and other
+ * time-zone-dependent routines in the run-time library. For DOS and
+ * Windows, we need to call the run-time library routine tzset() to set up
+ * the time zone from the environment; most systems shouldn't need to do
+ * anything in this routine. It's sufficient to call this once during the
+ * process lifetime, since it's meant to perform static initialization that
+ * lasts as long as the process is running.
+ */
+#ifndef os_tzset
+void os_tzset(void);
+#endif
+
+/*
+ * Higher-precision time. This retrieves the same time information as
+ * os_time() (i.e., the elapsed time since the standard Unix Epoch, January
+ * 1, 1970 at midnight UTC), but retrieves it with the highest precision
+ * available on the local system, up to nanosecond precision. If less
+ * precision is available, that's fine; just return the time to the best
+ * precision available, but expressed in terms of the number of
+ * nanoseconds. For example, if you can retrieve milliseconds, you can
+ * convert that to nanoseconds by multiplying by 1,000,000.
+ *
+ * On return, fills in '*seconds' with the number of whole seconds since
+ * the Epoch, and fills in '*nanoseconds' with the fractional portion,
+ * expressed in nanosceconds. Note that '*nanoseconds' is merely the
+ * fractional portion of the time, so 0 <= *nanoseconds < 1000000000.
+ */
+void os_time_ns(os_time_t *seconds, long *nanoseconds);
+
+/*
+ * Get the local time zone name, as a location name in the IANA zoneinfo
+ * database. For example, locations using US Pacific Time should return
+ * "America/Los_Angeles".
+ *
+ * Returns true if successful, false if not. If the local operating system
+ * doesn't have a way to obtain this information, or if it's not available
+ * in the local machine's configuration, this returns false.
+ *
+ * The zoneinfo database is also known as the Olson or TZ (timezone)
+ * database; it's widely used on Unix systems as the definitive source of
+ * local time zone settings. See http://www.iana.org/time-zones for more
+ * information.
+ *
+ * On many Unix systems, the TZ environment variable contains the zoneinfo
+ * zone name when its first character is ':'. Windows uses a proprietary
+ * list of time zone names that can be mapped to zoneinfo names via a
+ * hand-coded list (such a list is maintained in the Unicode CLDR; our
+ * Windows implementation uses the CLDR list to generate the mapping).
+ * MacOS X uses zoneinfo keys directly; /etc/localtime is a link to the
+ * zoneinfo file for the local zone as set via the system preferences.
+ *
+ * os_tzset() must be invoked at some point before this routine is called.
+ */
+int os_get_zoneinfo_key(char *buf, size_t buflen);
+
+/*
+ * Get a description of the local time zone. Fills in '*info' with the
+ * available information. Returns true on success, false on failure.
+ *
+ * See osstzprs.h/.c for a portable implementation of a parser for
+ * POSIX-style TZ strings. That can serve as a full implementation of this
+ * function for systems that use the POSIX TZ environment variable syntax
+ * to specify the timezone. (That routine simply parses a string from any
+ * source, so it can be used to parse the TZ syntax even on systems where
+ * the string comes from somewhere other than the TZ environment variable.)
+ *
+ * os_tzset() must be invoked at some point before this routine is called.
+ *
+ * The following two structures are used for the return information:
+ *
+ * os_tzrule_t - Timezone Rule structure. This describes a rule for an
+ * annual transition between daylight savings time and standard time in a
+ * time zone. Most timezones that have recurring standard/daylight changes
+ * require two of these rules, one for switching to daylight time in the
+ * spring and one for switching to standard time in the fall.
+ *
+ * os_tzinfo_t - Timezone Information structure. This describes a
+ * timezone's clock settings, name(s), and rules for recurring annual
+ * changes between standard time and daylight time, if applicable.
+ */
+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
+ * day" in the POSIX docs, thus the "J", even though it's a bit of a
+ * misnomer.)(Because of the invariance of the mapping from J-number to
+ * date, this is just an obtuse way of specifying a month/day date.
+ * But even so, we'll let the OS layer relay this back to us in
+ * J-number format and count on the portable caller to work out the
+ * date, rather than foisting that work on each platform
+ * implementation.)
+ */
+ int jday;
+
+ /*
+ * Day of year, 1-366, counting Feb 29 on leap years; set to 0 if not
+ * used; ignored if 'jday' is nonzero. This corresponds to the Julian
+ * day sans "J" in TZ strings (almost - that TZ format uses 0-365 as
+ * its range, so bump it up by one when parsing a TZ string). This
+ * format is even more obtuse than the J-day format, in that it doesn't
+ * even have an invariant month/day mapping (not after day 59, anyway -
+ * day 60 is either February 29 or March 1, depending on the leapness
+ * of the year, and every day after that is similarly conditional). As
+ * far as I can tell, no one uses this option, so I'm not sure why it
+ * exists. The zoneinfo source format doesn't have a way to represent
+ * it, which says to me that no one has ever used it in a statutory DST
+ * start/end date definition in the whole history of time zones around
+ * the world, since the whole history of time zones around the world is
+ * exactly what the zoneinfo database captures in exhaustive and
+ * painstaking detail. If anyone had ever used it in defining a time
+ * zone, zoneinfo would have an option for it. My guess is that it's a
+ * fossilized bug from some early C RTL that's been retained out of an
+ * abundance of caution vis-a-vis compatibility, and was entirely
+ * replaced in practice by the J-number format as soon as someone
+ * noticed the fiddly leap year behavior. But for the sake of
+ * completeness...
+ */
+ int yday;
+
+ /*
+ * The month (1-12), week of the month, and day of the week (1-7 for
+ * Sunday to Saturday). Week 1 is the first week in which 'day'
+ * occurs, week 2 is the second, etc.; week 5 is the last occurrence of
+ * 'day' in the month. These fields are used for "second Sunday in
+ * March" types of rules. Set these to zero if they're not used;
+ * they're ignored in any case if 'jday' or 'yday' are non-zero.
+ */
+ int month;
+ int week;
+ int day;
+
+ /* time of day, in seconds after midnight (e.g., 2AM is 120 == 2*60*60) */
+ int time;
+};
+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
+ * east of GMT and negative for zones west: New York standard time
+ * (EST) is 5 hours west of GMT, so its offset is -5*60*60.
+ *
+ * Set both of these fields (if possible) regardless of whether
+ * standard or daylight time is currently in effect in the zone. The
+ * caller will select which offset to use based on the start/end rules,
+ * or based on the 'is_dst' flag if no rules are available.
+ *
+ * If it's only possible to determine the current wall clock offset, be
+ * it standard or daylight time, and it's not possible to determine the
+ * time difference between the two, simply set both of these to the
+ * current offset. This information isn't available from the standard
+ * C library, and many OS APIs also lack it.
+ */
+ int32_t std_ofs;
+ int32_t dst_ofs;
+
+ /*
+ * The abbreviations for the local zone's standard time and daylight
+ * time, respectively, when displaying date/time values. E.g., "EST"
+ * and "EDT" for US Eastern Time. If the zone doesn't observe daylight
+ * time (it's on standard time year round), set dst_abbr to an empty
+ * string.
+ *
+ * As with std_ofs and dst_ofs, you can set both of these to the same
+ * string if it's only possible to determine the one that's currently
+ * in effect.
+ */
+ char std_abbr[16];
+ char dst_abbr[16];
+
+ /*
+ * The ongoing rules for switching between daylight and standard time
+ * in this zone, if available. 'dst_start' is the date when daylight
+ * savings starts, 'dst_end' is the date when standard time resumes.
+ * Set all fields to 0 if the start/stop dates aren't available, or the
+ * zone is on standard time year round.
+ */
+ struct os_tzrule_t dst_start;
+ struct os_tzrule_t dst_end;
+
+ /*
+ * True -> the zone is CURRENTLY on daylight savings time; false means
+ * it's currently on standard time.
+ *
+ * This is only used if the start/end rules aren't specified. In the
+ * absence of start/end rules, there's no way to know when the current
+ * standard/daylight phase ends, so we'll have to assume that the
+ * current mode is in effect permanently. In this case, the caller
+ * will use only be able to use the offset and abbreviation for the
+ * current mode and will have to ignore the other one.
+ */
+ int is_dst;
};
+int os_get_timezone_info(struct os_tzinfo_t *info);
+
+
+/*
+ * Get the current system high-precision timer. This function returns a
+ * value giving the wall-clock ("real") time in milliseconds, relative to
+ * any arbitrary zero point. It doesn't matter what this value is relative
+ * to -- the only important thing is that the values returned by two
+ * different calls should differ by the number of actual milliseconds that
+ * have elapsed between the two calls. This might be the number of
+ * milliseconds since the computer was booted, since the current user
+ * logged in, since midnight of the previous night, since the program
+ * started running, since 1-1-1970, etc - it doesn't matter what the epoch
+ * is, so the implementation can use whatever's convenient on the local
+ * system.
+ *
+ * True millisecond precision isn't required. Each implementation should
+ * simply use the best precision available on the system. If your system
+ * doesn't have any kind of high-precision clock, you can simply use the
+ * time() function and multiply the result by 1000 (but see the note below
+ * about exceeding 32-bit precision).
+ *
+ * However, it *is* required that the return value be in *units* of
+ * milliseconds, even if your system clock doesn't have that much
+ * precision; so on a system that uses its own internal clock units, this
+ * routine must multiply the clock units by the appropriate factor to yield
+ * milliseconds for the return value.
+ *
+ * It is also required that the values returned by this function be
+ * monotonically increasing. In other words, each subsequent call must
+ * return a value that is equal to or greater than the value returned from
+ * the last call. On some systems, you must be careful of two special
+ * situations.
+ *
+ * First, the system clock may "roll over" to zero at some point; for
+ * example, on some systems, the internal clock is reset to zero at
+ * midnight every night. If this happens, you should make sure that you
+ * apply a bias after a roll-over to make sure that the value returned from
+ * this return continues to increase despite the reset of the system clock.
+ *
+ * Second, a 32-bit signed number can only hold about twenty-three days
+ * worth of milliseconds. While it seems unlikely that a TADS game would
+ * run for 23 days without a break, it's certainly reasonable to expect
+ * that the computer itself may run this long without being rebooted. So,
+ * if your system uses some large type (a 64-bit number, for example) for
+ * its high-precision timer, you may want to store a zero point the very
+ * first time this function is called, and then always subtract this zero
+ * point from the large value returned by the system clock. If you're
+ * using time(0)*1000, you should use this technique, since the result of
+ * time(0)*1000 will almost certainly not fit in 32 bits in most cases.
+ */
+long os_get_sys_clock_ms(void);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Hardware Configuration. Define the following functions appropriately
+ * for your hardware. For efficiency, these functions should be defined
+ * as macros if possible.
+ *
+ * Note that these hardware definitions are independent of the OS, at
+ * least to the extent that your OS can run on multiple types of
+ * hardware. So, rather than combining these definitions into your
+ * osxxx.h header file, we recommend that you put these definitions in a
+ * separate h_yyy.h header file, which can be configured into os.h with
+ * an appropriate "_M_yyy" preprocessor symbol. Refer to os.h for
+ * details of configuring the hardware include file.
+ */
+
+/*
+ * Round a size up to worst-case alignment boundary. For example, on a
+ * platform where the largest type must be aligned on a 4-byte boundary,
+ * this should round the value up to the next higher mutliple of 4 and
+ * return the result.
+ */
+/* size_t osrndsz(size_t siz); */
+
+/*
+ * Round a pointer up to worst-case alignment boundary.
+ */
+/* void *osrndpt(void *ptr); */
+
+/*
+ * Read an unaligned portable unsigned 2-byte value, returning an int
+ * value. The portable representation has the least significant byte
+ * first, so the value 0x1234 is represented as the byte 0x34, followed
+ * by the byte 0x12.
+ *
+ * The source value must be treated as unsigned, but the result is
+ * signed. This is significant on 32- and 64-bit platforms, because it
+ * means that the source value should never be sign-extended to 32-bits.
+ * For example, if the source value is 0xffff, the result is 65535, not
+ * -1.
+ */
+/* int osrp2(unsigned char *p); */
+
+/*
+ * Read an unaligned portable signed 2-byte value, returning int. This
+ * differs from osrp2() in that this function treats the source value as
+ * signed, and returns a signed result; hence, on 32- and 64-bit
+ * platforms, the result must be sign-extended to the int size. For
+ * example, if the source value is 0xffff, the result is -1.
+ */
+/* int osrp2s(unsigned char *p); */
+
+/*
+ * Write unsigned int to unaligned portable 2-byte value. The portable
+ * representation stores the low-order byte first in memory, so
+ * oswp2(0x1234) should result in storing a byte value 0x34 in the first
+ * byte, and 0x12 in the second byte.
+ */
+/* void oswp2(unsigned char *p, unsigned int i); */
+
+/*
+ * Write signed int to unaligned portable 2-byte value. Negative values
+ * must be stored in two's complement notation. E.g., -1 is stored as
+ * FF.FF, -32768 is stored as 00.80 (little-endian).
+ *
+ * Virtually all modern hardware uses two's complement notation as the
+ * native representation, which makes this routine a trivial synonym of
+ * osrp2() (i.e., #define oswp2s(p,i) oswp2(p,i)). We distinguish the
+ * signed version on the extremely off chance that TADS is ever ported to
+ * wacky hardware with a different representation for negative integers
+ * (one's complement, sign bit, etc).
+ */
+/* void oswp2s(unsigned char *p, int i); */
+
+/*
+ * Read an unaligned unsigned portable 4-byte value, returning long. The
+ * underlying value should be considered signed, and the result is signed.
+ * The portable representation stores the bytes starting with the least
+ * significant: the value 0x12345678 is stored with 0x78 in the first byte,
+ * 0x56 in the second byte, 0x34 in the third byte, and 0x12 in the fourth
+ * byte.
+ */
+/* unsigned long osrp4(unsigned char *p); */
+
+/*
+ * Read an unaligned signed portable 4-byte value, returning long.
+ */
+/* long osrp4s(unsigned char *p); */
+
+/*
+ * Write an unsigned long to an unaligned portable 4-byte value. The
+ * portable representation stores the low-order byte first in memory, so
+ * 0x12345678 is written to memory as 0x78, 0x56, 0x34, 0x12.
+ */
+/* void oswp4(unsigned char *p, unsigned long l); */
+
+/*
+ * Write a signed long, using little-endian byte order and two's complement
+ * notation for negative numbers. This is a trivial synonym for oswp4()
+ * for all platforms with native two's complement arithmetic (which is
+ * virtually all modern platforms). See oswp2s() for more discussion.
+ */
+/* void oswp4s(unsigned char *p, long l); */
+
+/*
+ * For convenience and readability, the 1-byte integer (signed and
+ * unsigned) equivalents of the above.
+ */
+#define osrp1(p) (*(unsigned char *)(p))
+#define osrp1s(p) (*(signed char *)(p))
+#define oswp1(p, b) (*(unsigned char *)(p) = (b))
+#define oswp1s(p, b) (*(signed char *)(p) = (b))
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * varargs va_copy() extension.
+ *
+ * On some compilers, va_list is a reference type. This means that if a
+ * va_list value is passed to a function that uses va_arg() to step through
+ * the referenced arguments, the caller's copy of the va_list might be
+ * updated on return. This is problematic in cases where the caller needs
+ * to use the va_list again in another function call, since the va_list is
+ * no longer pointing to the first argument for the second call. C99 has a
+ * solution in the form of the va_copy() macro. Unfortunately, this isn't
+ * typically available in pre-C99 compilers, and isn't standard in *any*
+ * C++ version. We thus virtualize it here in a macro.
+ *
+ * os_va_copy() has identical semantics to C99 va_copy(). A matching call
+ * to os_va_copy_end() must be made for each call to os_va_copy() before
+ * the calling function returns; this has identical semantics to C99
+ * va_end().
+ *
+ * Because our semantics are identical to the C99 version, we provide a
+ * default definition here for compilers that define va_copy(). Platform
+ * headers must provide suitable definitions only if their compilers don't
+ * have va_copy(). We also provide a definition for GCC compilers that
+ * define the private __va_copy macro, which also has the same semantics.
+ */
+#ifdef va_copy
+# define os_va_copy(dst, src) va_copy(dst, src)
+# define os_va_copy_end(dst) va_end(dst)
+#else
+# if defined(__GNUC__) && defined(__va_copy)
+# define os_va_copy(dst, src) __va_copy(dst, src)
+# define os_va_copy_end(dst) va_end(dst)
+# endif
+#endif
+
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Platform Identifiers. You must define the following macros in your
+ * osxxx.h header file:
+ *
+ * OS_SYSTEM_NAME - a string giving the system identifier. This string
+ * must contain only characters that are valid in a TADS identifier:
+ * letters, numbers, and underscores; and must start with a letter or
+ * underscore. For example, on MS-DOS, this string is "MSDOS".
+ *
+ * OS_SYSTEM_LDESC - a string giving the system descriptive name. This
+ * is used in messages displayed to the user. For example, on MS-DOS,
+ * this string is "MS-DOS".
+ */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Message Linking Configuration. You should #define ERR_LINK_MESSAGES
+ * in your osxxx.h header file if you want error messages linked into
+ * the application. Leave this symbol undefined if you want an external
+ * message file.
+ */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Program Exit Codes. These values are used for the argument to exit()
+ * to conform to local conventions. Define the following values in your
+ * OS-specific header:
+ *
+ * OSEXSUCC - successful completion. Usually defined to 0.
+ *. OSEXFAIL - failure. Usually defined to 1.
+ */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Basic memory management interface. These functions are merely
+ * documented here, but no prototypes are defined, because most
+ * platforms #define macros for these functions and types, mapping them
+ * to malloc or other system interfaces.
+ */
+
+/*
+ * Theoretical maximum osmalloc() size. This may be less than the
+ * capacity of the argument to osmalloc() on some systems. For example,
+ * on segmented architectures (such as 16-bit x86), memory is divided into
+ * segments, so a single memory allocation can allocate only a subset of
+ * the total addressable memory in the system. This value thus specifies
+ * the maximum amount of memory that can be allocated in one chunk.
+ *
+ * Note that this is an architectural maximum for the hardware and
+ * operating system. It doesn't have anything to do with the total amount
+ * of memory actually available at run-time.
+ *
+ * #define OSMALMAX to a constant long value with theoretical maximum
+ * osmalloc() argument value. For a platform with a flat (unsegmented)
+ * 32-bit memory space, this is usually 0xffffffff; for 16-bit platforms,
+ * this is usually 0xffff.
+ */
+/* #define OSMALMAX 0xffffffff */
+
+/*
+ * Allocate a block of memory of the given size in bytes. The actual
+ * allocation may be larger, but may be no smaller. The block returned
+ * should be worst-case aligned (i.e., suitably aligned for any type).
+ * Return null if the given amount of memory is not available.
+ */
+/* void *osmalloc(size_t siz); */
+
+/*
+ * Free memory previously allocated with osmalloc().
+ */
+/* void osfree(void *block); */
+
+/*
+ * Reallocate memory previously allocated with osmalloc() or
+ * osrealloc(), changing the block's size to the given number of bytes.
+ * If necessary, a new block at a different address can be allocated, in
+ * which case the data from the original block is copied (the lesser of
+ * the old block size and the new size is copied) to the new block, and
+ * the original block is freed. If the new size is less than the old
+ * size, this need not do anything at all, since the returned block can
+ * be larger than the new requested size. If the block cannot be
+ * enlarged to the requested size, return null.
+ */
+/* void *osrealloc(void *block, size_t siz); */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Basic file I/O interface. These functions are merely documented here,
+ * but no prototypes are defined, because most platforms #define macros for
+ * these functions and types, mapping them to stdio or other system I/O
+ * interfaces.
+ *
+ * When writing a file, writes might or might not be buffered in
+ * application memory; this is up to the OS implementation, which can
+ * perform buffering according to local conventions and what's most
+ * efficient. However, it shouldn't make any difference to the caller
+ * whether writes are buffered or not - the OS implementation must take
+ * care that any buffering is invisible to the app. (Porters: note that
+ * the basic C stdio package has the proper behavior here, so you'll get
+ * the correct semantics if you use a simple stdio implementation.)
+ *
+ * Write buffering might be visible to *other* apps, though. In
+ * particular, another process might not see data written to a file (with
+ * osfwb(), os_fprint(), etc) immediately, since the write functions might
+ * hold the written bytes in an internal memory buffer rather than sending
+ * them to the OS. Any internal buffers are guaranteed to be flushed to
+ * the OS upon calling osfcls() or osfflush(). Note that it's never
+ * *necessary* to call osfflush(), because buffered data will always be
+ * flushed on closing the file with osfcls(). However, if you want other
+ * apps to be able to see updates immediately, you can use osfflush() to
+ * ensure that buffers are flushed to a file before you close it.
+ *
+ * You can also use osfflush() to check for buffered write errors. When
+ * you use osfwb() or other write functions to write data, they will return
+ * a success indication even if the data was only copied into a buffer.
+ * This means that a write that appeared to succeed might actually fail
+ * later, when the buffer is flushed. The only way to know for sure is to
+ * explicitly flush buffers using osfflush(), and check the result code.
+ * If the original write function and a subsequent osfflush() *both* return
+ * success indications, then the write has definitely succeeded.
+ */
+
+
+/*
+ * Define the following values in your OS header to indicate local
+ * file/path syntax conventions:
+ *
+ * OSFNMAX - integer indicating maximum length of a filename
+ *
+ * OSPATHCHAR - character giving the normal path separator character
+ *. OSPATHALT - string giving other path separator characters
+ *. OSPATHURL - string giving path separator characters for URL conversions
+ *. OSPATHSEP - directory separator for PATH-style environment variables
+ *. OSPATHPWD - string giving the special path representing the current
+ *. working directory; for Unix or Windows, this is "."
+ *
+ * OSPATHURL is a little different: this specifies the characters that
+ * should be converted to URL-style separators when converting a path from
+ * local notation to URL notation. This is usually the same as the union
+ * of OSPATHCHAR and OSPATHALT, but need not be; for example, on DOS, the
+ * colon (':') is a path separator for most purposes, but is NOT a path
+ * character for URL conversions.
+ */
+
+/*
+ * Define the type osfildef as the appropriate file handle structure for
+ * your osfxxx functions. This type is always used as a pointer, but
+ * the value is always obtained from an osfopxxx call, and is never
+ * synthesized by portable code, so you can use essentially any type
+ * here that you want.
+ *
+ * For platforms that use C stdio functions to implement the osfxxx
+ * functions, osfildef can simply be defined as FILE.
+ */
+/* typedef FILE osfildef; */
+
+
+/*
+ * File types.
+ *
+ * These are symbols of the form OSFTxxxx defining various content types,
+ * somewhat aking to MIME types. These were mainly designed for the old
+ * Mac OS (versions up to OS 9), where the file system stored a type tag
+ * with each file's metadata. The type tags were used for things like
+ * filtering file selector dialogs and setting file-to-app associations in
+ * the desktop shell.
+ *
+ * Our OSFTxxx symbols are abstract file types that we define, for types
+ * used within the TADS family of applications. They give us a common,
+ * cross-platform reference point for each type we use. Each port where
+ * file types are meaningful then maps our abstract type IDs to the
+ * corresponding port-specific type IDs. In practice, this has never been
+ * used anywhere other than the old Mac OS ports; in fact, it's not even
+ * used in the modern Mac OS (OS X and later), since Apple decided to stop
+ * fighting the tide and start using filename suffixes for this sort of
+ * tagging, like everyone else always has.
+ *
+ * For the list of file types, see osifctyp.h
+ */
+
+
+/*
+ * Local newline convention.
+ *
+ * Because of the pernicious NIH ("Not Invented Here") cultures of the
+ * major technology vendors, basically every platform out there has its own
+ * unique way of expressing newlines in text files. Unix uses LF (ASCII
+ * 10); Mac uses CR (ASCII 13); DOS and Windows use CR-LF pairs. In the
+ * past there were heaven-only-knows how many other conventions in use, but
+ * fortunately these three have the market pretty well locked up at this
+ * point. But we do still have to worry about these three.
+ *
+ * Our strategy on input is to be open to just about anything whenever
+ * possible. So, when we're reading something that we believe to be a text
+ * file, we'll treat all of these as line endings: CR, LF, CR-LF, and
+ * LF-CR. It's pretty safe to do this; if we have a CR and LF occurring
+ * adjacently, it's almost certain that they're intended to be taken
+ * together as a single newline sequence. Likewise, if there's a lone CR
+ * or LF, it's rare for it to mean anything other than a newline.
+ *
+ * On output, though, we can't be as loose. The problem is that other
+ * applications on our big three platforms *don't* tend to aim for the same
+ * flexibility we do on input: other apps usually expect exactly the local
+ * conventions on input, and don't always work well if they don't get it.
+ * So it's important that when we're writing a text file, we write newlines
+ * in the local convention. This means that we sometimes need to know what
+ * the local convention actually is. That's where this definition comes
+ * in.
+ *
+ * Each port must define OS_NEWLINE_SEQ as an ASCII string giving the local
+ * newline sequence to write on output. For example, DOS defines it as
+ * "\r\n" (CR-LF). Always define it as a STRING (not a character
+ * constant), even if it's only one character long.
+ *
+ * (Note that some compilers use wacky mappings for \r and \n. Some older
+ * Mac compilers, for example, defined \n as CR and \r as LF, because of
+ * the Mac convention where newline is represented as CR in a text file.
+ * If there's any such variability on your platform, you can always use the
+ * octal codes to be unambiguous: \012 for LF and \015 for CR.)
+ */
+/* #define OS_NEWLINE_SEQ "\r\n" */
+
+
+
+/*
+ * Open text file for reading. This opens the file with read-only access;
+ * we're not allowed to write to the file using this handle. Returns NULL
+ * on error.
+ *
+ * A text file differs from a binary file in that some systems perform
+ * translations to map between C conventions and local file system
+ * conventions; for example, on DOS, the stdio library maps the DOS CR-LF
+ * newline convention to the C-style '\n' newline format. On many systems
+ * (Unix, for example), there is no distinction between text and binary
+ * files.
+ *
+ * On systems that support file sharing and locking, this should open the
+ * file in "shared read" mode - this means that other processes are allowed
+ * to simultaneously read from the file, but no other processs should be
+ * allowed to write to the file as long as we have it open. If another
+ * process already has the file open with write access, this routine should
+ * return failure, since we can't take away the write privileges the other
+ * process already has and thus we can't guarantee that other processes
+ * won't write to the file while we have it open.
+ */
+/* osfildef *osfoprt(const char *fname, os_filetype_t typ); */
+
+/*
+ * Open a text file for "volatile" reading: we open the file with read-only
+ * access, and we explicitly accept instability in the file's contents due
+ * to other processes simultaneously writing to the file. On systems that
+ * support file sharing and locking, the file should be opened in "deny
+ * none" mode, meaning that other processes can simultaneously open the
+ * file for reading and/or writing even while have the file open.
+ */
+/* osfildef *osfoprtv(const char *fname, os_filetype_t typ); */
+
+/*
+ * Open text file for writing; returns NULL on error. If the file already
+ * exists, this truncates the file to zero length, deleting any existing
+ * contents.
+ */
+/* 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 such
+ * file exists. Returns NULL on error.
+ */
+/* osfildef *osfoprwt(const char *fname, os_filetype_t typ); */
+
+/*
+ * Open text file for reading/writing. If the file already exists,
+ * truncate the existing contents to zero length. Create a new file if it
+ * doesn't already exist. Return null on error.
+ */
+/* osfildef *osfoprwtt(const char *fname, os_filetype_t typ); */
+
+/*
+ * Open binary file for writing; returns NULL on error. If the file
+ * exists, this truncates the existing contents to zero length.
+ */
+/* osfildef *osfopwb(const char *fname, os_filetype_t typ); */
+
+/*
+ * Open source file for reading - use the appropriate text or binary
+ * mode.
+ */
+/* osfildef *osfoprs(const char *fname, os_filetype_t typ); */
+
+/*
+ * Open binary file for reading; returns NULL on error.
+ */
+/* osfildef *osfoprb(const char *fname, os_filetype_t typ); */
+
+/*
+ * Open binary file for 'volatile' reading; returns NULL on error.
+ * ("Volatile" means that we'll accept writes from other processes while
+ * reading, so the file should be opened in "deny none" mode or the
+ * equivalent, to the extent that the local system supports file sharing
+ * modes.)
+ */
+/* osfildef *osfoprbv(const char *fname, os_filetype_t typ); */
+
+/*
+ * Open binary file for random-access reading/writing. If the file already
+ * exists, keep the existing contents; if the file doesn't already exist,
+ * create a new empty file.
+ *
+ * The caller is allowed to perform any mixture of read and write
+ * operations on the returned file handle, and can seek around in the file
+ * to read and write at random locations.
+ *
+ * If the local file system supports file sharing or locking controls, this
+ * should generally open the file in something equivalent to "exclusive
+ * write, shared read" mode ("deny write" in DENY terms), so that other
+ * processes can't modify the file at the same time we're modifying it (but
+ * it doesn't bother us to have other processes reading from the file while
+ * we're working on it, as long as they don't mind that we could change
+ * things on the fly). It's not absolutely necessary to assert these
+ * locking semantics, but if there's an option to do so this is preferred.
+ * Stricter semantics (such as "exclusive" or "deny all" mode) are better
+ * than less strict semantics. Less strict semantics are dicey, because in
+ * that case the caller has no way of knowing that another process could be
+ * modifying the file at the same time, and no way (through osifc) of
+ * coordinating that activity. If less strict semantics are implemented,
+ * the caller will basically be relying on luck to avoid corruptions due to
+ * writing by other processes.
+ *
+ * Return null on error.
+ */
+/* osfildef *osfoprwb(const char *fname, os_filetype_t typ); */
+
+/*
+ * Open binary file for random-access reading/writing. If the file already
+ * exists, truncate the existing contents (i.e., delete the contents of the
+ * file, resetting it to a zero-length file). Create a new file if it
+ * doesn't already exist. The caller is allowed to perform any mixture of
+ * read and write operations on the returned handle, and can seek around in
+ * the file to read and write at random locations.
+ *
+ * The same comments regarding sharing/locking modes for osfoprwb() apply
+ * here as well.
+ *
+ * Return null on error.
+ */
+/* osfildef *osfoprwtb(const char *fname, os_filetype_t typ); */
+
+/*
+ * Duplicate a file handle. Returns a new osfildef* handle that accesses
+ * the same open file as an existing osfildef* handle. The new handle is
+ * independent of the original handle, with its own seek position,
+ * buffering, etc. The new handle and the original handle must each be
+ * closed separately when the caller is done with them (closing one doesn't
+ * close the other). The effect should be roughly the same as the Unix
+ * dup() function.
+ *
+ * On success, returns a new, non-null osfildef* handle duplicating the
+ * original handle. Returns null on failure.
+ *
+ * 'mode' is a simplified stdio fopen() mode string. The first
+ * character(s) indicate the access type: "r" for read access, "w" for
+ * write access, or "r+" for read/write access. Note that "w+" mode is
+ * specifically not defined, since the fopen() handling of "w+" is to
+ * truncate any existing file contents, which is not desirable when
+ * duplicating a handle. The access type can optionally be followed by "t"
+ * for text mode, "s" for source file mode, or "b" for binary mode, with
+ * the same meanings as for the various osfop*() functions. The default is
+ * 't' for text mode if none of these are specified.
+ *
+ * If the osfop*() functions are implemented in terms of stdio FILE*
+ * objects, this can be implemented as fdopen(dup(fileno(orig)), mode), or
+ * using equivalents if the local stdio library uses different names for
+ * these functions. Note that "s" (source file format) isn't a stdio mode,
+ * so implementations must translate it to the appropriate "t" or "b" mode.
+ * (For that matter, "t" and "b" modes aren't universally supported either,
+ * so some implementations may have to translate these, or more likely
+ * simply remove them, as most platforms don't distinguish text and binary
+ * modes anyway.)
+ */
+osfildef *osfdup(osfildef *orig, const char *mode);
+
+/*
+ * Set a file's type information. This is primarily for implementations on
+ * Mac OS 9 and earlier, where the file system keeps file-type metadata
+ * separate from the filename. On such systems, this can be used to set
+ * the type metadata after a file is created. The system should map the
+ * os_filetype_t values to the actual metadata values on the local system.
+ * On most systems, there's no such thing as file-type metadata, in which
+ * case this function should simply be stubbed out with an empty function.
+ */
+void os_settype(const char *f, os_filetype_t typ);
+
+/* open the error message file for reading */
+osfildef *oserrop(const char *arg0);
+
+/*
+ * Get a line of text from a text file. Uses fgets semantics.
+ */
+/* char *osfgets(char *buf, size_t len, osfildef *fp); */
+
+/*
+ * Write a line of text to a text file. Uses fputs semantics.
+ */
+/* void osfputs(const char *buf, osfildef *fp); */
+
+/*
+ * Write to a text file. os_fprintz() takes a null-terminated string,
+ * while os_fprint() takes an explicit separate length argument that might
+ * not end with a null terminator.
+ */
+void os_fprintz(osfildef *fp, const char *str);
+void os_fprint(osfildef *fp, const char *str, size_t len);
+
+/*
+ * Write bytes to file. Return 0 on success, non-zero on error.
+ */
+/* int osfwb(osfildef *fp, const void *buf, int bufl); */
+
+/*
+ * Flush buffered writes to a file. This ensures that any bytes written to
+ * the file (with osfwb(), os_fprint(), etc) are actually sent out to the
+ * operating system, rather than being buffered in application memory for
+ * later writing.
+ *
+ * Note that this routine only guarantees that we write through to the
+ * operating system. This does *not* guarantee that the data will actually
+ * be committed to the underlying physical storage device. Such a
+ * guarantee is hard to come by in general, since most modern systems use
+ * multiple levels of software and hardware buffering - the OS might buffer
+ * some data in system memory, and the physical disk drive might itself
+ * buffer data in its own internal cache. This routine thus isn't good
+ * enough, for example, to protect transactional data that needs to survive
+ * a power failure or a serious system crash. What this routine *does*
+ * ensure is that buffered data are written through to the OS; in
+ * particular, this ensures that another process that's reading from the
+ * same file will see all updates we've made up to this point.
+ *
+ * Returns 0 on success, non-zero on error. Errors can occur for any
+ * reason that they'd occur on an ordinary write - a full disk, a hardware
+ * failure, etc.
+ */
+/* int osfflush(osfildef *fp); */
+
+/*
+ * Read a character from a file. Provides the same semantics as fgetc().
+ */
+/* int osfgetc(osfildef *fp); */
+
+/*
+ * Read bytes from file. Return 0 on success, non-zero on error.
+ */
+/* int osfrb(osfildef *fp, void *buf, int bufl); */
+
+/*
+ * Read bytes from file and return the number of bytes read. 0
+ * indicates that no bytes could be read.
+ */
+/* size_t osfrbc(osfildef *fp, void *buf, size_t bufl); */
+
+/*
+ * Get the current seek location in the file. The first byte of the
+ * file has seek position 0.
+ */
+/* long osfpos(osfildef *fp); */
+
+/*
+ * Seek to a location in the file. The first byte of the file has seek
+ * position 0. Returns zero on success, non-zero on error.
+ *
+ * The following constants must be defined in your OS-specific header;
+ * these values are used for the "mode" parameter to indicate where to
+ * seek in the file:
+ *
+ * OSFSK_SET - set position relative to the start of the file
+ *. OSFSK_CUR - set position relative to the current file position
+ *. OSFSK_END - set position relative to the end of the file
+ */
+/* int osfseek(osfildef *fp, long pos, int mode); */
+
+/*
+ * Close a file.
+ *
+ * If the OS implementation uses buffered writes, this routine guarantees
+ * that any buffered data are flushed to the underlying file. So, it's not
+ * necessary to call osfflush() before calling this routine. However,
+ * since this function doesn't return any error indication, a caller could
+ * use osfflush() first to check for errors on any final buffered writes.
+ */
+/* void osfcls(osfildef *fp); */
+
+/*
+ * Delete a file. Returns zero on success, non-zero on error.
+ */
+/* int osfdel(const char *fname); */
+
+/*
+ * Rename/move a file. This should apply the usual C rename() behavior.
+ * Renames the old file to the new name, which may be in a new directory
+ * location if supported on the local system; moves across devices,
+ * volumes, file systems, etc may or may not be supported according to the
+ * local system's rules. If the new file already exists, results are
+ * undefined. Returns true on success, false on failure.
+ */
+/* int os_rename_file(const char *oldname, const char *newname); */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * File "stat()" information - mode, size, time stamps
+ */
+
+/*
+ * Test access to a file - i.e., determine if the file exists. Returns
+ * zero if the file exists, non-zero if not. (The semantics may seem
+ * backwards, but this is consistent with the conventions used by most of
+ * the other osfxxx calls: zero indicates success, non-zero indicates an
+ * error. If the file exists, "accessing" it was successful, so osfacc
+ * returns zero; if the file doesn't exist, accessing it gets an error,
+ * hence a non-zero return code.)
+ */
+/* int osfacc(const char *fname) */
+
+/*
+ * Get a file's mode and attribute flags. This retrieves information on
+ * the given file equivalent to the st_mode member of the 'struct stat'
+ * data returned by the Unix stat() family of functions, as well as some
+ * extra system-specific attributes. On success, fills in *mode (if mode
+ * is non-null) with the mode information as a bitwise combination of
+ * OSFMODE_xxx values, fills in *attr (if attr is non-null) with a
+ * combination of OSFATTR_xxx attribute flags, and returns true; on
+ * failure, simply returns false. Failure can occur if the file doesn't
+ * exist, can't be accessed due to permissions, etc.
+ *
+ * Note that 'mode' and/or 'attr' can be null if the caller doesn't need
+ * that information. Implementations must check these parameters for null
+ * pointers and skip returning the corresponding information if null.
+ *
+ * If the file in 'fname' is a symbolic link, the behavior depends upon
+ * 'follow_links'. If 'follow_links' is true, the function should resolve
+ * the link reference (and if that points to another link, the function
+ * resolves that link as well, and so on) and return information on the
+ * object the link points to. Otherwise, the function returns information
+ * on the link itself. This only applies for symbolic links (not for hard
+ * links), and only if the underlying OS and file system support this
+ * distinction; if the OS transparently resolves links and doesn't allow
+ * retrieving information about the link itself, 'follow_links' can be
+ * ignored. Likewise, hard links (on systems that support them) are
+ * generally indistinguishable from regular files, so this function isn't
+ * expected to do anything special with them.
+ *
+ * The '*mode' value returned is a bitwise combination of OSFMODE_xxx flag.
+ * Many of the flags are mutually exclusive; for example, "file" and
+ * "directory" should never be combined. It's also possible for '*mode' to
+ * be zero for a valid file; this means that the file is of some special
+ * type on the local system that doesn't fit any of the OSFMODE_xxx types.
+ * (If any ports do encounter such cases, we can add OSFMODE_xxx types to
+ * accommodate new types. The list below isn't meant to be final; it's
+ * just what we've encountered so far on the platforms where TADS has
+ * already been ported.)
+ *
+ * The OSFMODE_xxx values are left for the OS to define so that they can be
+ * mapped directly to the OS API's equivalent constants, if desired. This
+ * makes the routine easy to write, since you can simply set *mode directly
+ * to the mode information the OS returns from its stat() or equivalent.
+ * However, note that these MUST be defined as bit flags - that is, each
+ * value must be exactly a power of 2. Windows and Unix-like systems
+ * follow this practice, as do most "stat()" functions in C run-time
+ * libraries, so this usually works automatically if you map these
+ * constants to OS or C library values. However, if a port defines its own
+ * values for these, take care that they're all powers of 2.
+ *
+ * Obviously, a given OS might not have all of the file types listed here.
+ * If any OSFMODE_xxx values aren't applicable on the local OS, you can
+ * simply define them as zero since they'll never be returned.
+ *
+ * Notes on attribute flags:
+ *
+ * OSFATTR_HIDDEN means that the file is conventionally hidden by default
+ * in user interface views or listings, but is still fully accessible to
+ * the user. Hidden files are also usually excluded by default from
+ * wildcard patterns in commands ("rm *.*"). On Unix, a hidden file is one
+ * whose name starts with "."; on Windows, it's a file with the HIDDEN bit
+ * in its file attributes. On systems where this concept exists, the user
+ * can still manipulate these files as normal by naming them explicitly,
+ * and can typically make them appear in UI views or directory listings via
+ * a preference setting or command flag (e.g., "ls -a" on Unix). The
+ * "hidden" flag is explicitly NOT a security or permissions mechanism, and
+ * it doesn't protect the file against intentional access by a user; it's
+ * merely a convenience designed to reduce clutter by excluding files
+ * maintained by the OS or by an application (such as preference files,
+ * indices, caches, etc) from casual folder browsing, where a user is
+ * typically only concerned with her own document files. On systems where
+ * there's no such naming convention or attribute metadata, this flag will
+ * never appear.
+ *
+ * OSFATTR_SYSTEM is similar to 'hidden', but means that the file is
+ * specially marked as an operating system file. This is mostly a
+ * DOS/Windows concept, where it corresponds to the SYSTEM bit in the file
+ * attributes; this flag will probably never appear on other systems. The
+ * distinction between 'system' and 'hidden' is somewhat murky even on
+ * Windows; most 'system' file are also marked as 'hidden', and in
+ * practical terms in the user interface, 'system' files are treated the
+ * same as 'hidden'.
+ *
+ * OSFATTR_READ means that the file is readable by this process.
+ *
+ * OSFATTR_WRITE means that the file is writable by this process.
+ */
+/* int osfmode(const char *fname, int follow_links, */
+/* unsigned long *mode, unsigned long *attr); */
+
+/* file mode/type constants */
+/* #define OSFMODE_FILE - regular file */
+/* #define OSFMODE_DIR - directory */
+/* #define OSFMODE_BLK - block-mode device */
+/* #define OSFMODE_CHAR - character-mode device */
+/* #define OSFMODE_PIPE - pipe/FIFO/other character-oriented IPC */
+/* #define OSFMODE_SOCKET - network socket */
+/* #define OSFMODE_LINK - symbolic link */
+
+/* file attribute constants */
+/* #define OSFATTR_HIDDEN - hidden file */
+/* #define OSFATTR_SYSTEM - system file */
+/* #define OSFATTR_READ - the file is readable by this process */
+/* #define OSFATTR_WRITE - the file is writable by this process */
+
+struct os_file_stat_t {
+ /*
+ * Size of the file, in bytes. For platforms lacking 64-bit types, we
+ * split this into high and low 32-bit portions. Platforms where the
+ * native stat() or equivalent only returns a 32-bit file size can
+ * simply set sizehi to zero, since sizelo can hold the entire size
+ * value.
+ */
+ uint32_t sizelo;
+ uint32_t sizehi;
+
+ /*
+ * Creation time, modification time, and last access time. If the file
+ * system doesn't keep information on one or more of these, use
+ * (os_time_t)0 to indicate that the timestamp isn't available. It's
+ * fine to return any subset of these. Per the standard C stat(),
+ * these should be expressed as seconds after the Unix Epoch.
+ */
+ os_time_t cre_time;
+ os_time_t mod_time;
+ os_time_t acc_time;
+
+ /* file mode, using the same flags as returned from osfmode() */
+ unsigned long mode;
+
+ /* file attributes, using the same flags as returned from osfmode() */
+ unsigned long attrs;
+};
+
+
+/*
+ * Get stat() information. This fills in the portable os_file_stat
+ * structure with the requested file information. Returns true on success,
+ * false on failure (file not found, permissions error, etc).
+ *
+ * 'follow_links' has the same meaning as for osfmode().
+ */
+int os_file_stat(const char *fname, int follow_links, os_file_stat_t *s);
+
+/*
+ * Manually resolve a symbolic link. If the local OS and file system
+ * support symbolic links, and the given filename is a symbolic link (in
+ * which case osfmode(fname, FALSE, &m, &a) will set OSFMODE_LINK in the
+ * mode bits), this fills in 'target' with the name of the link target
+ * (i.e., the object that the link in 'fname' points to). This should
+ * return a fully qualified file system path. Returns true on success,
+ * false on failure.
+ *
+ * This should only resolve a single level of indirection. If the link
+ * target of 'fname' is itself a link to a second target, this should only
+ * resolve the single reference from 'fname' to its direct direct. Callers
+ * that wish to resolve the final target of a chain of link references must
+ * iterate until the returned path doesn't refer to a link.
+ */
+int os_resolve_symlink(const char *fname, char *target, size_t target_size);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Get a list of root directories. If 'buf' is non-null, fills in 'buf'
+ * with a list of strings giving the root directories for the local,
+ * file-oriented devices on the system. The strings are each null
+ * terminated and are arranged consecutively in the buffer, with an extra
+ * null terminator after the last string to mark the end of the list.
+ *
+ * The return value is the length of the buffer required to hold the
+ * results. If the caller's buffer is null or is too short, the routine
+ * should return the full length required, and leaves the contents of the
+ * buffer undefined; the caller shouldn't expect any contents to be filled
+ * in if the return value is greater than buflen. Both 'buflen' and the
+ * return value include the null terminators, including the extra null
+ * terminator at the end of the list. If an error occurs, or the system
+ * has no concept of a root directory, returns zero.
+ *
+ * Each result string should be expressed using the syntax for the root
+ * directory on a device. For example, on Windows, "C:\" represents the
+ * root directory on the C: drive.
+ *
+ * "Local" means a device is mounted locally, as opposed to being merely
+ * visible on the network via some remote node syntax; e.g., on Windows
+ * this wouldn't include any UNC-style \\SERVER\SHARE names, and on VMS it
+ * excludes any SERVER:: nodes. It's up to each system how to treat
+ * virtual local devices, i.e., those that look synctactically like local
+ * devices but are actually mounted network devices, such as Windows mapped
+ * network drives; we recommend including them if it would take extra work
+ * to filter them out, and excluding them if it would take extra work to
+ * include them. "File-oriented" means that the returned devices are
+ * accessed via file systems, not as character devices or raw block
+ * devices; so this would exclude /dev/xxx devices on Unix and things like
+ * CON: and LPT1: on Windows.
+ *
+ * Examples ("." represents a null byte):
+ *
+ * Windows: C:\.D:\.E:\..
+ *
+ * Unix example: /..
+ */
+size_t os_get_root_dirs(char *buf, size_t buflen);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Open a directory. This begins an enumeration of a directory's contents.
+ * 'dirname' is a relative or absolute path to a directory. On success,
+ * returns true, and 'handle' is set to a port-defined handle value that's
+ * used in subsequent calls to os_read_dir() and os_close_dir(). Returns
+ * false on failure.
+ *
+ * If the routine succeeds, the caller must eventually call os_close_dir()
+ * to release the resources associated with the handle.
+ */
+/* typedef <local system type> osdirhdl_t; */
+int os_open_dir(const char *dirname, /*OUT*/osdirhdl_t *handle);
+
+/*
+ * Read the next file in a directory. 'handle' is a handle value obtained
+ * from a call to os_open_dir(). On success, returns true and fills in
+ * 'fname' with the next filename; the handle is also internally updated so
+ * that the next call to this function will retrieve the next file, and so
+ * on until all files have been retrieved. If an error occurs, or there
+ * are no more files in the directory, returns false.
+ *
+ * The filename returned is the root filename only, without the path. The
+ * caller can build the full path by calling os_build_full_path() or
+ * os_combine_paths() with the original directory name and the returned
+ * filename as parameters.
+ *
+ * This routine lists all objects in the directory that are visible to the
+ * corresponding native API, and is non-recursive. The listing should thus
+ * include subdirectory objects, but not the contents of subdirectories.
+ * Implementations are encouraged to simply return all objects returned
+ * from the corresponding native directory scan API; there's no need to do
+ * any filtering, except perhaps in cases where it's difficult or
+ * impossible to represent an object in terms of the osifc APIs (e.g., it
+ * might be reasonable to exclude files without names). System relative
+ * links, such as the Unix/DOS "." and "..", specifically should be
+ * included in the listing. For unusual objects that don't fit into the
+ * os_file_stat() taxonomy or that otherwise might create confusion for a
+ * caller, err on the side of full disclosure (i.e., just return everything
+ * unfiltered); if necessary, we can extend the os_file_stat() taxonomy or
+ * add new osifc APIs to create a portable abstraction to handle whatever
+ * is unusual or potentially confusing about the native object. For
+ * example, Unix implementations should feel free to return symbolic link
+ * objects, including dangling links, since we have the portable
+ * os_resolve_symlink() that lets the caller examine the meaning of the
+ * link object.
+ */
+int os_read_dir(osdirhdl_t handle, char *fname, size_t fname_size);
+
+/*
+ * Close a directory handle. This releases the resources associated with a
+ * directory search started with os_open_dir(). Every successful call to
+ * os_open_dir() must have a matching call to os_close_dir(). As usual for
+ * open/close protocols, the handle is invalid after calling this function,
+ * so no more calls to os_read_dir() may be made with the handle.
+ */
+void os_close_dir(osdirhdl_t handle);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * NB - this routine is DEPRECATED as of TADS 2.5.16/3.1.1. Callers should
+ * use os_open_dir(), os_read_dir(), os_close_dir() instead.
+ *
+ * Find the first file matching a given pattern. The returned context
+ * pointer is a pointer to whatever system-dependent context structure is
+ * needed to continue the search with the next file, and is opaque to the
+ * caller. The caller must pass the context pointer to the next-file
+ * routine. The caller can optionally cancel a search by calling the
+ * close-search routine with the context pointer. If the return value is
+ * null, it indicates that no matching files were found. If a file was
+ * found, outbuf will be filled in with its name, and isdir will be set to
+ * true if the match is a directory, false if it's a file. If pattern is
+ * null, all files in the given directory should be returned; otherwise,
+ * pattern is a string containing '*' and '?' as wildcard characters, but
+ * not containing any directory separators, and all files in the given
+ * directory matching the pattern should be returned.
+ *
+ * Important: because this routine may allocate memory for the returned
+ * context structure, the caller must either call os_find_next_file until
+ * that routine returns null, or call os_find_close() to cancel the search,
+ * to ensure that the os code has a chance to release the allocated memory.
+ *
+ * 'outbuf' should be set on output to the name of the matching file,
+ * without any path information.
+ *
+ * 'outpathbuf' should be set on output to full path of the matching file.
+ * If possible, 'outpathbuf' should use the same relative or absolute
+ * notation that the search criteria used on input. For example, if dir =
+ * "resfiles", and the file found is "MyPic.jpg", outpathbuf should be set
+ * to "resfiles/MyPic.jpg" (or appropriate syntax for the local platform).
+ * Similarly, if dir = "/home/tads/resfiles", outpath buf should be
+ * "/home/tads/resfiles/MyPic.jpg". The result should always conform to
+ * correct local conventions, which may require some amount of manipulation
+ * of the filename; for example, on the Mac, if dir = "resfiles", the
+ * result should be ":resfiles:MyPic.jpg" (note the added leading colon) to
+ * conform to Macintosh relative path notation.
+ *
+ * Note that 'outpathbuf' may be null, in which case the caller is not
+ * interested in the full path information.
+ */
+/*
+ * Note the following possible ways this function may be called:
+ *
+ * dir = "", pattern = filename - in this case, pattern is the name of a
+ * file or directory in the current directory. filename *might* be a
+ * relative path specified by the user (on a command line, for example);
+ * for instance, on Unix, it could be something like "resfiles/jpegs".
+ *
+ * dir = path, pattern = filname - same as above, but this time the
+ * filename or directory pattern is relative to the given path, rather
+ * than to the current directory. For example, we could have dir =
+ * "/games/mygame" and pattern = "resfiles/jpegs".
+ *
+ * dir = path, pattern = 0 (NULL) - this should search for all files in
+ * the given path. The path might be absolute or it might be relative.
+ *
+ * dir = path, pattern = "*" - this should have the same result as when
+ * pattern = 0.
+ *
+ * dir = path, pattern = "*.ext" - this should search for all files in
+ * the given path whose names end with ".ext".
+ *
+ * dir = path, pattern = "abc*" - this should search for all files in
+ * the given path whose names start with "abc".
+ *
+ * All of these combinations are possible because callers, for
+ * portability, must generally not manipulate filenames directly;
+ * instead, callers obtain paths and search strings from external
+ * sources, such as from the user, and present them to this routine with
+ * minimal manipulation.
+ */
+void *os_find_first_file(const char *dir,
+ char *outbuf, size_t outbufsiz, int *isdir,
+ char *outpathbuf, size_t outpathbufsiz);
+
+/*
+ * Implementation notes for porting os_find_first_file:
+ *
+ * The algorithm for this routine should go something like this:
+ *
+ * - If 'path' is null, create a variable real_path and initialize it
+ * with the current directory. Otherwise, copy path to real_path.
+ *
+ * - If 'pattern' contains any directory separators ("/" on Unix, for
+ * example), change real_path so that it reflects the additional leading
+ * subdirectories in the path in 'pattern', and remove the leading path
+ * information from 'pattern'. For example, on Unix, if real_path
+ * starts out as "./subdir", and pattern is "resfiles/jpegs", change
+ * real_path to "./subdir/resfiles", and change pattern to "jpegs".
+ * Take care to add and remove path separators as needed to keep the
+ * path strings well-formed.
+ *
+ * - Begin a search using appropriate OS API's for all files in
+ * real_path.
+ *
+ * - Check each file found. Skip any files that don't match 'pattern',
+ * treating "*" as a wildcard that matches any string of zero or more
+ * characters, and "?" as a wildcard that matches any single character
+ * (or matches nothing at the end of a string). For example:
+ *
+ *. "*" matches anything
+ *. "abc?" matches "abc", "abcd", "abce", "abcf", but not "abcde"
+ *. "abc???" matches "abc", "abcd", "abcde", "abcdef", but not "abcdefg"
+ *. "?xyz" matches "wxyz", "axyz", but not "xyz" or "abcxyz"
+ *
+ * - Return the first file that matches, if any, by filling in 'outbuf'
+ * and 'isdir' with appropriate information. Before returning, allocate
+ * a context structure (which is entirely for your own use, and opaque
+ * to the caller) and fill it in with the information necessary for
+ * os_find_next_file to get the next matching file. If no file matches,
+ * return null.
+ */
+
+
+/*
+ * Find the next matching file, continuing a search started with
+ * os_find_first_file(). Returns null if no more files were found, in
+ * which case the search will have been automatically closed (i.e.,
+ * there's no need to call os_find_close() after this routine returns
+ * null). Returns a non-null context pointer, which is to be passed to
+ * this function again to get the next file, if a file was found.
+ *
+ * 'outbuf' and 'outpathbuf' are filled in with the filename (without
+ * path) and full path (relative or absolute, as appropriate),
+ * respectively, in the same manner as they do for os_find_first_file().
+ *
+ * Implementation note: if os_find_first_file() allocated memory for the
+ * search context, this routine must free the memory if it returs null,
+ * because this indicates that the search is finished and the caller
+ * need not call os_find_close().
+ */
+void *os_find_next_file(void *ctx, char *outbuf, size_t outbufsiz,
+ int *isdir, char *outpathbuf, size_t outpathbufsiz);
+
+/*
+ * Cancel a search. The context pointer returned by the last call to
+ * os_find_first_file() or os_find_next_file() is the parameter. There
+ * is no need to call this function if find-first or find-next returned
+ * null, since they will have automatically closed the search.
+ *
+ * Implementation note: if os_find_first_file() allocated memory for the
+ * search context, this routine should release the memory.
+ */
+void os_find_close(void *ctx);
+
+/*
+ * Special filename classification
+ */
+enum os_specfile_t
+{
+ /* not a special file */
+ OS_SPECFILE_NONE,
+
+ /*
+ * current directory link - this is a file like the "." file on Unix
+ * or DOS, which is a special link that simply refers to itself
+ */
+ OS_SPECFILE_SELF,
+
+ /*
+ * parent directory link - this is a file like the ".." file on Unix
+ * or DOS, which is a special link that refers to the parent
+ * directory
+ */
+ OS_SPECFILE_PARENT
+};
+
+/*
+ * Determine if the given filename refers to a special file. Returns the
+ * appropriate enum value if so, or OS_SPECFILE_NONE if not. The given
+ * filename must be a root name - it must not contain a path prefix. The
+ * purpose here is to classify the results from os_find_first_file() and
+ * os_find_next_file() to identify the special relative links, so callers
+ * can avoid infinite recursion when traversing a directory tree.
+ */
+enum os_specfile_t os_is_special_file(const char *fname);
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Convert string to all-lowercase.
+ */
+char *os_strlwr(char *s);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Character classifications for quote characters. os_squote() returns
+ * true if its argument is any type of single-quote character;
+ * os_dquote() returns true if its argument is any type of double-quote
+ * character; and os_qmatch(a, b) returns true if a and b are matching
+ * open- and close-quote characters.
+ *
+ * These functions allow systems with extended character codes with
+ * weird quote characters (such as the Mac) to match the weird
+ * characters, so that users can use the extended quotes in input.
+ *
+ * These are usually implemented as macros. The most common
+ * implementation simply returns true for the standard ASCII quote
+ * characters:
+ *
+ * #define os_squote(c) ((c) == '\'')
+ *. #define os_dquote(c) ((c) == '"')
+ *. #define os_qmatch(a, b) ((a) == (b))
+ *
+ * These functions take int arguments to allow for the possibility of
+ * Unicode input.
+ */
+/* int os_squote(int c); */
+/* int os_dquote(int c); */
+/* int os_qmatch(int a, int b); */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Special file and directory locations
+ */
+
+/*
+ * Get the full filename (including directory path) to the executable
+ * file, given the argv[0] parameter passed into the main program. This
+ * fills in the buffer with a null-terminated string that can be used in
+ * osfoprb(), for example, to open the executable file.
+ *
+ * Returns non-zero on success. If it's not possible to determine the
+ * name of the executable file, returns zero.
+ *
+ * Some operating systems might not provide access to the executable file
+ * information, so non-trivial implementation of this routine is optional;
+ * if the necessary information is not available, simply implement this to
+ * return zero. If the information is not available, callers should offer
+ * gracefully degraded functionality if possible.
+ */
+int os_get_exe_filename(char *buf, size_t buflen, const char *argv0);
+
+/*
+ * Get a special directory path. Returns the selected path, in a format
+ * suitable for use with os_build_full_path(). The main program's argv[0]
+ * parameter is provided so that the system code can choose to make the
+ * special paths relative to the program install directory, but this is
+ * entirely up to the system implementation, so the argv[0] parameter can
+ * be ignored if it is not needed.
+ *
+ * The 'id' parameter selects which special path is requested; this is one
+ * of the constants defined below. If the id is not understood, there is
+ * no way of signalling an error to the caller; this routine can fail with
+ * an assert() in such cases, because it indicates that the OS layer code
+ * is out of date with respect to the calling code.
+ *
+ * This routine can be implemented using one of the strategies below, or a
+ * combination of these. These are merely suggestions, though, and systems
+ * are free to ignore these and implement this routine using whatever
+ * scheme is the best fit to local conventions.
+ *
+ * - Relative to argv[0]. Some systems use this approach because it keeps
+ * all of the TADS files together in a single install directory tree, and
+ * doesn't require any extra configuration information to find the install
+ * directory. Since we base the path name on the executable that's
+ * actually running, we don't need any environment variables or parameter
+ * files or registry entries to know where to look for related files.
+ *
+ * - Environment variables or local equivalent. On some systems, it is
+ * conventional to set some form of global system parameter (environment
+ * variables on Unix, for example) for this sort of install configuration
+ * data. In these cases, this routine can look up the appropriate
+ * configuration variables in the system environment.
+ *
+ * - Hard-coded paths. Some systems have universal conventions for the
+ * installation configuration of compiler-like tools, so the paths to our
+ * component files can be hard-coded based on these conventions.
+ *
+ * - Hard-coded default paths with environment variable overrides. Let the
+ * user set environment variables if they want, but use the standard system
+ * paths as hard-coded defaults if the variables aren't set. This is often
+ * the best choice; users who expect the standard system conventions won't
+ * have to fuss with any manual settings or even be aware of them, while
+ * users who need custom settings aren't stuck with the defaults.
+ */
+void os_get_special_path(char *buf, size_t buflen,
+ const char *argv0, int id);
+
+/*
+ * TADS 3 system resource path. This path is used to load system
+ * resources, such as character mapping files and error message files.
+ */
+#define OS_GSP_T3_RES 1
+
+/*
+ * TADS 3 compiler - system headers. This is the #include path for the
+ * header files included with the compiler.
+ */
+#define OS_GSP_T3_INC 2
+
+/*
+ * TADS 3 compiler - system library source code. This is the path to the
+ * library source files that the compiler includes in every compilation by
+ * default (such as _main.t).
+ */
+#define OS_GSP_T3_LIB 3
+
+/*
+ * TADS 3 compiler - user library path list. This is a list of directory
+ * paths, separated by the OSPATHSEP character, that should be searched for
+ * user library files. The TADS 3 compiler uses this as an additional set
+ * of locations to search after the list of "-Fs" options and before the
+ * OS_GSP_T3_LIB directory.
+ *
+ * This path list is intended for the user's use, so no default value is
+ * needed. The value should be user-configurable using local conventions;
+ * on Unix, for example, this might be handled with an environment
+ * variable.
+ */
+#define OS_GSP_T3_USER_LIBS 4
+
+/*
+ * TADS 3 interpreter - application data path. This is the directory where
+ * we should store things like option settings: data that we want to store
+ * in a file, global to all games. Depending on local system conventions,
+ * this can be a global shared directory for all users, or can be a
+ * user-specific directory.
+ */
+#define OS_GSP_T3_APP_DATA 5
+
+/*
+ * TADS 3 interpreter - system configuration files. This is used for files
+ * that affect all games, and generally all users on the system, so it
+ * should be in a central location. On Windows, for example, we simply
+ * store these files in the install directory containing the intepreter
+ * binary.
+ */
+#define OS_GSP_T3_SYSCONFIG 6
+
+/*
+ * System log files. This is the directory for system-level status, debug,
+ * and error logging files. (Note that we're NOT talking about in-game
+ * transcript logging per the SCRIPT command. SCRIPT logs are usually sent
+ * to files selected by the user via a save-file dialog, so these don't
+ * need a special location.)
+ */
+#define OS_GSP_LOGFILE 7
+
+
+/*
+ * Seek to the resource file embedded in the current executable file,
+ * given the main program's argv[0].
+ *
+ * On platforms where the executable file format allows additional
+ * information to be attached to an executable, this function can be used
+ * to find the extra information within the executable.
+ *
+ * The 'typ' argument gives a resource type to find. This is an arbitrary
+ * string that the caller uses to identify what type of object to find.
+ * The "TGAM" type, for example, is used by convention to indicate a TADS
+ * compiled GAM file.
+ */
+osfildef *os_exeseek(const char *argv0, const char *typ);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Load a string resource. Given a string ID number, load the string
+ * into the given buffer.
+ *
+ * Returns zero on success, non-zero if an error occurs (for example,
+ * the buffer is too small, or the requested resource isn't present).
+ *
+ * Whenever possible, implementations should use an operating system
+ * mechanism for loading the string from a user-modifiable resource
+ * store; this will make localization of these strings easier, since the
+ * resource store can be modified without the need to recompile the
+ * application. For example, on the Macintosh, the normal system string
+ * resource mechanism should be used to load the string from the
+ * application's resource fork.
+ *
+ * When no operating system mechanism exists, the resources can be
+ * stored as an array of strings in a static variable; this isn't ideal,
+ * because it makes it much more difficult to localize the application.
+ *
+ * Resource ID's are application-defined. For example, for TADS 2,
+ * "res.h" defines the resource ID's.
+ */
+int os_get_str_rsc(int id, char *buf, size_t buflen);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Look for a file in the "standard locations": current directory, program
+ * directory, PATH-like environment variables, etc. The actual standard
+ * locations are specific to each platform; the implementation is free to
+ * use whatever conventions are appropriate to the local system. On
+ * systems that have something like Unix environment variables, it might be
+ * desirable to define a TADS-specific variable (TADSPATH, for example)
+ * that provides a list of directories to search for TADS-related files.
+ *
+ * On return, fill in 'buf' with the full filename of the located copy of
+ * the file (if a copy was indeed found), in a format suitable for use with
+ * the osfopxxx() functions; in other words, after this function returns,
+ * the caller should be able to pass the contents of 'buf' to an osfopxxx()
+ * function to open the located file.
+ *
+ * Returns true (non-zero) if a copy of the file was located, false (zero)
+ * if the file could not be found in any of the standard locations.
+ */
+int os_locate(const char *fname, int flen, const char *arg0,
+ char *buf, size_t bufsiz);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Create and open a temporary file. The file must be opened to allow
+ * both reading and writing, and must be in "binary" mode rather than
+ * "text" mode, if the system makes such a distinction. Returns null on
+ * failure.
+ *
+ * If 'fname' is non-null, then this routine should create and open a file
+ * with the given name. When 'fname' is non-null, this routine does NOT
+ * need to store anything in 'buf'. Note that the routine shouldn't try
+ * to put the file in a special directory or anything like that; just open
+ * the file with the name exactly as given.
+ *
+ * If 'fname' is null, this routine must choose a file name and fill in
+ * 'buf' with the chosen name; if possible, the file should be in the
+ * conventional location for temporary files on this system, and should be
+ * unique (i.e., it shouldn't be the same as any existing file). The
+ * filename stored in 'buf' is opaque to the caller, and cannot be used by
+ * the caller except to pass to osfdel_temp(). On some systems, it may
+ * not be possible to determine the actual filename of a temporary file;
+ * in such cases, the implementation may simply store an empty string in
+ * the buffer. (The only way the filename would be unavailable is if the
+ * implementation uses a system API that creates a temporary file, and
+ * that API doesn't return the name of the created temporary file. In
+ * such cases, we don't need the name; the only reason we need the name is
+ * so we can pass it to osfdel_temp() later, but since the system is going
+ * to delete the file automatically, osfdel_temp() doesn't need to do
+ * anything and thus doesn't need the name.)
+ *
+ * After the caller is done with the file, it should close the file (using
+ * osfcls() as normal), then the caller MUST call osfdel_temp() to delete
+ * the temporary file.
+ *
+ * This interface is intended to take advantage of systems that have
+ * automatic support for temporary files, while allowing implementation on
+ * systems that don't have any special temp file support. On systems that
+ * do have automatic delete-on-close support, this routine should use that
+ * system-level support, because it helps ensure that temp files will be
+ * deleted even if the caller fails to call osfdel_temp() due to a
+ * programming error or due to a process or system crash. On systems that
+ * don't have any automatic delete-on-close support, this routine can
+ * simply use the same underlying system API that osfoprwbt() normally
+ * uses (although this routine must also generate a name for the temp file
+ * when the caller doesn't supply one).
+ *
+ * This routine can be implemented using ANSI library functions as
+ * follows: if 'fname' is non-null, return fopen(fname,"w+b"); otherwise,
+ * set buf[0] to '\0' and return tmpfile().
+ */
+osfildef *os_create_tempfile(const char *fname, char *buf);
+
+/*
+ * Delete a temporary file - this is used to delete a file created with
+ * os_create_tempfile(). For most platforms, this can simply be defined
+ * the same way as osfdel(). For platforms where the operating system or
+ * file manager will automatically delete a file opened as a temporary
+ * file, this routine should do nothing at all, since the system will take
+ * care of deleting the temp file.
+ *
+ * Callers are REQUIRED to call this routine after closing a file opened
+ * with os_create_tempfile(). When os_create_tempfile() is called with a
+ * non-null 'fname' argument, the same value should be passed as 'fname' to
+ * this function. When os_create_tempfile() is called with a null 'fname'
+ * argument, then the buffer passed in the 'buf' argument to
+ * os_create_tempfile() must be passed as the 'fname' argument here. In
+ * other words, if the caller explicitly names the temporary file to be
+ * opened in os_create_tempfile(), then that same filename must be passed
+ * here to delete the named file; if the caller lets os_create_tempfile()
+ * generate a filename, then the generated filename must be passed to this
+ * routine.
+ *
+ * If os_create_tempfile() is implemented using ANSI library functions as
+ * described above, then this routine can also be implemented with ANSI
+ * library calls as follows: if 'fname' is non-null and fname[0] != '\0',
+ * then call remove(fname); otherwise do nothing.
+ */
+int osfdel_temp(const char *fname);
+
+/*
+ * Get the temporary file path. This should fill in the buffer with a
+ * path prefix (suitable for strcat'ing a filename onto) for a good
+ * directory for a temporary file, such as the swap file.
+ */
+void os_get_tmp_path(char *buf);
+
+/*
+ * Generate a name for a temporary file. This constructs a random file
+ * path in the system temp directory that isn't already used by an existing
+ * file.
+ *
+ * On systems with long filenames, this can be implemented by selecting a
+ * GUID-strength random name (such as 32 random hex digits) with a decent
+ * random number generator. That's long enough that the odds of a
+ * collision are essentially zero. On systems that only support short
+ * filenames, the odds of a collision are non-zero, so the routine should
+ * actually check that the chosen filename doesn't exist.
+ *
+ * Optionally, before returning, this routine *may* create (and close) an
+ * empty placeholder file to "reserve" the chosen filename. This isn't
+ * required, and on systems with long filenames it's usually not necessary
+ * because of the negligible chance of a collision. On systems with short
+ * filenames, a placeholder can be useful to prevent a subsequent call to
+ * this routine, or a separate process, from using the same filename before
+ * the caller has had a chance to use the returned name to create the
+ * actual temp file.
+ *
+ * Returns true on success, false on failure. This can fail if there's no
+ * system temporary directory defined, or the temp directory is so full of
+ * other files that we can't find an unused filename.
+ */
+int os_gen_temp_filename(char *buf, size_t buflen);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Basic directory/folder management routines
+ */
+
+/*
+ * Switch to a new working directory.
+ *
+ * This is meant to behave similarly to the Unix concept of a working
+ * directory, in that it sets the base directory assumed for subsequent
+ * file operations (e.g., the osfopxx() functions, osfdel(), etc - anything
+ * that takes a filename or directory name as an argument). The working
+ * directory applies to filenames specified with relative paths in the
+ * local system notation. File operations on filenames specified with
+ * absolute paths, of course, ignore the working directory.
+ */
+void os_set_pwd(const char *dir);
+
+/*
+ * Switch the working directory to the directory containing the given
+ * file. Generally, this routine should only need to parse the filename
+ * enough to determine the part that's the directory path, then use
+ * os_set_pwd() to switch to that directory.
+ */
+void os_set_pwd_file(const char *filename);
+
+/*
+ * Create a directory. This creates a new directory/folder with the given
+ * name, which may be given as a relative or absolute path. Returns true
+ * on success, false on failure.
+ *
+ * If 'create_parents' is true, and the directory has mulitiple path
+ * elements, this routine should create each enclosing parent that doesn't
+ * already exist. For example, if the path is specified as "a/b/c", and
+ * there exists a folder "a" in the working directory, but "a" is empty,
+ * this should first create "b" and then create "c". If an error occurs
+ * creating any parent, the routine should simply stop and return failure.
+ * (Optionally, the routine may attempt to behave atomically by undoing any
+ * parent folder creations it accomplished before failing on a nested
+ * folder, but this isn't required. To reduce the chances of a failure
+ * midway through the operation, the routine might want to scan the
+ * filename before starting to ensure that it contains only valid
+ * characters, since an invalid character is the most likely reason for a
+ * failure part of the way through.)
+ *
+ * We recommend making the routine flexible in terms of the notation it
+ * accepts; e.g., on Unix, "/dir/sub/folder" and "/dir/sub/folder/" should
+ * be considered equivalent.
+ */
+int os_mkdir(const char *dir, int create_parents);
+
+/*
+ * Remove a directory. Returns true on success, false on failure.
+ *
+ * If the directory isn't already empty, this routine fails. That is, the
+ * routine does NOT recursively delete the contents of a non-empty
+ * directory. It's up to the caller to delete any contents before removing
+ * the directory, if that's the caller's intention. (Note to implementors:
+ * most native OS APIs to remove directories fail by default if the
+ * directory isn't empty, so it's usually safe to implement this simply by
+ * calling the native API. However, if your system's version of this API
+ * can remove a non-empty directory, you MUST add an extra test before
+ * removing the directory to ensure it's empty, and return failure if it's
+ * not. For the purposes of this test, "empty" should of course ignore any
+ * special objects that are automatically or implicitly present in all
+ * directories, such as the Unix "." and ".." relative links.)
+ */
+int os_rmdir(const char *dir);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Filename manipulation routines
+ */
+
+/* apply a default extension to a filename, if it doesn't already have one */
+void os_defext(char *fname, const char *ext);
+
+/* unconditionally add an extention to a filename */
+void os_addext(char *fname, const char *ext);
+
+/* remove the extension from a filename */
+void os_remext(char *fname);
+
+/*
+ * Compare two file names/paths for syntactic equivalence. Returns true if
+ * the names are equivalent names according to the local file system's
+ * syntax conventions, false if not. This does a syntax-only comparison of
+ * the paths, without looking anything up in the file system. This means
+ * that a false return doesn't guarantee that the paths don't point to the
+ * same file.
+ *
+ * This routine DOES make the following equivalences:
+ *
+ * - if the local file system is insensitive to case, the names are
+ * compared ignoring case
+ *
+ * - meaningless path separator difference are ignored: on Unix, "a/b" ==
+ * "a//b" == "a/b/"; on Windows, "a/b" == "a\\b"
+ *
+ * - relative links that are strictly structural or syntactic are applied;
+ * for example, on Unix or Windows, "a/./b" == "a/b" = "a/b/c/..". This
+ * only applies for special relative links that can be resolved without
+ * looking anything up in the file system.
+ *
+ * This DOES NOT do the following:
+ *
+ * - it doesn't apply working directories/volums to relative paths
+ *
+ * - it doesn't follow symbolic links in the file system
+ */
+int os_file_names_equal(const char *a, const char *b);
+
+/*
+ * Get a pointer to the root name portion of a filename. This is the part
+ * of the filename after any path or directory prefix. For example, on
+ * Unix, given the string "/home/mjr/deep.gam", this function should return
+ * a pointer to the 'd' in "deep.gam". If the filename doesn't appear to
+ * have a path prefix, it should simply return the argument unchanged.
+ *
+ * IMPORTANT: the returned pointer MUST point into the original 'buf'
+ * string, and the contents of that buffer must NOT be modified. The
+ * return value must point into the same buffer because there are no
+ * allowances for the alternatives. In particular, (a) you can't return a
+ * pointer to newly allocated memory, because callers won't free it, so
+ * doing so would cause a memory leak; and (b) you can't return a pointer
+ * to an internal static buffer, because callers might call this function
+ * more than once and still rely on a value returned on an older call,
+ * which would be invalid if a static buffer could be overwritten on each
+ * call. For these reasons, it's required that the return value point to a
+ * position within the original string passed in 'buf'.
+ */
+char *os_get_root_name(const char *buf);
+
+/*
+ * Determine whether a filename specifies an absolute or relative path.
+ * This is used to analyze filenames provided by the user (for example,
+ * in a #include directive, or on a command line) to determine if the
+ * filename can be considered relative or absolute. This can be used,
+ * for example, to determine whether to search a directory path for a
+ * file; if a given filename is absolute, a path search makes no sense.
+ * A filename that doesn't specify an absolute path can be combined with
+ * a path using os_build_full_path().
+ *
+ * Returns true if the filename specifies an absolute path, false if
+ * not.
+ */
+int os_is_file_absolute(const char *fname);
+
+/*
+ * Extract the path from a filename. Fills in pathbuf with the path
+ * portion of the filename. If the filename has no path, the pathbuf
+ * should be set appropriately for the current directory (on Unix or DOS,
+ * for example, it can be set to an empty string).
+ *
+ * The result can end with a path separator character or not, depending on
+ * local OS conventions. Paths extracted with this function can only be
+ * used with os_build_full_path(), so the conventions should match that
+ * function's.
+ *
+ * Unix examples:
+ *
+ *. /home/mjr/deep.gam -> /home/mjr
+ *. games/deep.gam -> games
+ *. deep.gam -> [empty string]
+ *
+ * Mac examples:
+ *
+ * :home:mjr:deep.gam -> :home:mjr
+ *. Hard Disk:games:deep.gam -> Hard Disk:games
+ *. Hard Disk:deep.gam -> Hard Disk:
+ *. deep.gam -> [empty string]
+ *
+ * VMS examples:
+ *
+ *. SYS$DISK:[mjr.games]deep.gam -> SYS$DISK:[mjr.games]
+ *. SYS$DISK:[mjr.games] -> SYS$DISK:[mjr]
+ *. deep.gam -> [empty string]
+ *
+ * Note in the last example that we've retained the trailing colon in the
+ * path, whereas we didn't in the others; although the others could also
+ * retain the trailing colon, it's required only for the last case. The
+ * last case requires the colon because it would otherwise be impossible to
+ * determine whether "Hard Disk" was a local subdirectory or a volume name.
+ *
+ */
+void os_get_path_name(char *pathbuf, size_t pathbuflen, const char *fname);
+
+/*
+ * Build a full path name, given a path and a filename. The path may have
+ * been specified by the user, or may have been extracted from another file
+ * via os_get_path_name(). This routine must take care to add path
+ * separators as needed, but also must take care not to add too many path
+ * separators.
+ *
+ * This routine should reformat the path into canonical format to the
+ * extent possible purely through syntactic analysis. For example, special
+ * relative links, such as Unix "." and "..", should be resolved; for
+ * example, combining "a/./b/c" with ".." on Unix should yield "a/b".
+ * However, symbolic links that require looking up names in the file system
+ * should NOT be resolved. We don't want to perform any actual file system
+ * lookups because might want to construct hypothetical paths that don't
+ * necessarily relate to files on the local system.
+ *
+ * Note that relative path names may require special care on some
+ * platforms. In particular, if the source path is relative, the result
+ * should also be relative. For example, on the Macintosh, a path of
+ * "games" and a filename "deep.gam" should yield ":games:deep.gam" - note
+ * the addition of the leading colon to make the result path relative.
+ *
+ * Note also that the 'filename' argument is not only allowed to be an
+ * ordinary file, possibly qualified with a relative path, but is also
+ * allowed to be a subdirectory. The result in this case should be a path
+ * that can be used as the 'path' argument to a subsequent call to
+ * os_build_full_path; this allows a path to be built in multiple steps by
+ * descending into subdirectories one at a time.
+ *
+ * Unix examples:
+ *
+ *. /home/mjr + deep.gam -> /home/mjr/deep.gam"
+ *. /home/mjr + .. -> /home
+ *. /home/mjr + ../deep.gam -> /home/deep.gam
+ *. /home/mjr/ + deep.gam -> /home/mjr/deep.gam"
+ *. games + deep.gam -> games/deep.gam"
+ *. games/ + deep.gam -> games/deep.gam"
+ *. /home/mjr + games/deep.gam -> /home/mjr/games/deep.gam"
+ *. games + scifi/deep.gam -> games/scifi/deep.gam"
+ *. /home/mjr + games -> /home/mjr/games"
+ *
+ * Mac examples:
+ *
+ *. Hard Disk: + deep.gam -> Hard Disk:deep.gam
+ *. :games: + deep.gam -> :games:deep.gam
+ *. :games:deep + ::test.gam -> :games:test.gam
+ *. games + deep.gam -> :games:deep.gam
+ *. Hard Disk: + :games:deep.gam -> Hard Disk:games:deep.gam
+ *. games + :scifi:deep.gam -> :games:scifi:deep.gam
+ *. Hard Disk: + games -> Hard Disk:games
+ *. Hard Disk:games + scifi -> Hard Disk:games:scifi
+ *. Hard Disk:games:scifi + deep.gam -> Hard Disk:games:scifi:deep.gam
+ *. Hard Disk:games + :scifi:deep.gam -> Hard Disk:games:scifi:deep.gam
+ *
+ * VMS examples:
+ *
+ *. [home.mjr] + deep.gam -> [home.mjr]deep.gam
+ *. [home.mjr] + [-]deep.gam -> [home]deep.gam
+ *. mjr.dir + deep.gam -> [.mjr]deep.gam
+ *. [home]mjr.dir + deep.gam -> [home.mjr]deep.gam
+ *. [home] + [.mjr]deep.gam -> [home.mjr]deep.gam
+ */
+void os_build_full_path(char *fullpathbuf, size_t fullpathbuflen,
+ const char *path, const char *filename);
+
+/*
+ * Combine a path and a filename to form a full path to the file. This is
+ * *almost* the same as os_build_full_path(), but if the 'filename' element
+ * is a special relative link, such as Unix '.' or '..', this preserves
+ * that special link in the final name.
+ *
+ * Unix examples:
+ *
+ *. /home/mjr + deep.gam -> /home/mjr/deep.gam
+ *. /home/mjr + . -> /home/mjr/.
+ *. /home/mjr + .. -> /home/mjr/..
+ *
+ * Mac examples:
+ *
+ *. Hard Disk:games + deep.gam -> HardDisk:games:deep.gam
+ *. Hard Disk:games + :: -> HardDisk:games::
+ *
+ * VMS exmaples:
+ *
+ *. [home.mjr] + deep.gam -> [home.mjr]deep.gam
+ *. [home.mjr] + [-] -> [home.mjr.-]
+ */
+void os_combine_paths(char *fullpathbuf, size_t pathbuflen,
+ const char *path, const char *filename);
+
+
+/*
+ * Get the absolute, fully qualified filename for a file. This fills in
+ * 'result_buf' with the absolute path to the given file, taking into
+ * account the current working directory and any other implied environment
+ * information that affects the way the file system would resolve the given
+ * file name to a specific file on disk if we opened the file now using
+ * this name.
+ *
+ * The returned path should be in absolute path form, meaning that it's
+ * independent of the current working directory or any other environment
+ * settings. That is, this path should still refer to the same file even
+ * if the working directory changes.
+ *
+ * Note that it's valid to get the absolute path for a file that doesn't
+ * exist, or for a path with directory components that don't exist. For
+ * example, a caller might generate the absolute path for a file that it's
+ * about to create, or a hypothetical filename for path comparison
+ * purposes. The function should succeed even if the file or any path
+ * components don't exist. If the file is in relative format, and any path
+ * elements don't exist but are syntactically well-formed, the result
+ * should be the path obtained from syntactically combining the working
+ * directory with the relative path.
+ *
+ * On many systems, a given file might be reachable through more than one
+ * absolute path. For example, on Unix it might be possible to reach a
+ * file through symbolic links to the file itself or to parent directories,
+ * or hard links to the file. It's up to the implementation to determine
+ * which path to use in such cases.
+ *
+ * On success, returns true. If it's not possible to resolve the file name
+ * to an absolute path, the routine copies the original filename to the
+ * result buffer exactly as given, and returns false.
+ */
+int os_get_abs_filename(char *result_buf, size_t result_buf_size,
+ const char *filename);
+
+/*
+ * Get the relative version of the given filename path 'filename', relative
+ * to the given base directory 'basepath'. Both paths must be given in
+ * absolute format.
+ *
+ * Returns true on success, false if it's not possible to rewrite the path
+ * in relative terms. For example, on Windows, it's not possible to
+ * express a path on the "D:" drive as relative to a base path on the "C:"
+ * drive, since each drive letter has an independent root folder; there's
+ * no namespace entity enclosing a drive letter's root folder. On
+ * Unix-like systems where the entire namespace has a single hierarchical
+ * root, it should always be possible to express any path relative to any
+ * other.
+ *
+ * The result should be a relative path that can be combined with
+ * 'basepath' using os_build_full_path() to reconstruct a path that
+ * identifies the same file as the original 'filename' (it's not important
+ * that this procedure would result in the identical string - it just has
+ * to point to the same file). If it's not possible to express the
+ * filename relative to the base path, fill in 'result_buf' with the
+ * original filename and return false.
+ *
+ * Windows examples:
+ *
+ *. c:\mjr\games | c:\mjr\games\deep.gam -> deep.gam
+ *. c:\mjr\games | c:\mjr\games\tads\deep.gam -> tads\deep.gam
+ *. c:\mjr\games | c:\mjr\tads\deep.gam -> ..\tads\deep.gam
+ *. c:\mjr\games | d:\deep.gam -> d:\deep.gam (and return false)
+ *
+ * Mac OS examples:
+ *
+ *. Mac HD:mjr:games | Mac HD:mjr:games:deep.gam -> deep.gam
+ *. Mac HD:mjr:games | Mac HD:mjr:games:tads:deep.gam -> :tads:deep.gam
+ *. Mac HD:mjr:games | Ext Disk:deep.gam -> Ext Disk:deep.gam (return false)
+ *
+ * VMS examples:
+ *
+ *. SYS$:[mjr.games] | SYS$:[mjr.games]deep.gam -> deep.gam
+ *. SYS$:[mjr.games] | SYS$:[mjr.games.tads]deep.gam -> [.tads]deep.gam
+ *. SYS$:[mjr.games] | SYS$:[mjr.tads]deep.gam -> [-.tads]deep.gam
+ *. SYS$:[mjr.games] | DISK$:[mjr]deep.gam -> DISK$[mjr]deep.gam (ret false)
+ */
+int os_get_rel_path(char *result_buf, size_t result_buf_size,
+ const char *basepath, const char *filename);
+
+/*
+ * Determine if the given file is in the given directory. Returns true if
+ * so, false if not. 'filename' is a relative or absolute file name;
+ * 'path' is a relative or absolute directory path, such as one returned
+ * from os_get_path_name().
+ *
+ * If 'include_subdirs' is true, the function returns true if the file is
+ * either directly in the directory 'path', OR it's in any subdirectory of
+ * 'path'. If 'include_subdirs' is false, the function returns true only
+ * if the file is directly in the given directory.
+ *
+ * If 'match_self' is true, the function returns true if 'filename' and
+ * 'path' are the same directory; otherwise it returns false in this case.
+ *
+ * This routine is allowed to return "false negatives" - that is, it can
+ * claim that the file isn't in the given directory even when it actually
+ * is. The reason is that it's not always possible to determine for sure
+ * that there's not some way for a given file path to end up in the given
+ * directory. In contrast, a positive return must be reliable.
+ *
+ * If possible, this routine should fully resolve the names through the
+ * file system to determine the path relationship, rather than merely
+ * analyzing the text superficially. This can be important because many
+ * systems have multiple ways to reach a given file, such as via symbolic
+ * links on Unix; analyzing the syntax alone wouldn't reveal these multiple
+ * pathways.
+ *
+ * SECURITY NOTE: If possible, implementations should fully resolve all
+ * symbolic links, relative paths (e.g., Unix ".."), etc, before rendering
+ * judgment. One important application for this routine is to determine if
+ * a file is in a sandbox directory, to enforce security restrictions that
+ * prevent a program from accessing files outside of a designated folder.
+ * If the implementation fails to resolve symbolic links or relative paths,
+ * a malicious program or user could bypass the security restriction by,
+ * for example, creating a symbolic link within the sandbox directory that
+ * points to the root folder. Implementations can avoid this loophole by
+ * converting the file and directory names to absolute paths and resolving
+ * all symbolic links and relative notation before comparing the paths.
+ */
+int os_is_file_in_dir(const char *filename, const char *path,
+ int include_subdirs, int match_self);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Convert an OS filename path to URL-style format. This isn't a true URL
+ * conversion; rather, it simply expresses a filename in Unix-style
+ * notation, as a series of path elements separated by '/' characters.
+ * Unlike true URLs, we don't use % encoding or a scheme prefix (file://,
+ * etc).
+ *
+ * The result path never ends in a trailing '/', unless the entire result
+ * path is "/". This is for consistency; even if the source path ends with
+ * a local path separator, the result doesn't.
+ *
+ * If the local file system syntax uses '/' characters as ordinary filename
+ * characters, these must be replaced with some other suitable character in
+ * the result, since otherwise they'd be taken as path separators when the
+ * URL is parsed. If possible, the substitution should be reversible with
+ * respect to os_cvt_dir_url(), so that the same URL read back in on this
+ * same platform will produce the same original filename. One particular
+ * suggestion is that if the local system uses '/' to delimit what would be
+ * a filename extension on other platforms, replace '/' with '.', since
+ * this will provide reversibility as well as a good mapping if the URL is
+ * read back in on another platform.
+ *
+ * The local equivalents of "." and "..", if they exist, are converted to
+ * "." and ".." in the URL notation.
+ *
+ * Examples:
+ *
+ *. Windows: images\rooms\startroom.jpg -> images/rooms/startroom.jpg
+ *. Windows: ..\startroom.jpg -> ../startroom.jpg
+ *. Mac: :images:rooms:startroom.jpg -> images/rooms/startroom.jpg
+ *. Mac: ::startroom.jpg -> ../startroom.jpg
+ *. VMS: [.images.rooms]startroom.jpg -> images/rooms/startroom.jpg
+ *. VMS: [-.images]startroom.jpg -> ../images/startroom.jpg
+ *. Unix: images/rooms/startroom.jpg -> images/rooms/startroom.jpg
+ *. Unix: ../images/startroom.jpg -> ../images/startroom.jpg
+ *
+ * If the local name is an absolute path in the local file system (e.g.,
+ * Unix /file, Windows C:\file), translate as follows. If the local
+ * operating system uses a volume or device designator (Windows C:, VMS
+ * SYS$DISK:, etc), make the first element of the path the exact local
+ * syntax for the device designator: /C:/ on Windows, /SYS$DISK:/ on VMS,
+ * etc. Include the local syntax for the device prefix. For a system like
+ * Unix with a unified file system root ("/"), simply start with the root
+ * directory. Examples:
+ *
+ *. Windows: C:\games\deep.gam -> /C:/games/deep.gam
+ *. Windows: C:games\deep.gam -> /C:./games/deep.gam
+ *. Windows: \\SERVER\DISK\games\deep.gam -> /\\SERVER/DISK/games/deep.gam
+ *. Mac OS 9: Hard Disk:games:deep.gam -> /Hard Disk:/games/deep.gam
+ *. VMS: SYS$DISK:[games]deep.gam -> /SYS$DISK:/games/deep.gam
+ *. Unix: /games/deep.gam -> /games/deep.gam
+ *
+ * Rationale: it's effectively impossible to create a truly portable
+ * representation of an absolute path. Operating systems are too different
+ * in the way they represent root paths, and even if that were solvable, a
+ * root path is essentially unusable across machines anyway because it
+ * creates a dependency on the contents of a particular machine's disk. So
+ * if we're called upon to translate an absolute path, we can forget about
+ * trying to be truly portable and instead focus on round-trip fidelity -
+ * i.e., making sure that applying os_cvt_url_dir() to our result recovers
+ * the exact original path, assuming it's done on the same operating
+ * system. The approach outlined above should achieve round-trip fidelity
+ * when a local path is converted to a URL and back on the same machine,
+ * since the local URL-to-path converter should recognize its own special
+ * type of local absolute path prefix. It also produces reasonable results
+ * on other platforms - see the os_cvt_url_dir() comments below for
+ * examples of the decoding results for absolute paths moved to new
+ * platforms. The result when a device-rooted absolute path is encoded on
+ * one machine and then decoded on another will generally be a local path
+ * with a root on the default device/volume and an outermost directory with
+ * a name based on the original machine's device/volume name. This
+ * obviously won't reproduce the exact original path, but since that's
+ * impossible anyway, this is probably as good an approximation as we can
+ * create.
+ *
+ * Character sets: the input could be in local or UTF-8 character sets.
+ * The implementation shouldn't care, though - just treat bytes in the
+ * range 0-127 as plain ASCII, and everything else as opaque. I.e., do not
+ * quote or otherwise modify characters outside the 0-127 range.
+ */
+void os_cvt_dir_url(char *result_buf, size_t result_buf_size,
+ const char *src_path);
+
+/*
+ * Convert a URL-style path into a filename path expressed in the local
+ * file system's syntax. Fills in result_buf with a file path, constructed
+ * using the local file system syntax, that corresponds to the path in
+ * src_url expressed in URL-style syntax. Examples:
+ *
+ * images/rooms/startroom.jpg ->
+ *. Windows -> images\rooms\startroom.jpg
+ *. Mac OS 9 -> :images:rooms:startroom.jpg
+ *. VMS -> [.images.rooms]startroom.jpg
+ *
+ * The source format isn't a true URL; it's simply a series of path
+ * elements separated by '/' characters. Unlike true URLs, our input
+ * format doesn't use % encoding and doesn't have a scheme (file://, etc).
+ * (Any % in the source is treated as an ordinary character and left as-is,
+ * even if it looks like a %XX sequence. Anything that looks like a scheme
+ * prefix is left as-is, with any // treated as path separators.
+ *
+ * images/file%20name.jpg ->
+ *. Windows -> images\file%20name.jpg
+ *
+ * file://images/file.jpg ->
+ *. Windows -> file_\\images\file.jpg
+ *
+ * Any characters in the path that are invalid in the local file system
+ * naming rules are converted to "_", unless "_" is itself invalid, in
+ * which case they're converted to "X". One exception is that if '/' is a
+ * valid local filename character (rather than a path separator as it is on
+ * Unix and Windows), it can be used as the replacement for the character
+ * that os_cvt_dir_url uses as its replacement for '/', so that this
+ * substitution is reversible when a URL is generated and then read back in
+ * on this same platform.
+ *
+ * images/file:name.jpg ->
+ *. Windows -> images\file_name.jpg
+ *. Mac OS 9 -> :images:file_name.jpg
+ *. Unix -> images/file:name.jpg
+ *
+ * The path elements "." and ".." are specifically defined as having their
+ * Unix meanings: "." is an alias for the preceding path element, or the
+ * working directory if it's the first element, and ".." is an alias for
+ * the parent of the preceding element. When these appear as path
+ * elements, this routine translates them to the appropriate local
+ * conventions. "." may be translated simply by removing it from the path,
+ * since it reiterates the previous path element. ".." may be translated
+ * by removing the previous element - HOWEVER, if ".." appears as the first
+ * element, it has to be retained and translated to the equivalent local
+ * notation, since it will have to be applied later, when the result_buf
+ * path is actually used to open a file, at which point it will combined
+ * with the working directory or another base path.
+ *
+ *. /images/../file.jpg -> [Windows] file.jpg
+ *. ../images/file.jpg ->
+ *. Windows -> ..\images\file.jpg
+ *. Mac OS 9 -> ::images:file.jpg
+ *. VMS -> [-.images]file.jpg
+ *
+ * If the URL path is absolute (starts with a '/'), the routine inspects
+ * the path to see if it was created by the same OS, according to the local
+ * rules for converting absolute paths in os_cvt_dir_url() (see). If so,
+ * we reverse the encoding done there. If it doesn't appear that the name
+ * was created by the same operating system - that is, if reversing the
+ * encoding doesn't produce a valid local filename - then we create a local
+ * absolute path as follows. If the local system uses device/volume
+ * designators, we start with the current working device/volume or some
+ * other suitable default volume. We then add the first element of the
+ * path, if any, as the root directory name, applying the usual "_" or "X"
+ * substitution for any characters that aren't allowed in local names. The
+ * rest of the path is handled in the usual fashion.
+ *
+ *. /images/file.jpg ->
+ *. Windows -> \images\file.jpg
+ *. Unix -> /images/file.jpg
+ *
+ *. /c:/images/file.jpg ->
+ *. Windows -> c:\images\file.jpg
+ *. Unix -> /c:/images/file.jpg
+ *. VMS -> SYS$DISK:[c__.images]file.jpg
+ *
+ *. /Hard Disk:/images/file.jpg ->
+ *. Windows -> \Hard Disk_\images\file.jpg
+ *. Unix -> SYS$DISK:[Hard_Disk_.images]file.jpg
+ *
+ * Note how the device/volume prefix becomes the top-level directory when
+ * moving a path across machines. It's simply not possible to reconstruct
+ * the exact original path in such cases, since device/volume syntax rules
+ * have little in common across systems. But this seems like a good
+ * approximation in that (a) it produces a valid local path, and (b) it
+ * gives the user a reasonable basis for creating a set of folders to mimic
+ * the original source system, if they want to use that approach to port
+ * the data rather than just changing the paths internally in the source
+ * material.
+ *
+ * Character sets: use the same rules as for os_cvt_dir_url().
+ */
+void os_cvt_url_dir(char *result_buf, size_t result_buf_size,
+ const char *src_url);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Get a suitable seed for a random number generator; should use the system
+ * clock or some other source of an unpredictable and changing seed value.
+ */
+void os_rand(long *val);
+
+/*
+ * Generate random bytes for use in seeding a PRNG (pseudo-random number
+ * generator). This is an extended version of os_rand() for PRNGs that use
+ * large seed vectors containing many bytes, rather than the simple 32-bit
+ * seed that os_rand() assumes.
+ *
+ * As with os_rand(), this function isn't meant to be used directly as a
+ * random number source for ongoing use - instead, this is intended mostly
+ * for seeding a PRNG, which will then be used as the primary source of
+ * random numbers. The big problem with using this routine directly as a
+ * randomness source is that some implementations might rely heavily on
+ * environmental randomness, such as the real-time clock or OS usage
+ * statistics. Such sources tend to provide reasonable entropy from one
+ * run to the next, but not within a single session, as the underlying data
+ * sources don't change rapidly enough.
+ *
+ * Ideally, this routine should generate *truly* random bytes obtained from
+ * hardware sources. Not all systems can provide that, though, so true
+ * randomness isn't guaranteed. Here are the suggested implementation
+ * options, in descending order of desirability:
+ *
+ * 1. Use a hardware source of true randomness, such as a /dev/rand type
+ * of device. However, note that this call should return reasonably
+ * quickly, so always use a non-blocking source. Some Unix /dev/rand
+ * devices, for example, can block indefinitely to allow sufficient entropy
+ * to accumulate.
+ *
+ * 2. Use a cryptographic random number source provided by the OS. Some
+ * systems provide this as an API service. If going this route, be sure
+ * that the OS generator is itself "seeded" with some kind of true
+ * randomness source, as it defeats the whole purpose if you always return
+ * a fixed pseudo-random sequence each time the program runs.
+ *
+ * 3. Use whatever true random sources are available locally to seed a
+ * software pseudo-random number generator, then generate bytes from your
+ * PRNG. Some commonly available sources of true randomness are a
+ * high-resolution timer, the system clock, the current process ID,
+ * logged-in user ID, environment variables, uninitialized pages of memory,
+ * the IP address; each of these sources might give you a few bits of
+ * entropy at best, so the best bet is to use an ensemble. You could, for
+ * example, concatenate a bunch of this type of information together and
+ * calculate an MD5 or SHA1 hash to mix the bits more thoroughly. For the
+ * PRNG, use a cryptographic generator. (You could use the ISAAC generator
+ * from TADS 3, as that's a crypto PRNG, but it's probably better to use a
+ * different generator here since TADS 3 is going to turn around and use
+ * this function's output to seed ISAAC - seeding one ISAAC instance with
+ * another ISAAC's output seems likely to magnify any weaknesses in the
+ * ISAAC algorithm.) Note that this option is basically the DIY version of
+ * option 2. Option 2 is better because the OS probably has access to
+ * better sources of true randomness than an application does.
+ */
+void os_gen_rand_bytes(unsigned char *buf, size_t len);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Display routines.
+ *
+ * Our display model is a simple stdio-style character stream.
+ *
+ * In addition, we provide an optional "status line," which is a
+ * non-scrolling area where a line of text can be displayed. If the status
+ * line is supported, text should only be displayed in this area when
+ * os_status() is used to enter status-line mode (mode 1); while in status
+ * line mode, text is written to the status line area, otherwise (mode 0)
+ * it's written to the normal main text area. The status line is normally
+ * shown in a different color to set it off from the rest of the text.
+ *
+ * The OS layer can provide its own formatting (word wrapping in
+ * particular) if it wants, in which case it should also provide pagination
+ * using os_more_prompt().
+ */
+
+/*
+ * OS_MAXWIDTH - the maximum width of a line of text. Most platforms use
+ * 135 for this, but you can use more or less as appropriate. If you use
+ * OS-level line wrapping, then the true width of a text line is
+ * irrelevant, and the portable code will use this merely for setting its
+ * internal buffer sizes.
+ *
+ * This must be defined in the os_xxx.h header file for each platform.
+ */
+/*#define OS_MAXWIDTH 135 - example only: define for real in os_xxx.h header*/
+
+/*
+ * Print a string on the console. These routines come in two varieties:
+ *
+ * os_printz - write a NULL-TERMINATED string
+ *. os_print - write a COUNTED-LENGTH string, which may not end with a null
+ *
+ * These two routines are identical except that os_printz() takes a string
+ * which is terminated by a null byte, and os_print() instead takes an
+ * explicit length, and a string that may not end with a null byte.
+ *
+ * os_printz(str) may be implemented as simply os_print(str, strlen(str)).
+ *
+ * The string is written in one of three ways, depending on the status mode
+ * set by os_status():
+ *
+ * status mode == 0 -> write to main text window
+ *. status mode == 1 -> write to status line
+ *. anything else -> do not display the text at all
+ *
+ * Implementations are free to omit any status line support, in which case
+ * they should simply suppress all output when the status mode is anything
+ * other than zero.
+ *
+ * The following special characters must be recognized in the displayed
+ * text:
+ *
+ * '\n' - newline: end the current line and move the cursor to the start of
+ * the next line. If the status line is supported, and the current status
+ * mode is 1 (i.e., displaying in the status line), then two special rules
+ * apply to newline handling: newlines preceding any other text should be
+ * ignored, and a newline following any other text should set the status
+ * mode to 2, so that all subsequent output is suppressed until the status
+ * mode is changed with an explicit call by the client program to
+ * os_status().
+ *
+ * '\r' - carriage return: end the current line and move the cursor back to
+ * the beginning of the current line. Subsequent output is expected to
+ * overwrite the text previously on this same line. The implementation
+ * may, if desired, IMMEDIATELY clear the previous text when the '\r' is
+ * written, rather than waiting for subsequent text to be displayed.
+ *
+ * All other characters may be assumed to be ordinary printing characters.
+ * The routine need not check for any other special characters.
+ *
+ */
+void os_printz(const char *str);
+void os_print(const char *str, size_t len);
+
+/*
+ * Print to the debugger console. These routines are for interactive
+ * debugger builds only: they display the given text to a separate window
+ * within the debugger UI (separate from the main game command window)
+ * where the debugger displays status information specific to the debugging
+ * session (such as compiler/build output, breakpoint status messages,
+ * etc). For example, TADS Workbench on Windows displays these messages in
+ * its "Debug Log" window.
+ *
+ * These routines only need to be implemented for interactive debugger
+ * builds, such as TADS Workbench on Windows. These can be omitted for
+ * regular interpreter builds.
+ */
+void os_dbg_printf(const char *fmt, ...);
+void os_dbg_vprintf(const char *fmt, va_list args);
+
+/*
+ * Allocating sprintf and vsprintf. These work like the regular C library
+ * sprintf and vsprintf funtions, but they allocate a return buffer that's
+ * big enough to hold the result, rather than formatting into a caller's
+ * buffer. This protects against buffer overruns and ensures that the
+ * result isn't truncated.
+ *
+ * On return, '*bufptr' is filled in with a pointer to a buffer allocated
+ * with osmalloc(). This buffer contains the formatted string result. The
+ * caller is responsible for freeing the buffer by calling osfree().
+ * *bufptr can be null on return if an error occurs.
+ *
+ * The return value is the number of bytes written to the allocated buffer,
+ * not including the null terminator. If an error occurs, the return value
+ * is -1 and *bufptr is undefined.
+ *
+ * Many modern C libraries provide equivalents of these, usually called
+ * asprintf() and vasprintf(), respectively.
+ */
+/* int os_asprintf(char **bufptr, const char *fmt, ...); */
+int os_vasprintf(char **bufptr, const char *fmt, va_list ap);
+
+
+/*
+ * Set the status line mode. There are three possible settings:
+ *
+ * 0 -> main text mode. In this mode, all subsequent text written with
+ * os_print() and os_printz() is to be displayed to the main text area.
+ * This is the normal mode that should be in effect initially. This mode
+ * stays in effect until an explicit call to os_status().
+ *
+ * 1 -> statusline mode. In this mode, text written with os_print() and
+ * os_printz() is written to the status line, which is usually rendered as
+ * a one-line area across the top of the terminal screen or application
+ * window. In statusline mode, leading newlines ('\n' characters) are to
+ * be ignored, and any newline following any other character must change
+ * the mode to 2, as though os_status(2) had been called.
+ *
+ * 2 -> suppress mode. In this mode, all text written with os_print() and
+ * os_printz() must simply be ignored, and not displayed at all. This mode
+ * stays in effect until an explicit call to os_status().
+ */
+void os_status(int stat);
+
+/* get the status line mode */
+int os_get_status();
+
+/*
+ * Set the score value. This displays the given score and turn counts on
+ * the status line. In most cases, these values are displayed at the right
+ * edge of the status line, in the format "score/turns", but the format is
+ * up to the implementation to determine. In most cases, this can simply
+ * be implemented as follows:
+ *
+ *. void os_score(int score, int turncount)
+ *. {
+ *. char buf[40];
+ *. sprintf(buf, "%d/%d", score, turncount);
+ *. os_strsc(buf);
+ *. }
+ */
+void os_score(int score, int turncount);
+
+/* display a string in the score area in the status line */
+void os_strsc(const char *p);
+
+/* clear the screen */
+void oscls(void);
+
+/* redraw the screen */
+void os_redraw(void);
+
+/* flush any buffered display output */
+void os_flush(void);
+
+/*
+ * Update the display - process any pending drawing immediately. This
+ * only needs to be implemented for operating systems that use
+ * event-driven drawing based on window invalidations; the Windows and
+ * Macintosh GUI's both use this method for drawing window contents.
+ *
+ * The purpose of this routine is to refresh the display prior to a
+ * potentially long-running computation, to avoid the appearance that the
+ * application is frozen during the computation delay.
+ *
+ * Platforms that don't need to process events in the main thread in order
+ * to draw their window contents do not need to do anything here. In
+ * particular, text-mode implementations generally don't need to implement
+ * this routine.
+ *
+ * This routine doesn't absolutely need a non-empty implementation on any
+ * platform, but it will provide better visual feedback if implemented for
+ * those platforms that do use event-driven drawing.
+ */
+void os_update_display();
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Set text attributes. Text subsequently displayed through os_print() and
+ * os_printz() are to be displayed with the given attributes.
+ *
+ * 'attr' is a (bitwise-OR'd) combination of OS_ATTR_xxx values. A value
+ * of zero indicates normal text, with no extra attributes.
+ */
+void os_set_text_attr(int attr);
+
+/* attribute code: bold-face */
+#define OS_ATTR_BOLD 0x0001
+
+/* attribute code: italic */
+#define OS_ATTR_ITALIC 0x0002
+
+/*
+ * Abstract attribute codes. Each platform can choose a custom rendering
+ * for these by #defining them before this point, in the OS-specific header
+ * (osdos.h, osmac.h, etc). We provide *default* definitions in case the
+ * platform doesn't define these.
+ *
+ * For compatibility with past versions, we treat HILITE, EM, and BOLD as
+ * equivalent. Platforms that can display multiple kinds of text
+ * attributes (boldface and italic, say) should feel free to use more
+ * conventional HTML mappings, such as EM->italic and STRONG->bold.
+ */
+
+/*
+ * "Highlighted" text, as appropriate to the local platform. On most
+ * text-mode platforms, the only kind of rendering variation possible is a
+ * brighter or intensified color. If actual bold-face is available, that
+ * can be used instead. This is the attribute used for text enclosed in a
+ * TADS2 "\( \)" sequence.
+ */
+#ifndef OS_ATTR_HILITE
+# define OS_ATTR_HILITE OS_ATTR_BOLD
+#endif
+
+/* HTML <em> attribute - by default, map this to bold-face */
+#ifndef OS_ATTR_EM
+# define OS_ATTR_EM OS_ATTR_BOLD
+#endif
+
+/* HTML <strong> attribute - by default, this has no effect */
+#ifndef OS_ATTR_STRONG
+# define OS_ATTR_STRONG 0
+#endif
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Colors.
+ *
+ * There are two ways of encoding a color. First, a specific color can be
+ * specified as an RGB (red-green-blue) value, with discreet levels for
+ * each component's intensity, ranging from 0 to 255. Second, a color can
+ * be "parameterized," which doesn't specify a color in absolute terms but
+ * rather specified one of a number of pre-defined *types* of colors;
+ * these pre-defined types can be chosen by the OS implementation, or, on
+ * some systems, selected by the user via a preferences mechanism.
+ *
+ * The os_color_t type encodes a color in 32 bits. The high-order 8 bits
+ * of a color value give the parameterized color identifier, or are set to
+ * zero to indicate an RGB color. An RGB color is encoded in the
+ * low-order 24 bits, via the following formula:
+ *
+ * (R << 16) + (G << 8) + B
+ *
+ * R specifies the intensity of the red component of the color, G green,
+ * and B blue. Each of R, G, and B must be in the range 0-255.
+ */
+typedef unsigned long os_color_t;
+
+/* encode an R, G, B triplet into an os_color_t value */
+#define os_rgb_color(r, g, b) (((r) << 16) + ((g) << 8) + (b))
+
+/*
+ * Determine if a color is given as an RGB value or as a parameterized
+ * color value. Returns true if the color is given as a parameterized
+ * color (one of the OS_COLOR_xxx values), false if it's given as an
+ * absolute RGB value.
+ */
+#define os_color_is_param(color) (((color) & 0xFF000000) != 0)
+
+/* get the red/green/blue components of an os_color_t value */
+#define os_color_get_r(color) ((int)(((color) >> 16) & 0xFF))
+#define os_color_get_g(color) ((int)(((color) >> 8) & 0xFF))
+#define os_color_get_b(color) ((int)((color) & 0xFF))
+
+/*
+ * Parameterized color codes. These are os_color_t values that indicate
+ * colors by type, rather than by absolute RGB values.
+ */
+
+/*
+ * "transparent" - applicable to backgrounds only, this specifies that the
+ * current screen background color should be used
+ */
+#define OS_COLOR_P_TRANSPARENT ((os_color_t)0x01000000)
+
+/* "normal text" color (as set via user preferences, if applicable) */
+#define OS_COLOR_P_TEXT ((os_color_t)0x02000000)
+
+/* normal text background color (from user preferences) */
+#define OS_COLOR_P_TEXTBG ((os_color_t)0x03000000)
+
+/* "status line" text color (as set via user preferences, if applicable) */
+#define OS_COLOR_P_STATUSLINE ((os_color_t)0x04000000)
+
+/* status line background color (from user preferences) */
+#define OS_COLOR_P_STATUSBG ((os_color_t)0x05000000)
+
+/* input text color (as set via user preferences, if applicable) */
+#define OS_COLOR_P_INPUT ((os_color_t)0x06000000)
+
+/*
+ * Set the text foreground and background colors. This sets the text
+ * color for subsequent os_printf() and os_vprintf() calls.
+ *
+ * The background color can be OS_COLOR_TRANSPARENT, in which case the
+ * background color is "inherited" from the current screen background.
+ * Note that if the platform is capable of keeping old text for
+ * "scrollback," then the transparency should be a permanent attribute of
+ * the character - in other words, it should not be mapped to the current
+ * screen color in the scrollback buffer, because doing so would keep the
+ * current screen color even if the screen color changes in the future.
+ *
+ * Text color support is optional. If the platform doesn't support text
+ * colors, this can simply do nothing. If the platform supports text
+ * colors, but the requested color or attributes cannot be displayed, the
+ * implementation should use the best available approximation.
+ */
+void os_set_text_color(os_color_t fg, os_color_t bg);
+
+/*
+ * Set the screen background color. This sets the text color for the
+ * background of the screen. If possible, this should immediately redraw
+ * the main text area with this background color. The color is given as an
+ * OS_COLOR_xxx value.
+ *
+ * If the platform is capable of redisplaying the existing text, then any
+ * existing text that was originally displayed with 'transparent'
+ * background color should be redisplayed with the new screen background
+ * color. In other words, the 'transparent' background color of previously
+ * drawn text should be a permanent attribute of the character - the color
+ * should not be mapped on display to the then-current background color,
+ * because doing so would lose the transparency and thus retain the old
+ * screen color on a screen color change.
+ */
+void os_set_screen_color(os_color_t color);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * os_plain() - Use plain ascii mode for the display. If possible and
+ * necessary, turn off any text formatting effects, such as cursor
+ * positioning, highlighting, or coloring. If this routine is called,
+ * the terminal should be treated as a simple text stream; users might
+ * wish to use this mode for things like text-to-speech converters.
+ *
+ * Purely graphical implementations that cannot offer a textual mode
+ * (such as Mac OS or Windows) can ignore this setting.
+ *
+ * If this routine is to be called, it must be called BEFORE os_init().
+ * The implementation should set a flag so that os_init() will know to
+ * set up the terminal for plain text output.
+ */
+#ifndef os_plain
+/*
+ * some platforms (e.g. Mac OS) define this to be a null macro, so don't
+ * define a prototype in those cases
+ */
+void os_plain(void);
+#endif
+
+/*
+ * Set the game title. The output layer calls this routine when a game
+ * sets its title (via an HTML <title> tag, for example). If it's
+ * convenient to do so, the OS layer can use this string to set a window
+ * caption, or whatever else makes sense on each system. Most
+ * character-mode implementations will provide an empty implementation,
+ * since there's not usually any standard way to show the current
+ * application title on a character-mode display.
+ */
+void os_set_title(const char *title);
+
+/*
+ * Show the system-specific MORE prompt, and wait for the user to respond.
+ * Before returning, remove the MORE prompt from the screen.
+ *
+ * This routine is only used and only needs to be implemented when the OS
+ * layer takes responsibility for pagination; this will be the case on
+ * most systems that use proportionally-spaced (variable-pitch) fonts or
+ * variable-sized windows, since on such platforms the OS layer must do
+ * most of the formatting work, leaving the standard output layer unable
+ * to guess where pagination should occur.
+ *
+ * If the portable output formatter handles the MORE prompt, which is the
+ * usual case for character-mode or terminal-style implementations, this
+ * routine is not used and you don't need to provide an implementation.
+ * Note that HTML TADS provides an implementation of this routine, because
+ * the HTML renderer handles line breaking and thus must handle
+ * pagination.
+ */
+void os_more_prompt();
+
+/*
+ * Interpreter Class Configuration.
+ *
+ * If this is a TEXT-ONLY interpreter: DO NOT define USE_HTML.
+ *
+ * If this is a MULTIMEDIA (HTML TADS) intepreter: #define USE_HTML
+ *
+ * (This really should be called something like OS_USE_HTML - the USE_ name
+ * is for historical reasons. This purpose of this macro is to configure
+ * the tads 2 VM-level output formatter's line breaking and MORE mode
+ * behavior. In HTML mode, the VM-level formatter knows that it's feeding
+ * its output to a page layout engine, so the VM-level output is just a
+ * text stream. In plain-text mode, the VM formatter *is* the page layout
+ * engine, so it needs to do all of the word wrapping and MORE prompting
+ * itself. The tads 3 output layer does NOT use this macro for its
+ * equivalent configuration, but instead has different .cpp files for the
+ * different modes, and you simply link in the one for the configuration
+ * you want.)
+ */
+/* #define USE_HTML */
+
+
+/*
+ * Enter HTML mode. This is only used when the run-time is compiled
+ * with the USE_HTML flag defined. This call instructs the renderer
+ * that HTML sequences should be parsed; until this call is made, the
+ * renderer should not interpret output as HTML. Non-HTML
+ * implementations do not need to define this routine, since the
+ * run-time will not call it if USE_HTML is not defined.
+ */
+void os_start_html(void);
+
+/* exit HTML mode */
+void os_end_html(void);
+
+/*
+ * Global variables with the height and width (in character cells - rows
+ * and columns) of the main text display area into which os_printf
+ * displays. The height and width are given in text lines and character
+ * columns, respectively. The portable code can use these values to
+ * format text for display via os_printf(); for example, the caller can
+ * use the width to determine where to put line breaks.
+ *
+ * These values are only needed for systems where os_printf() doesn't
+ * perform its own word-wrap formatting. On systems such as the Mac,
+ * where os_printf() performs word wrapping, these sizes aren't really
+ * important because the portable code doesn't need to perform any real
+ * formatting.
+ *
+ * These variables reflect the size of the "main text area," which is the
+ * area of the screen excluding the status line and any "banner" windows
+ * (as created with the os_banner_xxx() interfaces).
+ *
+ * The OS code must initialize these variables during start-up, and must
+ * adjust them whenever the display size is changed by user action or
+ * other external events (for example, if we're running inside a terminal
+ * window, and the user resizes the window, the OS code must recalculate
+ * the layout and adjust these accordingly).
+ */
+extern int G_os_pagelength;
+extern int G_os_linewidth;
+
+/*
+ * Global flag that tells the output formatter whether to count lines
+ * that it's displaying against the total on the screen so far. If this
+ * variable is true, lines are counted, and the screen is paused with a
+ * [More] message when it's full. When not in MORE mode, lines aren't
+ * counted. This variable should be set to false when displaying text
+ * that doesn't count against the current page, such as status line
+ * information.
+ *
+ * This flag should not be modified by OS code. Instead, the output
+ * formatter will set this flag according to its current state; the OS
+ * code can use this flag to determine whether or not to display a MORE
+ * prompt during os_printf()-type operations. Note that this flag is
+ * normally interesting to the OS code only when the OS code itself is
+ * handling the MORE prompt.
+ */
+extern int G_os_moremode;
+
+/*
+ * Global buffer containing the name of the byte-code file (the "game
+ * file") loaded into the VM. This is used only where applicable, which
+ * generally means in TADS Interpreter builds. In other application
+ * builds, this is simply left empty. The application is responsible for
+ * setting this during start-up (or wherever else the byte-code filename
+ * becomes known or changes).
+ */
+extern char G_os_gamename[OSFNMAX];
+
+/*
+ * Set non-stop mode. This tells the OS layer that it should disable any
+ * MORE prompting it would normally do.
+ *
+ * This routine is needed only when the OS layer handles MORE prompting; on
+ * character-mode platforms, where the prompting is handled in the portable
+ * console layer, this can be a dummy implementation.
+ */
+void os_nonstop_mode(int flag);
+
+/*
+ * Update progress display with current info, if appropriate. This can
+ * be used to provide a status display during compilation. Most
+ * command-line implementations will just ignore this notification; this
+ * can be used for GUI compiler implementations to provide regular
+ * display updates during compilation to show the progress so far.
+ */
+/* void os_progress(const char *fname, unsigned long linenum); */
+
+/*
+ * Set busy cursor. If 'flag' is true, provide a visual representation
+ * that the system or application is busy doing work. If 'flag' is
+ * false, remove any visual "busy" indication and show normal status.
+ *
+ * We provide a prototype here if your osxxx.h header file does not
+ * #define a macro for os_csr_busy. On many systems, this function has
+ * no effect at all, so the osxxx.h header file simply #define's it to
+ * do an empty macro.
+ */
+#ifndef os_csr_busy
+void os_csr_busy(int flag);
+#endif
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * User Input Routines
+ */
+
+/*
+ * Ask the user for a filename, using a system-dependent dialog or other
+ * mechanism. Returns one of the OS_AFE_xxx status codes (see below).
+ *
+ * prompt_type is the type of prompt to provide -- this is one of the
+ * OS_AFP_xxx codes (see below). The OS implementation doesn't need to
+ * pay any attention to this parameter, but it can be used if desired to
+ * determine the type of dialog to present if the system provides
+ * different types of dialogs for different types of operations.
+ *
+ * file_type is one of the OSFTxxx codes for system file type. The OS
+ * implementation is free to ignore this information, but can use it to
+ * filter the list of files displayed if desired; this can also be used
+ * to apply a default suffix on systems that use suffixes to indicate
+ * file type. If OSFTUNK is specified, it means that no filtering
+ * should be performed, and no default suffix should be applied.
+ */
+int os_askfile(const char *prompt, char *fname_buf, int fname_buf_len,
+ int prompt_type, os_filetype_t file_type);
+
+/*
+ * os_askfile status codes
+ */
+
+/* success */
+#define OS_AFE_SUCCESS 0
+
+/*
+ * Generic failure - this is largely provided for compatibility with
+ * past versions, in which only zero and non-zero error codes were
+ * meaningful; since TRUE is defined as 1 on most platforms, we assume
+ * that 1 is probably the generic non-zero error code that most OS
+ * implementations have traditionally used. In addition, this can be
+ * used to indicate any other error for which there is no more specific
+ * error code.
+ */
+#define OS_AFE_FAILURE 1
+
+/* user cancelled */
+#define OS_AFE_CANCEL 2
+
+/*
+ * os_askfile prompt types
+ *
+ * Important note: do not change these values when porting TADS. These
+ * values can be used by games, so they must be the same on all
+ * platforms.
+ */
+#define OS_AFP_OPEN 1 /* choose an existing file to open for reading */
+#define OS_AFP_SAVE 2 /* choose a filename for saving to a file */
+
+
+/*
+ * Read a string of input. Fills in the buffer with a null-terminated
+ * string containing a line of text read from the standard input. The
+ * returned string should NOT contain a trailing newline sequence. On
+ * success, returns 'buf'; on failure, including end of file, returns a
+ * null pointer.
+ */
+unsigned char *os_gets(unsigned char *buf, size_t bufl);
+
+/*
+ * Read a string of input with an optional timeout. This behaves like
+ * os_gets(), in that it allows the user to edit a line of text (ideally
+ * using the same editing keys that os_gets() does), showing the line of
+ * text under construction during editing. This routine differs from
+ * os_gets() in that it returns if the given timeout interval expires
+ * before the user presses Return (or the local equivalent).
+ *
+ * If the user presses Return before the timeout expires, we store the
+ * command line in the given buffer, just as os_gets() would, and we return
+ * OS_EVT_LINE. We also update the display in the same manner that
+ * os_gets() would, by moving the cursor to a new line and scrolling the
+ * displayed text as needed.
+ *
+ * If a timeout occurs before the user presses Return, we store the command
+ * line so far in the given buffer, statically store the cursor position,
+ * insert mode, buffer text, and anything else relevant to the editing
+ * state, and we return OS_EVT_TIMEOUT.
+ *
+ * If the implementation does not support the timeout operation, this
+ * routine should simply return OS_EVT_NOTIMEOUT immediately when called;
+ * the routine should not allow the user to perform any editing if the
+ * timeout is not supported. Callers must use the ordinary os_gets()
+ * routine, which has no timeout capabilities, if the timeout is not
+ * supported.
+ *
+ * When we return OS_EVT_TIMEOUT, the caller is responsible for doing one
+ * of two things.
+ *
+ * The first possibility is that the caller performs some work that doesn't
+ * require any display operations (in other words, the caller doesn't
+ * invoke os_printf, os_getc, or anything else that would update the
+ * display), and then calls os_gets_timeout() again. In this case, we will
+ * use the editing state that we statically stored before we returned
+ * OS_EVT_TIMEOUT to continue editing where we left off. This allows the
+ * caller to perform some computation in the middle of user command editing
+ * without interrupting the user - the extra computation is transparent to
+ * the user, because we act as though we were still in the midst of the
+ * original editing.
+ *
+ * The second possibility is that the caller wants to update the display.
+ * In this case, the caller must call os_gets_cancel() BEFORE making any
+ * display changes. Then, the caller must do any post-input work of its
+ * own, such as updating the display mode (for example, closing HTML font
+ * tags that were opened at the start of the input). The caller is now
+ * free to do any display work it wants.
+ *
+ * If we have information stored from a previous call that was interrupted
+ * by a timeout, and os_gets_cancel(TRUE) was never called, we will resume
+ * editing where we left off when the cancelled call returned; this means
+ * that we'll restore the cursor position, insertion state, and anything
+ * else relevant. Note that if os_gets_cancel(FALSE) was called, we must
+ * re-display the command line under construction, but if os_gets_cancel()
+ * was never called, we will not have to make any changes to the display at
+ * all.
+ *
+ * Note that when resuming an interrupted editing session (interrupted via
+ * os_gets_cancel()), the caller must re-display the prompt prior to
+ * invoking this routine.
+ *
+ * Note that we can return OS_EVT_EOF in addition to the other codes
+ * mentioned above. OS_EVT_EOF indicates that an error occurred reading,
+ * which usually indicates that the application is being terminated or that
+ * some hardware error occurred reading the keyboard.
+ *
+ * If 'use_timeout' is false, the timeout should be ignored. Without a
+ * timeout, the function behaves the same as os_gets(), except that it will
+ * resume editing of a previously-interrupted command line if appropriate.
+ * (This difference is why the timeout is optional: a caller might not need
+ * a timeout, but might still want to resume a previous input that did time
+ * out, in which case the caller would invoke this routine with
+ * use_timeout==FALSE. The regular os_gets() would not satisfy this need,
+ * because it cannot resume an interrupted input.)
+ *
+ * Note that a zero timeout has the same meaning as for os_get_event(): if
+ * input is available IMMEDIATELY, return the input, otherwise return
+ * immediately with the OS_EVT_TIMEOUT result code.
+ */
+int os_gets_timeout(unsigned char *buf, size_t bufl,
+ unsigned long timeout_in_milliseconds, int use_timeout);
+
+/*
+ * Cancel an interrupted editing session. This MUST be called if any
+ * output is to be displayed after a call to os_gets_timeout() returns
+ * OS_EVT_TIMEOUT.
+ *
+ * 'reset' indicates whether or not we will forget the input state saved
+ * by os_gets_timeout() when it last returned. If 'reset' is true, we'll
+ * clear the input state, so that the next call to os_gets_timeout() will
+ * start with an empty input buffer. If 'reset' is false, we will retain
+ * the previous input state, if any; this means that the next call to
+ * os_gets_timeout() will re-display the same input buffer that was under
+ * construction when it last returned.
+ *
+ * This routine need not be called if os_gets_timeout() is to be called
+ * again with no other output operations between the previous
+ * os_gets_timeout() call and the next one.
+ *
+ * Note that this routine needs only a trivial implementation when
+ * os_gets_timeout() is not supported (i.e., the function always returns
+ * OS_EVT_NOTIMEOUT).
+ */
+void os_gets_cancel(int reset);
+
+/*
+ * Read a character from the keyboard. For extended keystrokes, this
+ * function returns zero, and then returns the CMD_xxx code for the
+ * extended keystroke on the next call. For example, if the user presses
+ * the up-arrow key, the first call to os_getc() should return 0, and the
+ * next call should return CMD_UP. Refer to the CMD_xxx codes below.
+ *
+ * os_getc() should return a high-level, translated command code for
+ * command editing. This means that, where a functional interpretation of
+ * a key and the raw key-cap interpretation both exist as CMD_xxx codes,
+ * the functional interpretation should be returned. For example, on Unix,
+ * Ctrl-E is conventionally used in command editing to move to the end of
+ * the line, following Emacs key bindings. Hence, os_getc() should return
+ * CMD_END for this keystroke, rather than a single 0x05 character (ASCII
+ * Ctrl-E), because CMD_END is the high-level command code for the
+ * operation.
+ *
+ * The translation ability of this function allows for system-dependent key
+ * mappings to functional meanings.
+ */
+int os_getc(void);
+
+/*
+ * Read a character from the keyboard, following the same protocol as
+ * os_getc() for CMD_xxx codes (i.e., when an extended keystroke is
+ * encountered, os_getc_raw() returns zero, then returns the CMD_xxx code
+ * on the subsequent call).
+ *
+ * This function differs from os_getc() in that this function returns the
+ * low-level, untranslated key code whenever possible. This means that,
+ * when a functional interpretation of a key and the raw key-cap
+ * interpretation both exist as CMD_xxx codes, this function returns the
+ * key-cap interpretation. For the Unix Ctrl-E example in the comments
+ * describing os_getc() above, this function should return 5 (the ASCII
+ * code for Ctrl-E), because the ASCII control character interpretation is
+ * the low-level key code.
+ *
+ * This function should return all control keys using their ASCII control
+ * codes, whenever possible. Similarly, this function should return ASCII
+ * 27 for the Escape key, if possible.
+ *
+ * For keys for which there is no portable ASCII representation, this
+ * should return the CMD_xxx sequence. So, this function acts exactly the
+ * same as os_getc() for arrow keys, function keys, and other special keys
+ * that have no ASCII representation. This function returns a
+ * non-translated version ONLY when an ASCII representation exists - in
+ * practice, this means that this function and os_getc() vary only for CTRL
+ * keys and Escape.
+ */
+int os_getc_raw(void);
+
+
+/* wait for a character to become available from the keyboard */
+void os_waitc(void);
+
+/*
+ * Constants for os_getc() when returning commands. When used for
+ * command line editing, special keys (arrows, END, etc.) should cause
+ * os_getc() to return 0, and return the appropriate CMD_ value on the
+ * NEXT call. Hence, os_getc() must keep the appropriate information
+ * around statically for the next call when a command key is issued.
+ *
+ * The comments indicate which CMD_xxx codes are "translated" codes and
+ * which are "raw"; the difference is that, when a particular keystroke
+ * could be interpreted as two different CMD_xxx codes, one translated
+ * and the other raw, os_getc() should always return the translated
+ * version of the key, and os_getc_raw() should return the raw version.
+ */
+#define CMD_UP 1 /* move up/up arrow (translated) */
+#define CMD_DOWN 2 /* move down/down arrow (translated) */
+#define CMD_RIGHT 3 /* move right/right arrow (translated) */
+#define CMD_LEFT 4 /* move left/left arrow (translated) */
+#define CMD_END 5 /* move cursor to end of line (translated) */
+#define CMD_HOME 6 /* move cursor to start of line (translated) */
+#define CMD_DEOL 7 /* delete to end of line (translated) */
+#define CMD_KILL 8 /* delete entire line (translated) */
+#define CMD_DEL 9 /* delete current character (translated) */
+#define CMD_SCR 10 /* toggle scrollback mode (translated) */
+#define CMD_PGUP 11 /* page up (translated) */
+#define CMD_PGDN 12 /* page down (translated) */
+#define CMD_TOP 13 /* top of file (translated) */
+#define CMD_BOT 14 /* bottom of file (translated) */
+#define CMD_F1 15 /* function key F1 (raw) */
+#define CMD_F2 16 /* function key F2 (raw) */
+#define CMD_F3 17 /* function key F3 (raw) */
+#define CMD_F4 18 /* function key F4 (raw) */
+#define CMD_F5 19 /* function key F5 (raw) */
+#define CMD_F6 20 /* function key F6 (raw) */
+#define CMD_F7 21 /* function key F7 (raw) */
+#define CMD_F8 22 /* function key F8 (raw) */
+#define CMD_F9 23 /* function key F9 (raw) */
+#define CMD_F10 24 /* function key F10 (raw) */
+#define CMD_CHOME 25 /* control-home (raw) */
+#define CMD_TAB 26 /* tab (translated) */
+#define CMD_SF2 27 /* shift-F2 (raw) */
+/* not used (obsolete) - 28 */
+#define CMD_WORD_LEFT 29 /* word left (ctrl-left on dos) (translated) */
+#define CMD_WORD_RIGHT 30 /* word right (ctrl-right on dos) (translated) */
+#define CMD_WORDKILL 31 /* delete word right (translated) */
+#define CMD_EOF 32 /* end-of-file (raw) */
+#define CMD_BREAK 33 /* break (Ctrl-C or local equivalent) (translated) */
+#define CMD_INS 34 /* insert key (raw) */
+
+
+/*
+ * ALT-keys - add alphabetical code to CMD_ALT: ALT-A == CMD_ALT + 0,
+ * ALT-B == CMD_ALT + 1, ALT-C == CMD_ALT + 2, etc
+ *
+ * These keys are all raw (untranslated).
+ */
+#define CMD_ALT 128 /* start of ALT keys */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Event information structure for os_get_event. The appropriate union
+ * member should be filled in, depending on the type of event that
+ * occurs.
+ */
+union os_event_info_t
+{
+ /*
+ * OS_EVT_KEY - this returns the one or two characters of the
+ * keystroke. If the key is an extended key, so that os_getc() would
+ * return a two-character sequence for the keystroke, the first
+ * character should be zero and the second the extended key code.
+ * Otherwise, the first character should simply be the ASCII key code.
+ *
+ * The key code here is the "raw" keycode, equivalent to the codes
+ * returned by os_getc_raw(). Note in particular that this means that
+ * CTRL and Escape keys are presented as one-byte ASCII control
+ * characters, not as two-byte CMD_xxx sequences.
+ *
+ * For multi-byte character sets (Shift-JIS, for example), note that
+ * os_get_event() must NOT return a complete two-byte character here.
+ * The two bytes here are exclusively used to represent special
+ * CMD_xxx keys (such as arrow keys and function keys). For a
+ * keystroke that is represented in a multi-byte character set using
+ * more than one byte, os_get_event() must return a series of events.
+ * Return an ordinary OS_EVT_KEY for the first byte of the sequence,
+ * putting the byte in key[0]; then, return the second byte as a
+ * separate OS_EVT_KEY as the next event; and so on for any additional
+ * bytes. This will allow callers that are not multibyte-aware to
+ * treat multi-byte characters as though they were sequences of
+ * one-byte characters.
+ */
+ int key[2];
+
+ /*
+ * OS_EVT_HREF - this returns the text of the HREF as a
+ * null-terminated string.
+ */
+ char href[256];
+
+ /* command ID (for OS_EVT_COMMAND) */
+ int cmd_id;
+};
+typedef union os_event_info_t os_event_info_t;
+
+/*
+ * Event types for os_get_event
+ */
+
+/* invalid/no event */
+#define OS_EVT_NONE 0x0000
+
+/* OS_EVT_KEY - user typed a key on the keyboard */
+#define OS_EVT_KEY 0x0001
+
+/* OS_EVT_TIMEOUT - no event occurred before the timeout elapsed */
+#define OS_EVT_TIMEOUT 0x0002
+
+/*
+ * OS_EVT_HREF - user clicked on a <A HREF> link. This only applies to
+ * the HTML-enabled run-time.
+ */
+#define OS_EVT_HREF 0x0003
+
+/*
+ * OS_EVT_NOTIMEOUT - caller requested a timeout, but timeout is not
+ * supported by this version of the run-time
+ */
+#define OS_EVT_NOTIMEOUT 0x0004
+
+/*
+ * OS_EVT_EOF - an error occurred reading the event. This generally
+ * means that the application is quitting or we can no longer read from
+ * the keyboard or terminal.
+ */
+#define OS_EVT_EOF 0x0005
+
+/*
+ * OS_EVT_LINE - user entered a line of text on the keyboard. This event
+ * is not returned from os_get_event(), but rather from os_gets_timeout().
+ */
+#define OS_EVT_LINE 0x0006
+
+
+/*
+ * Get an input event. The event types are shown above. If use_timeout is
+ * false, this routine should simply wait until one of the events it
+ * recognizes occurs, then return the appropriate information on the event.
+ * If use_timeout is true, this routine should return OS_EVT_TIMEOUT after
+ * the given number of milliseconds elapses if no event occurs first.
+ *
+ * This function is not obligated to obey the timeout. If a timeout is
+ * specified and it is not possible to obey the timeout, the function
+ * should simply return OS_EVT_NOTIMEOUT. The trivial implementation thus
+ * checks for a timeout, returns an error if specified, and otherwise
+ * simply waits for the user to press a key.
+ *
+ * A timeout value of 0 does *not* mean that there's no timeout (i.e., it
+ * doesn't mean we should wait indefinitely) - that's specified by passing
+ * FALSE for use_timeout. A zero timeout also doesn't meant that the
+ * function should unconditionally return OS_EVT_TIMEOUT. Instead, a zero
+ * timeout specifically means that IF an event is available IMMEDIATELY,
+ * without blocking the thread, we should return that event; otherwise we
+ * should immediately return a timeout event.
+ */
+int os_get_event(unsigned long timeout_in_milliseconds, int use_timeout,
+ os_event_info_t *info);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Extended os_get_event() codes.
+ *
+ * THESE ARE NOT USED in the basic osifc implementation - these are only
+ * used if the interpreter supports the "extended" interface defined in
+ * osifcext.h.
+ */
+
+/*
+ * System menu command event. Some interpreters (such as HTML TADS for
+ * Windows) provide menu commands for common system-level game verbs -
+ * SAVE, RESTORE, UNDO, QUIT. By default, these commands are returned as
+ * literal command strings ("save", "restore", etc) via os_gets(), as
+ * though the user had typed the commands at the keyboard. The extended OS
+ * interface allows the program to receive these commands via
+ * os_get_event(). When a command is enabled for os_get_event() via the
+ * extended OS interface, and the player selects the command via a menu (or
+ * toolbar button, etc) during a call to os_get_event(), os_get_event()
+ * returns this event code, with the menu ID stored in the cmd_id field of
+ * the event structure.
+ */
+#define OS_EVT_COMMAND 0x0100
+
+/* command IDs for OS_EVT_COMMAND */
+#define OS_CMD_NONE 0x0000 /* invalid command ID, for internal use */
+#define OS_CMD_SAVE 0x0001 /* save game */
+#define OS_CMD_RESTORE 0x0002 /* restore game */
+#define OS_CMD_UNDO 0x0003 /* undo last turn */
+#define OS_CMD_QUIT 0x0004 /* quit game */
+#define OS_CMD_CLOSE 0x0005 /* close the game window */
+#define OS_CMD_HELP 0x0006 /* show game help */
+
+/* highest command ID used in this version of the interface */
+#define OS_CMD_LAST 0x0006
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Ask for input through a dialog.
+ *
+ * 'prompt' is a text string to display as a prompting message. For
+ * graphical systems, this message should be displayed in the dialog;
+ * for text systems, this should be displayed on the terminal after a
+ * newline.
+ *
+ * 'standard_button_set' is one of the OS_INDLG_xxx values defined
+ * below, or zero. If this value is zero, no standard button set is to
+ * be used; the custom set of buttons defined in 'buttons' is to be used
+ * instead. If this value is non-zero, the appropriate set of standard
+ * buttons, with labels translated to the local language if possible, is
+ * to be used.
+ *
+ * 'buttons' is an array of strings to use as button labels.
+ * 'button_count' gives the number of entries in the 'buttons' array.
+ * 'buttons' and 'button_count' are ignored if 'standard_button_set' is
+ * non-zero, since a standard set of buttons is used instead. If
+ * 'buttons' and 'button_count' are to be used, each entry contains the
+ * label of a button to show.
+ */
+/*
+ * An ampersand ('&') character in a label string indicates that the
+ * next character after the '&' is to be used as the short-cut key for
+ * the button, if supported. The '&' should NOT be displayed in the
+ * string; instead, the character should be highlighted according to
+ * local system conventions. On Windows, for example, the short-cut
+ * character should be shown underlined; on a text display, the response
+ * might be shown with the short-cut character enclosed in parentheses.
+ * If there is no local convention for displaying a short-cut character,
+ * then the '&' should simply be removed from the displayed text.
+ *
+ * The short-cut key specified by each '&' character should be used in
+ * processing responses. If the user presses the key corresponding to a
+ * button's short-cut, the effect should be the same as if the user
+ * clicked the button with the mouse. If local system conventions don't
+ * allow for short-cut keys, any short-cut keys can be ignored.
+ *
+ * 'default_index' is the 1-based index of the button to use as the
+ * default. If this value is zero, there is no default response. If
+ * the user performs the appropriate system-specific action to select
+ * the default response for the dialog, this is the response that is to
+ * be selected. On Windows, for example, pressing the "Return" key
+ * should select this item.
+ *
+ * 'cancel_index' is the 1-based index of the button to use as the
+ * cancel response. If this value is zero, there is no cancel response.
+ * This is the response to be used if the user cancels the dialog using
+ * the appropriate system-specific action. On Windows, for example,
+ * pressing the "Escape" key should select this item.
+ */
+/*
+ * icon_id is one of the OS_INDLG_ICON_xxx values defined below. If
+ * possible, an appropriate icon should be displayed in the dialog.
+ * This can be ignored in text mode, and also in GUI mode if there is no
+ * appropriate system icon.
+ *
+ * The return value is the 1-based index of the response selected. If
+ * an error occurs, return 0.
+ */
+int os_input_dialog(int icon_id, const char *prompt, int standard_button_set,
+ const char **buttons, int button_count,
+ int default_index, int cancel_index);
+
+/*
+ * Standard button set ID's
+ */
+
+/* OK */
+#define OS_INDLG_OK 1
+
+/* OK, Cancel */
+#define OS_INDLG_OKCANCEL 2
+
+/* Yes, No */
+#define OS_INDLG_YESNO 3
+
+/* Yes, No, Cancel */
+#define OS_INDLG_YESNOCANCEL 4
+
+/*
+ * Dialog icons
+ */
+
+/* no icon */
+#define OS_INDLG_ICON_NONE 0
+
+/* warning */
+#define OS_INDLG_ICON_WARNING 1
+
+/* information */
+#define OS_INDLG_ICON_INFO 2
+
+/* question */
+#define OS_INDLG_ICON_QUESTION 3
+
+/* error */
+#define OS_INDLG_ICON_ERROR 4
+
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * OS main entrypoint
+ */
+int os0main(int oargc, char **oargv,
+ int (*mainfn)(int, char **, char *),
+ const char *before, const char *config);
+
+/*
+ * new-style OS main entrypoint - takes an application container context
+ */
+int os0main2(int oargc, char **oargv,
+ int (*mainfn)(int, char **, struct appctxdef *, char *),
+ const char *before, const char *config,
+ struct appctxdef *appctx);
+
+/*
+ * OBSOLETE - Get filename from startup parameter, if possible; returns
+ * true and fills in the buffer with the parameter filename on success,
+ * false if no parameter file could be found.
+ *
+ * (This was used until TADS 2.2.5 for the benefit of the Mac interpreter,
+ * and interpreters on systems with similar desktop shells, to allow the
+ * user to launch the terp by double-clicking on a saved game file. The
+ * terp would read the launch parameters, discover that a saved game had
+ * been used to invoke it, and would then stash away the saved game info
+ * for later retrieval from this function. This functionality was replaced
+ * in 2.2.5 with a command-line parameter: the terp now uses the desktop
+ * launch data to synthesize a suitable argv[] vectro to pass to os0main()
+ * or os0main2(). This function should now simply be stubbed out - it
+ * should simply return FALSE.)
+ */
+int os_paramfile(char *buf);
+
+/*
+ * Initialize. This should be called during program startup to
+ * initialize the OS layer and check OS-specific command-line arguments.
+ *
+ * If 'prompt' and 'buf' are non-null, and there are no arguments on the
+ * given command line, the OS code can use the prompt to ask the user to
+ * supply a filename, then store the filename in 'buf' and set up
+ * argc/argv to give a one-argument command string. (This mechanism for
+ * prompting for a filename is obsolescent, and is retained for
+ * compatibility with a small number of existing implementations only;
+ * new implementations should ignore this mechanism and leave the
+ * argc/argv values unchanged.)
+ */
+int os_init(int *argc, char *argv[], const char *prompt,
+ char *buf, int bufsiz);
+
+/*
+ * Termination functions. There are three main termination functions,
+ * described individually below; here's a brief overview of the
+ * relationship among the functions. The important thing to realize is
+ * that these functions have completely independent purposes; they should
+ * never call one another, and they should never do any of the work that's
+ * intended for the others.
+ *
+ * os_uninit() is meant to undo the effects of os_init(). On many
+ * systems, os_init() has some global effect, such as setting the terminal
+ * to some special input or output mode. os_uninit's purpose is to undo
+ * these global effects, returning the terminal mode (and whatever else)
+ * to the conditions they were in at program startup. Portable callers
+ * are meant to call this routine at some point before terminating if they
+ * ever called os_init(). Note that this routine DOES NOT terminate the
+ * program - it should simply undo anything that os_init() did and return,
+ * to let the caller do any additional termination work of its own.
+ *
+ * os_expause() optionally pauses before termination, to allow the user to
+ * acknowledge any text the program displays just before exiting. This
+ * doesn't have to do anything at all, but it's useful on systems where
+ * program termination will do something drastic like close the window:
+ * without a pause, the user wouldn't have a chance to read any text the
+ * program displayed after the last interactive input, because the window
+ * would abruptly disappear moments after the text was displayed. For
+ * systems where termination doesn't have such drastic effects, there's no
+ * need to do anything in this routine. Because it's up to this routine
+ * whether or not to pause, this routine must display a prompt if it's
+ * going to pause for user input - the portable caller obviously can't do
+ * so, because the caller doesn't know if the routine is going to pause or
+ * not (so if the caller displayed a prompt assuming the routine would
+ * pause, the prompt would show up even on systems where there actually is
+ * no pause, which would be confusing). This routine DOES NOT terminate
+ * the program; it simply pauses if necessary to allow the user to
+ * acknowledge the last bit of text the program displayed, then returns to
+ * allow the caller to carry on with its own termination work.
+ *
+ * os_term() is meant to perform the same function as the C standard
+ * library routine exit(): this actually terminates the program, exiting
+ * to the operating system. This routine is not meant to return to its
+ * caller, because it's supposed to exit the program directly. For many
+ * systems, this routine can simply call exit(); the portable code calls
+ * this routine instead of calling exit() directly, because some systems
+ * have their own OS-specific way of terminating rather than using exit().
+ * This routine MUST NOT undo the effects of os_init(), because callers
+ * might not have ever called os_init(); callers are required to call
+ * os_uninit() if they ever called os_init(), before calling os_term(), so
+ * this routine can simply assume that any global modes set by os_init()
+ * have already been undone by the time this is called.
+ */
+
+/*
+ * Uninitialize. This is called prior to progam termination to reverse
+ * the effect of any changes made in os_init(). For example, if
+ * os_init() put the terminal in raw mode, this should restore the
+ * previous terminal mode. This routine should not terminate the
+ * program (so don't call exit() here) - the caller might have more
+ * processing to perform after this routine returns.
+ */
+void os_uninit(void);
+
+/*
+ * Pause prior to exit, if desired. This is meant to be called by
+ * portable code just before the program is to be terminated; it can be
+ * implemented to show a prompt and wait for user acknowledgment before
+ * proceeding. This is useful for implementations that are using
+ * something like a character-mode terminal window running on a graphical
+ * operating system: this gives the implementation a chance to pause
+ * before exiting, so that the window doesn't just disappear
+ * unceremoniously.
+ *
+ * This is allowed to do nothing at all. For regular character-mode
+ * systems, this routine usually doesn't do anything, because when the
+ * program exits, the terminal will simply return to the OS command
+ * prompt; none of the text displayed just before the program exited will
+ * be lost, so there's no need for any interactive pause. Likewise, for
+ * graphical systems where the window will remain open, even after the
+ * program exits, until the user explicitly closes the window, there's no
+ * need to do anything here.
+ *
+ * If this is implemented to pause, then this routine MUST show some kind
+ * of prompt to let the user know we're waiting. In the simple case of a
+ * text-mode terminal window on a graphical OS, this should simply print
+ * out some prompt text ("Press a key to exit...") and then wait for the
+ * user to acknowledge the prompt (by pressing a key, for example). For
+ * graphical systems, the prompt could be placed in the window's title
+ * bar, or status-bar, or wherever is appropriate for the OS.
+ */
+void os_expause(void);
+
+/*
+ * Terminate. This should exit the program with the given exit status.
+ * In general, this should be equivalent to the standard C library
+ * exit() function, but we define this interface to allow the OS code to
+ * do any necessary pre-termination cleanup.
+ */
+void os_term(int status);
+
+/*
+ * Install/uninstall the break handler. If possible, the OS code should
+ * set (if 'install' is true) or clear (if 'install' is false) a signal
+ * handler for keyboard break signals (control-C, etc, depending on
+ * local convention). The OS code should set its own handler routine,
+ * which should note that a break occurred with an internal flag; the
+ * portable code uses os_break() from time to time to poll this flag.
+ */
+void os_instbrk(int install);
+
+/*
+ * Check for user break ("control-C", etc) - returns true if a break is
+ * pending, false if not. If this returns true, it should "consume" the
+ * pending break (probably by simply clearing the OS code's internal
+ * break-pending flag).
+ */
+int os_break(void);
+
+/*
+ * Sleep for a given interval. This should simply pause for the given
+ * number of milliseconds, then return. On multi-tasking systems, this
+ * should use a system API to suspend the current process for the desired
+ * delay; on single-tasking systems, this can simply sit in a wait loop
+ * until the desired interval has elapsed.
+ */
+void os_sleep_ms(long delay_in_milliseconds);
+
+/*
+ * Yield CPU; returns TRUE if user requested an interrupt (a "control-C"
+ * type of operation to abort the entire program), FALSE to continue.
+ * Portable code should call this routine from time to time during lengthy
+ * computations that don't involve any UI operations; if practical, this
+ * routine should be invoked roughly every 10 to 100 milliseconds.
+ *
+ * The purpose of this routine is to support "cooperative multitasking"
+ * systems, such as pre-X MacOS, where it's necessary for each running
+ * program to call the operating system explicitly in order to yield the
+ * CPU from time to time to other concurrently running programs. On
+ * cooperative multitasking systems, a program can only lose control of
+ * the CPU by making specific system calls, usually related to GUI events;
+ * a program can never lose control of the CPU asynchronously. So, a
+ * program that performs lengthy computations without any UI interaction
+ * can cause the rest of the system to freeze up until the computations
+ * are finished; but if a compute-intensive program explicitly yields the
+ * CPU from time to time, it allows other programs to remain responsive.
+ * Yielding the CPU at least every 100 milliseconds or so will generally
+ * allow the UI to remain responsive; yielding more frequently than every
+ * 10 ms or so will probably start adding noticeable overhead.
+ *
+ * On single-tasking systems (such as MS-DOS), there's only one program
+ * running at a time, so there's no need to yield the CPU; on virtually
+ * every modern system, the OS automatically schedules CPU time without
+ * the running programs having any say in the matter, so again there's no
+ * need for a program to yield the CPU. For systems where this routine
+ * isn't needed, the system header should simply #define os_yield to
+ * something like "((void)0)" - this will allow the compiler to completely
+ * ignore calls to this routine for systems where they aren't needed.
+ *
+ * Note that this routine is NOT meant to provide scheduling hinting to
+ * modern systems with true multitasking, so a trivial implementation is
+ * fine for any modern system.
+ */
+#ifndef os_yield
+int os_yield(void);
+#endif
+
+/*
+ * Set the default saved-game extension. This routine will NOT be called
+ * when we're using the standard saved game extension; this routine will be
+ * invoked only if we're running as a stand-alone game, and the game author
+ * specified a non-standard saved-game extension when creating the
+ * stand-alone game.
+ *
+ * This routine is not required if the system does not use the standard,
+ * semi-portable os0.c implementation. Even if the system uses the
+ * standard os0.c implementation, it can provide an empty routine here if
+ * the system code doesn't need to do anything special with this
+ * information.
+ *
+ * The extension is specified as a null-terminated string. The extension
+ * does NOT include the leading period.
+ */
+void os_set_save_ext(const char *ext);
+
+/*
+ * Get the saved game extension previously set with os_set_save_ext().
+ * Returns null if no custom extension has been set.
+ */
+const char *os_get_save_ext();
+
+
+/* ------------------------------------------------------------------------*/
+/*
+ * Translate a character from the HTML 4 Unicode character set to the
+ * current character set used for display. Takes an HTML 4 character
+ * code and returns the appropriate local character code.
+ *
+ * The result buffer should be filled in with a null-terminated string
+ * that should be used to represent the character. Multi-character
+ * results are possible, which may be useful for certain approximations
+ * (such as using "(c)" for the copyright symbol).
+ *
+ * Note that we only define this prototype if this symbol isn't already
+ * defined as a macro, which may be the case on some platforms.
+ * Alternatively, if the function is already defined (for example, as an
+ * inline function), the defining code can define OS_XLAT_HTML4_DEFINED,
+ * in which case we'll also omit this prototype.
+ *
+ * Important: this routine provides the *default* mapping that is used
+ * when no external character mapping file is present, and for any named
+ * entities not defined in the mapping file. Any entities in the
+ * mapping file, if used, will override this routine.
+ *
+ * A trivial implementation of this routine (that simply returns a
+ * one-character result consisting of the original input character,
+ * truncated to eight bits if necessary) can be used if you want to
+ * require an external mapping file to be used for any game that
+ * includes HTML character entities. The DOS version implements this
+ * routine so that games will still look reasonable when played with no
+ * mapping file present, but other systems are not required to do this.
+ */
+#ifndef os_xlat_html4
+# ifndef OS_XLAT_HTML4_DEFINED
+void os_xlat_html4(unsigned int html4_char,
+ char *result, size_t result_buf_len);
+# endif
+#endif
+
+/*
+ * Generate a filename for a character-set mapping file. This function
+ * should determine the current native character set in use, if
+ * possible, then generate a filename, according to system-specific
+ * conventions, that we should attempt to load to get a mapping between
+ * the current native character set and the internal character set
+ * identified by 'internal_id'.
+ *
+ * The internal character set ID is a string of up to 4 characters.
+ *
+ * On DOS, the native character set is a DOS code page. DOS code pages
+ * are identified by 3- or 4-digit identifiers; for example, code page
+ * 437 is the default US ASCII DOS code page. We generate the
+ * character-set mapping filename by appending the internal character
+ * set identifier to the DOS code page number, then appending ".TCP" to
+ * the result. So, to map between ISO Latin-1 (internal ID = "La1") and
+ * DOS code page 437, we would generate the filename "437La1.TCP".
+ *
+ * Note that this function should do only two things. First, determine
+ * the current native character set that's in use. Second, generate a
+ * filename based on the current native code page and the internal ID.
+ * This function is NOT responsible for figuring out the mapping or
+ * anything like that -- it's simply where we generate the correct
+ * filename based on local convention.
+ *
+ * 'filename' is a buffer of at least OSFNMAX characters.
+ *
+ * 'argv0' is the executable filename from the original command line.
+ * This parameter is provided so that the system code can look for
+ * mapping files in the original TADS executables directory, if desired.
+ */
+void os_gen_charmap_filename(char *filename, char *internal_id,
+ char *argv0);
+
+/*
+ * Receive notification that a character mapping file has been loaded.
+ * The caller doesn't require this routine to do anything at all; this
+ * is purely for the system-dependent code's use so that it can take
+ * care of any initialization that it must do after the caller has
+ * loaded a charater mapping file. 'id' is the character set ID, and
+ * 'ldesc' is the display name of the character set. 'sysinfo' is the
+ * extra system information string that is stored in the mapping file;
+ * the interpretation of this information is up to this routine.
+ *
+ * For reference, the Windows version uses the extra information as a
+ * code page identifier, and chooses its default font character set to
+ * match the code page. On DOS, the run-time requires the player to
+ * activate an appropriate code page using a DOS command (MODE CON CP
+ * SELECT) prior to starting the run-time, so this routine doesn't do
+ * anything at all on DOS.
+ */
+void os_advise_load_charmap(const char *id, const char *ldesc, const char *sysinfo);
+
+/*
+ * Generate the name of the character set mapping table for Unicode
+ * characters to and from the given local character set. Fills in the
+ * buffer with the implementation-dependent name of the desired
+ * character set map. See below for the character set ID codes.
+ *
+ * For example, on Windows, the implementation would obtain the
+ * appropriate active code page (which is simply a Windows character set
+ * identifier number) from the operating system, and build the name of
+ * the Unicode mapping file for that code page, such as "CP1252". On
+ * Macintosh, the implementation would look up the current script system
+ * and return the name of the Unicode mapping for that script system,
+ * such as "ROMAN" or "CENTEURO".
+ *
+ * If it is not possible to determine the specific character set that is
+ * in use, this function should return "asc7dflt" (ASCII 7-bit default)
+ * as the character set identifier on an ASCII system, or an appropriate
+ * base character set name on a non-ASCII system. "asc7dflt" is the
+ * generic character set mapping for plain ASCII characters.
+ *
+ * The given buffer must be at least 32 bytes long; the implementation
+ * must limit the result it stores to 32 bytes. (We use a fixed-size
+ * buffer in this interface for simplicity, and because there seems no
+ * need for greater flexibility in the interface; a character set name
+ * doesn't carry very much information so shouldn't need to be very
+ * long. Note that this function doesn't generate a filename, but
+ * simply a mapping name; in practice, a map name will be used to
+ * construct a mapping file name.)
+ *
+ * Because this function obtains the Unicode mapping name, there is no
+ * need to specify the internal character set to be used: the internal
+ * character set is Unicode.
+ */
+/*
+ * Implementation note: when porting this routine, the convention that
+ * you use to name your mapping files is up to you. You should simply
+ * choose a convention for this implementation, and then use the same
+ * convention for packaging the mapping files for your OS release. In
+ * most cases, the best convention is to use the names that the Unicode
+ * consortium uses in their published cross-mapping listings, since
+ * these listings can be used as the basis of the mapping files that you
+ * include with your release. For example, on Windows, the convention
+ * is to use the code page number to construct the map name, as in
+ * CP1252 or CP1250.
+ */
+void os_get_charmap(char *mapname, int charmap_id);
+
+/*
+ * Character map for the display (i.e., for the user interface). This
+ * is the character set which is used for input read from the keyboard,
+ * and for output displayed on the monitor or terminal.
+ */
+#define OS_CHARMAP_DISPLAY 1
+
+/*
+ * Character map for mapping filename strings. This should identify the
+ * character set currently in use on the local system for filenames
+ * (i.e., for strings identifying objects in the local file system),
+ * providing a suitable name for use in opening a mapping file.
+ *
+ * On many platforms, the entire system uses only one character set at
+ * the OS level, so the file system character set is the same as the
+ * display character set. Some systems define a particular character
+ * set for file system use, though, because different users might be
+ * running applications on terminals that display different character
+ * sets.
+ */
+#define OS_CHARMAP_FILENAME 2
+
+/*
+ * Default character map for file contents. On most systems, this will
+ * be the same as display. On some systems, it won't be possible to
+ * know in general what character set files use; in fact, this isn't
+ * possible anywhere, since a file might have been copied without
+ * translation from a different type of computer using a different
+ * character set. So, this isn't meant to provide a reliable choice of
+ * character set for any arbitrary file; it's simply meant to be a good
+ * guess that most files on this system are likely to use.
+ */
+#define OS_CHARMAP_FILECONTENTS 3
+
+/*
+ * Default character map for the command line. This is the maping we use
+ * to interpret command line arguments passed to our main() or equivalent.
+ * On most systems, this will be the same as the display character set.
+ */
+#define OS_CHARMAP_CMDLINE 4
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * External Banner Interface. This interface provides the ability to
+ * divide the display window into multiple sub-windows, each with its own
+ * independent contents.
+ *
+ * To determine where a new banner is displayed, we look at the banners as
+ * a tree, rooted at the "main window," the special banner that the system
+ * automatically creates initially for the main game text. We start by
+ * allocating the entire display (or the entire application window, if
+ * we're running on a GUI system) to the main window. We then traverse
+ * the tree, starting with the root window's children. For each child
+ * window, we allocate space for the child out of the parent window's
+ * area, according to the child's alignment and size settings, and deduct
+ * this space from the parent window's size. We then lay out the children
+ * of the child.
+ *
+ * For each banner window, we take its requested space out of the parent
+ * window's area by starting at the edge of the parent window rectangle as
+ * indicated by the banner's alignment, and taking the requested `width
+ * (for a left/right banner) or height (for a top/bottom banner), limiting
+ * to the available width/height in the parent window's space. Give the
+ * banner the full extent of the parent's space in its other dimension (so
+ * a left/right banner gets the full height of the parent space, and a
+ * top/bottom banner gets the full width).
+ *
+ * Note that the layout proceeds exclusively down the tree (i.e., from the
+ * root to children to grandchildren, and so on). It *appears* that a
+ * child affects its parent, because of the deduction step: a child
+ * acquires screen space by carving out a chunk of its parent. The right
+ * way to think about this, though, is that the parent's full area is the
+ * union of the parent window and all of its children; when viewed this
+ * way, the parent's full area is fully determined the instant the parent
+ * is laid out, and never changes as its children are laid out. Note in
+ * particular that a child can never make a parent larger; the only thing
+ * a child can do to a parent is carve out a chunk of the parent for
+ * itself, which doesn't affect the boundaries of the union of the parent
+ * plus its children.
+ *
+ * Note also that if the banner has a border, and the implementation
+ * actually draws borders, the border must be drawn for the *full* area of
+ * the banner, as defined above. For example, suppose we have two
+ * borders: banner A is a child of the main window, is top-aligned, and
+ * has a border. Banner B is a child of banner A, right-aligned, with no
+ * border. Obviously, without considering banner B, banner A's space runs
+ * across the entire width of the main window, so its border (at the
+ * bottom of its area) runs across the entire width of the main window.
+ * Banner B carves out some space from A's right side for itself, so
+ * banner A's actual on-screen area runs from the left edge of the main
+ * window to banner B's left edge. However, even though banner A itself
+ * no longer runs the full width of the main window, banner A's *full*
+ * area - that is, the union of banner A's on-screen area and all of its
+ * children's full areas - does still run the entire width of the main
+ * window, hence banner A's border must still run the full width of the
+ * main window. The simple way of looking at this is that a banner's
+ * border is always to be drawn exactly the same way, regardless of
+ * whether or not the banner has children - simply draw the banner as it
+ * would be drawn if the banner had no children.
+ *
+ * Each time a banner is added or removed, we must recalculate the layout
+ * of the remaining banners and main text area. The os_banner_xxx()
+ * implementation is responsible for this layout refiguring.
+ *
+ * The entire external banner window interface is optional, although the
+ * functions must at least be defined as dummies to avoid linker errors
+ * when building. If a platform doesn't implement this feature,
+ * os_banner_create() should simply return null, and the other routines
+ * can do nothing.
+ */
+
+/*
+ * Create a banner window. 'info' gives the desired parameters for the new
+ * banner.
+ *
+ * Note that certain requested parameter settings might or might not be
+ * respected, depending on the capabilities of the platform and user
+ * preferences. os_banner_getinfo() can be used after creation to
+ * determine which parameter settings are actually used in the new banner.
+ *
+ * 'parent' gives the parent of this banner; this is the banner handle of
+ * another banner window, or null. If 'parent' is null, then the new
+ * banner is a child of the main window, which the system creates
+ * automatically at startup and which contains the main input/output
+ * transcript. The new banner's on-screen area is carved out of the
+ * parent's space, according to the alignment and size settings of the new
+ * window, so this determines how the window is laid out on the screen.
+ *
+ * 'where' is OS_BANNER_FIRST to make the new window the first child of its
+ * parent; OS_BANNER_LAST to make it the last child of its parent;
+ * OS_BANNER_BEFORE to insert it immediately before the existing banner
+ * identified by handle in 'other'; or OS_BANNER_AFTER to insert
+ * immediately after 'other'. When BEFORE or AFTER is used, 'other' must
+ * be another child of the same parent; if it is not, the routine should
+ * act as though 'where' were given as OS_BANNER_LAST.
+ *
+ * 'other' is a banner handle for an existing banner window. This is used
+ * to specify the relative position among children of the new banner's
+ * parent, if 'where' is either OS_BANNER_BEFORE or OS_BANNER_AFTER. If
+ * 'where' is OS_BANNER_FIRST or OS_BANNER_LAST, 'other' is ignored.
+ *
+ * 'wintype' is the type of the window. This is one of the
+ * OS_BANNER_TYPE_xxx codes indicating what kind of window is desired.
+ *
+ * 'align' is the banner's alignment, given as an OS_BANNER_ALIGN_xxx
+ * value. Top/bottom banners are horizontal: they run across the full
+ * width of the existing main text area. Left/right banners are vertical:
+ * they run down the full height of the existing main text area.
+ *
+ * 'siz' is the requested size of the new banner. The meaning of 'siz'
+ * depends on the value of 'siz_units', which can be OS_BANNER_SIZE_PCT to
+ * set the size as a percentage of the REMAINING space, or
+ * OS_BANNER_SIZE_ABS to set an absolute size in the "natural" units of the
+ * window. The natural units vary by window type: for text and text grid
+ * windows, this is in rows/columns of '0' characters in the default font
+ * for the window. Note that when OS_BANNER_SIZE_ABS is used in a text or
+ * text grid window, the OS implementation MUST add the space needed for
+ * margins and borders when determining the actual pixel size of the
+ * window; in other words, the window should be large enough that it can
+ * actually display the given number or rows or columns.
+ *
+ * The size is interpreted as a width or height according to the window's
+ * orientation. For a TOP or BOTTOM banner, the size is the height; for a
+ * LEFT or RIGHT banner, the size is the width. A banner has only one
+ * dimension's size given, since the other dimension's size is determined
+ * automatically by the layout rules.
+ *
+ * Note that the window's size can be changed later using
+ * banner_size_to_contents() or banner_set_size().
+ *
+ * 'style' is a combination of OS_BANNER_STYLE_xxx flags - see below. The
+ * style flags give the REQUESTED style for the banner, which might or
+ * might not be respected, depending on the platform's capabilities, user
+ * preferences, and other factors. os_banner_getinfo() can be used to
+ * determine which style flags are actually used.
+ *
+ * Returns the "handle" to the new banner window, which is an opaque value
+ * that is used in subsequent os_banner_xxx calls to operate on the window.
+ * Returns null if the window cannot be created. An implementation is not
+ * required to support this functionality at all, and can subset it if it
+ * does support it (for example, an implementation could support only
+ * top/bottom-aligned banners, but not left/right-aligned), so callers must
+ * be prepared for this routine to return null.
+ */
+void *os_banner_create(void *parent, int where, void *other, int wintype,
+ int align, int siz, int siz_units,
+ unsigned long style);
+
+
+/*
+ * insertion positions
+ */
+#define OS_BANNER_FIRST 1
+#define OS_BANNER_LAST 2
+#define OS_BANNER_BEFORE 3
+#define OS_BANNER_AFTER 4
+
+/*
+ * banner types
+ */
+
+/*
+ * Normal text stream window. This is a text stream that behaves
+ * essentially like the main text window: text is displayed to this
+ * through os_banner_disp(), always in a stream-like fashion by adding new
+ * text to the end of any exiting text.
+ *
+ * Systems that use proportional fonts should usually simply use the same
+ * font they use by default in the main text window. However, note that
+ * the OS_BANNER_STYLE_TAB_ALIGN style flag might imply that a fixed-pitch
+ * font should be used even when proportional fonts are available, because
+ * a fixed-pitch font will allow the calling code to rely on using spaces
+ * to align text within the window.
+ */
+#define OS_BANNER_TYPE_TEXT 1
+
+/*
+ * "Text grid" window. This type of window is similar to an normal text
+ * window (OS_BANNER_TYPE_TEXT), but is guaranteed to arrange its text in
+ * a regular grid of character cells, all of the same size. This means
+ * that the output position can be moved to an arbitrary point within the
+ * window at any time, so the calling program can precisely control the
+ * layout of the text in the window.
+ *
+ * Because the output position can be moved to arbitrary positions in the
+ * window, it is possible to overwrite text previously displayed. When
+ * this happens, the old text is completely obliterated by the new text,
+ * leaving no trace of the overwritten text.
+ *
+ * In order to guarantee that character cells are all the same size, this
+ * type of window does not allow any text attributes. The implementation
+ * should simply ignore any attempts to change text attributes in this
+ * type of window. However, colors can be used to the same degree they
+ * can be used in an ordinary text window.
+ *
+ * To guarantee the regular spacing of character cells, all
+ * implementations must use fixed-pitch fonts for these windows. This
+ * applies even to platforms where proportional fonts are available.
+ */
+#define OS_BANNER_TYPE_TEXTGRID 2
+
+
+/*
+ * banner alignment types
+ */
+#define OS_BANNER_ALIGN_TOP 0
+#define OS_BANNER_ALIGN_BOTTOM 1
+#define OS_BANNER_ALIGN_LEFT 2
+#define OS_BANNER_ALIGN_RIGHT 3
+
+/*
+ * size units
+ */
+#define OS_BANNER_SIZE_PCT 1
+#define OS_BANNER_SIZE_ABS 2
+
+
+/*
+ * banner style flags
+ */
+
+/*
+ * The banner has a visible border; this indicates that a line is to be
+ * drawn to separate the banner from the adjacent window or windows
+ * "inside" the banner. So, a top-aligned banner will have its border
+ * drawn along its bottom edge; a left-aligned banner will show a border
+ * along its right edge; and so forth.
+ *
+ * Note that character-mode platforms generally do NOT respect the border
+ * style, since doing so takes up too much screen space.
+ */
+#define OS_BANNER_STYLE_BORDER 0x00000001
+
+/*
+ * The banner has a vertical/horizontal scrollbar. Character-mode
+ * platforms generally do not support scrollbars.
+ */
+#define OS_BANNER_STYLE_VSCROLL 0x00000002
+#define OS_BANNER_STYLE_HSCROLL 0x00000004
+
+/*
+ * Automatically scroll the banner vertically/horizontally whenever new
+ * text is displayed in the window. In other words, whenever
+ * os_banner_disp() is called, scroll the window so that the text that the
+ * new cursor position after the new text is displayed is visible in the
+ * window.
+ *
+ * Note that this style is independent of the presence of scrollbars.
+ * Even if there are no scrollbars, we can still scroll the window's
+ * contents programmatically.
+ *
+ * Implementations can, if desired, keep an internal buffer of the
+ * window's contents, so that the contents can be recalled via the
+ * scrollbars if the text displayed in the banner exceeds the space
+ * available in the banner's window on the screen. If the implementation
+ * does keep such a buffer, we recommend the following method for managing
+ * this buffer. If the AUTO_VSCROLL flag is not set, then the banner's
+ * contents should be truncated at the bottom when the contents overflow
+ * the buffer; that is, once the banner's internal buffer is full, any new
+ * text that the calling program attempts to add to the banner should
+ * simply be discarded. If the AUTO_VSCROLL flag is set, then the OLDEST
+ * text should be discarded instead, so that the most recent text is
+ * always retained.
+ */
+#define OS_BANNER_STYLE_AUTO_VSCROLL 0x00000008
+#define OS_BANNER_STYLE_AUTO_HSCROLL 0x00000010
+
+/*
+ * Tab-based alignment is required/supported. On creation, this is a hint
+ * to the implementation that is sometimes necessary to determine what
+ * kind of font to use in the new window, for non-HTML platforms. If this
+ * flag is set on creation, the caller is indicating that it wants to use
+ * <TAB> tags to align text in the window.
+ *
+ * Character-mode implementations that use a single font with fixed pitch
+ * can simply ignore this. These implementations ALWAYS have a working
+ * <TAB> capability, because the portable output formatter provides <TAB>
+ * interpretation for a fixed-pitch window.
+ *
+ * Full HTML TADS implementations can also ignore this. HTML TADS
+ * implementations always have full <TAB> support via the HTML
+ * parser/renderer.
+ *
+ * Text-only implementations on GUI platforms (i.e., implementations that
+ * are not based on the HTML parser/renderer engine in HTML TADS, but
+ * which run on GUI platforms with proportionally-spaced text) should use
+ * this flag to determine the font to display. If this flag is NOT set,
+ * then the caller doesn't care about <TAB>, and the implementation is
+ * free to use a proportionally-spaced font in the window if desired.
+ *
+ * When retrieving information on an existing banner, this flag indicates
+ * that <TAB> alignment is actually supported on the window.
+ */
+#define OS_BANNER_STYLE_TAB_ALIGN 0x00000020
+
+/*
+ * Use "MORE" mode in this window. By default, a banner window should
+ * happily allow text to overflow the vertical limits of the window; the
+ * only special thing that should happen on overflow is that the window
+ * should be srolled down to show the latest text, if the auto-vscroll
+ * style is set. With this flag, though, a banner window acts just like
+ * the main text window: when the window fills up vertically, we show a
+ * MORE prompt (using appropriate system conventions), and wait for the
+ * user to indicate that they're ready to see more text. On most systems,
+ * the user acknowledges a MORE prompt by pressing a key or scrolling with
+ * the mouse, but it's up to the system implementor to decide what's
+ * appropriate for the system.
+ *
+ * Note that MORE mode in ANY banner window should generally override all
+ * other user input focus. In other words, if the game in the main window
+ * would like to read a keystroke from the user, but one of the banner
+ * windows is pausing with a MORE prompt, any keyboard input should be
+ * directed to the banner paused at the MORE prompt, not to the main
+ * window; the main window should not receive any key events until the MORE
+ * prompt has been removed.
+ *
+ * This style requires the auto-vscroll style. Implementations should
+ * assume auto-vscroll when this style is set. This style can be ignored
+ * with text grid windows.
+ */
+#define OS_BANNER_STYLE_MOREMODE 0x00000040
+
+/*
+ * This banner is a horizontal/vertical "strut" for sizing purposes. This
+ * means that the banner's content size is taken into account when figuring
+ * the content size of its *parent* banner. If the banner has the same
+ * orientation as the parent, its content size is added to its parent's
+ * internal content size to determine the parent's overall content size.
+ * If the banner's orientation is orthogonal to the parent's, then the
+ * parent's overall content size is the larger of the parent's internal
+ * content size and this banner's content size.
+ */
+#define OS_BANNER_STYLE_HSTRUT 0x00000080
+#define OS_BANNER_STYLE_VSTRUT 0x00000100
+
+
+/*
+ * Delete a banner. This removes the banner from the display, which
+ * requires recalculating the entire screen's layout to reallocate this
+ * banner's space to other windows. When this routine returns, the banner
+ * handle is invalid and can no longer be used in any os_banner_xxx
+ * function calls.
+ *
+ * If the banner has children, the children will no longer be displayed,
+ * but will remain valid in memory until deleted. A child window's
+ * display area always comes out of its parent's space, so once the parent
+ * is gone, a child has no way to acquire any display space; resizing the
+ * child won't help, since it simply has no way to obtain any screen space
+ * once its parent has been deleted. Even though the window's children
+ * will become invisible, their banner handles will remain valid; the
+ * caller is responsible for explicitly deleting the children even after
+ * deleting their parent.
+ */
+void os_banner_delete(void *banner_handle);
+
+/*
+ * "Orphan" a banner. This tells the osifc implementation that the caller
+ * wishes to sever all of its ties with the banner (as part of program
+ * termination, for example), but that the calling program does not
+ * actually require that the banner's on-screen display be immediately
+ * removed.
+ *
+ * The osifc implementation can do one of two things:
+ *
+ * 1. Simply call os_banner_delete(). If the osifc implementation
+ * doesn't want to do anything extra with the banner, it can simply delete
+ * the banner, since the caller has no more use for it.
+ *
+ * 2. Take ownership of the banner. If the osifc implementation wishes
+ * to continue displaying the final screen configuration after a program
+ * has terminated, it can simply take over the banner and leave it on the
+ * screen. The osifc subsystem must eventually delete the banner itself
+ * if it takes this routine; for example, if the osifc subsystem allows
+ * another client program to be loaded into the same window after a
+ * previous program has terminated, it would want to delete any orphaned
+ * banners from the previous program when loading a new program.
+ */
+void os_banner_orphan(void *banner_handle);
+
+/*
+ * Banner information structure. This is filled in by the system-specific
+ * implementation in os_banner_getinfo().
+ */
+struct os_banner_info_t
+{
+ /* alignment */
+ int align;
+
+ /* style flags - these indicate the style flags actually in use */
+ unsigned long style;
+
+ /*
+ * Actual on-screen size of the banner, in rows and columns. If the
+ * banner is displayed in a proportional font or can display multiple
+ * fonts of different sizes, this is approximated by the number of "0"
+ * characters in the window's default font that will fit in the
+ * window's display area.
+ */
+ int rows;
+ int columns;
+
+ /*
+ * Actual on-screen size of the banner in pixels. This is meaningful
+ * only for full HTML interpreter; for text-only interpreters, these
+ * are always set to zero.
+ *
+ * Note that even if we're running on a GUI operating system, these
+ * aren't meaningful unless this is a full HTML interpreter. Text-only
+ * interpreters should always set these to zero, even on GUI OS's.
+ */
+ int pix_width;
+ int pix_height;
+
+ /*
+ * OS line wrapping flag. If this is set, the window uses OS-level
+ * line wrapping because the window uses a proportional font, so the
+ * caller does not need to (and should not) perform line breaking in
+ * text displayed in the window.
+ *
+ * Note that OS line wrapping is a PERMANENT feature of the window.
+ * Callers can note this information once and expect it to remain
+ * fixed through the window's lifetime.
+ */
+ int os_line_wrap;
+};
+typedef struct os_banner_info_t os_banner_info_t;
+
+/*
+ * Get information on the banner - fills in the information structure with
+ * the banner's current settings. Note that this should indicate the
+ * ACTUAL properties of the banner, not the requested properties; this
+ * allows callers to determine how the banner is actually displayed, which
+ * depends upon the platform's capabilities and user preferences.
+ *
+ * Returns true if the information was successfully obtained, false if
+ * not. This can return false if the underlying OS window has already
+ * been closed by a user action, for example.
+ */
+int os_banner_getinfo(void *banner_handle, os_banner_info_t *info);
+
+/*
+ * Get the character width/height of the banner, for layout purposes. This
+ * gives the size of the banner in character cells.
+ *
+ * These are not meaningful when the underlying window uses a proportional
+ * font or varying fonts of different sizes. When the size of text varies
+ * in the window, the OS layer is responsible for word-wrapping and other
+ * layout, in which case these simply return zero.
+ *
+ * Note that these routines might appear to be redundant with the 'rows'
+ * and 'columns' information returned from os_banner_getinfo(), but these
+ * have two important distinctions. First, these routines return only the
+ * width and height information, so they can be implemented with less
+ * overhead than os_banner_getinfo(); this is important because formatters
+ * might need to call these routines frequently while formatting text.
+ * Second, these routines are not required to return an approximation for
+ * windows using proportional fonts, as os_banner_getinfo() does; these can
+ * simply return zero when a proportional font is in use.
+ */
+int os_banner_get_charwidth(void *banner_handle);
+int os_banner_get_charheight(void *banner_handle);
+
+/* clear the contents of a banner */
+void os_banner_clear(void *banner_handle);
+
+/*
+ * Display output on a banner. Writes the output to the window on the
+ * display at the current output position.
+ *
+ * The following special characters should be recognized and handled:
+ *
+ * '\n' - newline; move output position to the start of the next line.
+ *
+ * '\r' - move output position to start of current line; subsequent text
+ * overwrites any text previously displayed on the current line. It is
+ * permissible to delete the old text immediately on seeing the '\r',
+ * rather than waiting for additional text to actually overwrite it.
+ *
+ * All other characters should simply be displayed as ordinary printing
+ * text characters. Note that tab characters should not be passed to this
+ * routine, but if they are, they can simply be treated as ordinary spaces
+ * if desired. Other control characters (backspace, escape, etc) should
+ * never be passed to this routine; the implementation is free to ignore
+ * any control characters not listed above.
+ *
+ * If any text displayed here overflows the current boundaries of the
+ * window on the screen, the text MUST be "clipped" to the current window
+ * boundaries; in other words, anything this routine tries to display
+ * outside of the window's on-screen rectangle must not actually be shown
+ * on the screen.
+ *
+ * Text overflowing the display boundaries MUST also be retained in an
+ * internal buffer. This internal buffer can be limited to the actual
+ * maximum display size of the terminal screen or application window, if
+ * desired. It is necessary to retain clipped text, because this allows a
+ * window to be expanded to the size of its contents AFTER the contents
+ * have already been displayed.
+ *
+ * If the banner does its own line wrapping, it must indicate this via the
+ * os_line_wrap flag in the os_banner_getinfo() return data. If the
+ * banner doesn't indicate this flag, then it must not do any line
+ * wrapping at all, even if the caller attempts to write text beyond the
+ * right edge of the window - any text overflowing the width of the window
+ * must simply be clipped.
+ *
+ * Text grid banners must ALWAYS clip - these banners should never perform
+ * any line wrapping.
+ */
+void os_banner_disp(void *banner_handle, const char *txt, size_t len);
+
+/*
+ * Set the text attributes in a banner, for subsequent text displays.
+ * 'attr' is a (bitwise-OR'd) combination of OS_ATTR_xxx values.
+ */
+void os_banner_set_attr(void *banner_handle, int attr);
+
+/*
+ * Set the text color in a banner, for subsequent text displays. The 'fg'
+ * and 'bg' colors are given as RGB or parameterized colors; see the
+ * definition of os_color_t for details.
+ *
+ * If the underlying renderer is HTML-enabled, then this should not be
+ * used; the appropriate HTML code should simply be displayed to the
+ * banner instead.
+ */
+void os_banner_set_color(void *banner_handle, os_color_t fg, os_color_t bg);
+
+/*
+ * Set the screen color in the banner - this is analogous to the screen
+ * color in the main text area.
+ *
+ * If the underlying renderer is HTML-enabled, then this should not be
+ * used; the HTML <BODY> tag should be used instead.
+ */
+void os_banner_set_screen_color(void *banner_handle, os_color_t color);
+
+/* flush output on a banner */
+void os_banner_flush(void *banner_handle);
+
+/*
+ * Set the banner's size. The size has the same meaning as in
+ * os_banner_create().
+ *
+ * 'is_advisory' indicates whether the sizing is required or advisory only.
+ * If this flag is false, then the size should be set as requested. If
+ * this flag is true, it means that the caller intends to call
+ * os_banner_size_to_contents() at some point, and that the size being set
+ * now is for advisory purposes only. Platforms that support
+ * size-to-contents may simply ignore advisory sizing requests, although
+ * they might want to ensure that they have sufficient off-screen buffer
+ * space to keep track of the requested size of display, so that the
+ * information the caller displays in preparation for calling
+ * size-to-contents will be retained. Platforms that do not support
+ * size-to-contents should set the requested size even when 'is_advisory'
+ * is true.
+ */
+void os_banner_set_size(void *banner_handle, int siz, int siz_units,
+ int is_advisory);
+
+/*
+ * Set the banner to the size of its current contents. This can be used
+ * to set the banner's size after some text (or other material) has been
+ * displayed to the banner, so that the size can be set according to the
+ * banner's actual space requirements.
+ *
+ * This changes the banner's "requested size" to match the current size.
+ * Subsequent calls to os_banner_getinfo() will thus indicate a requested
+ * size according to the size set here.
+ */
+void os_banner_size_to_contents(void *banner_handle);
+
+/*
+ * Turn HTML mode on/off in the banner window. If the underlying renderer
+ * doesn't support HTML, these have no effect.
+ */
+void os_banner_start_html(void *banner_handle);
+void os_banner_end_html(void *banner_handle);
+
+/*
+ * Set the output coordinates in a text grid window. The grid window is
+ * arranged into character cells numbered from row zero, column zero for
+ * the upper left cell. This function can only be used if the window was
+ * created with type OS_BANNER_TYPE_TEXTGRID; the request should simply be
+ * ignored by other window types.
+ *
+ * Moving the output position has no immediate effect on the display, and
+ * does not itself affect the "content size" for the purposes of
+ * os_banner_size_to_contents(). This simply sets the coordinates where
+ * any subsequent text is displayed.
+ */
+void os_banner_goto(void *banner_handle, int row, int col);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Get system information. 'code' is a SYSINFO_xxx code, which
+ * specifies what type of information to get. The 'param' argument's
+ * meaning depends on which code is selected. 'result' is a pointer to
+ * an integer that is to be filled in with the result value. If the
+ * code is not known, this function should return FALSE. If the code is
+ * known, the function should fill in *result and return TRUE.
+ */
+int os_get_sysinfo(int code, void *param, long *result);
+
+/* determine if systemInfo is supported - os_get_sysinfo never gets this */
+#define SYSINFO_SYSINFO 1
+
+/* get interpreter version number - os_get_sysinfo never gets this */
+#define SYSINFO_VERSION 2
+
+/* get operating system name - os_get_sysinfo never gets this */
+#define SYSINFO_OS_NAME 3
+
+/*
+ * Can the system process HTML directives? returns 1 if so, 0 if not.
+ * Note that if this returns false, then all of the codes below from
+ * JPEG to LINKS are implicitly false as well, since TADS can only use
+ * images, sounds, and links through HTML.
+ */
+#define SYSINFO_HTML 4
+
+/* can the system display JPEG's? 1 if yes, 0 if no */
+#define SYSINFO_JPEG 5
+
+/* can the system display PNG's? 1 if yes, 0 if no */
+#define SYSINFO_PNG 6
+
+/* can the system play WAV's? 1 if yes, 0 if no */
+#define SYSINFO_WAV 7
+
+/* can the system play MIDI's? 1 if yes, 0 if no */
+#define SYSINFO_MIDI 8
+
+/* can the system play MIDI and WAV's simultaneously? yes=1, no=0 */
+#define SYSINFO_WAV_MIDI_OVL 9
+
+/* can the system play multiple WAV's simultaneously? yes=1, no=0 */
+#define SYSINFO_WAV_OVL 10
+
+/*
+ * GENERAL NOTES ON PREFERENCE SETTINGS:
+ *
+ * Several of the selectors below refer to the preference settings. We're
+ * talking about user-settable options to control various aspects of the
+ * interpreter. The conventional GUI for this kind of thing is a dialog
+ * box reachable through a menu command named something like "Options" or
+ * "Preferences". A couple of general notes about these:
+ *
+ * 1. The entire existence of a preferences mechanism is optional -
+ * interpreter writers aren't required to implement anything along these
+ * lines. In some cases the local platforms might not have any suitable
+ * conventions for a preferences UI (e.g., character-mode console
+ * applications), and in other cases the terp developer might just want to
+ * omit a prefs mechanism because of the work involved to implement it, or
+ * to keep the UI simpler.
+ *
+ * 2. If a given SYSINFO_PREF_xxx selector refers to a preference item
+ * that's not implemented in the local interpreter, the terp should simply
+ * return a suitable default result. For example, if the interpreter
+ * doesn't have a preference item to allow the user to turn sounds off, the
+ * selector SYSINFO_PREF_SOUNDS should return 1 to indicate that the user
+ * has not in fact turned off sounds (because there's no way to do so).
+ *
+ * 3. The various SYSINFO_PREF_xxx selectors are purely queries - they're
+ * NOT a mechanism for enforcing the preferences. For example, if the
+ * interpreter provides a "Sounds On/Off" option, it's up to the terp to
+ * enforce it the Off setting by ignoring any sound playback requests. The
+ * game isn't under any obligation to query any of the preferences or to
+ * alter its behavior based on their settings - you should expect that the
+ * game will go on trying to play sounds even when "Sounds Off" is selected
+ * in the preferences. The purpose of these SYSINFO selectors is to let
+ * the game determine the current presentation status, but *only if it
+ * cares*. For example, the game might determine whether or not sounds are
+ * actually being heard just before playing a sound effect that's important
+ * to the progress of the game, so that it can provide a visual alternative
+ * if the effect won't be heard.
+ */
+
+/*
+ * Get image preference setting - 1 = images can be displayed, 0 = images
+ * are not being displayed because the user turned off images in the
+ * preferences. This is, of course, irrelevant if images can't be
+ * displayed at all.
+ *
+ * See the general notes on preferences queries above.
+ */
+#define SYSINFO_PREF_IMAGES 11
+
+/*
+ * Get digitized sound effect (WAV) preference setting - 1 = sounds can be
+ * played, 0 = sounds are not being played because the user turned off
+ * sounds in the preferences.
+ *
+ * See the general notes on preferences queries above.
+ */
+#define SYSINFO_PREF_SOUNDS 12
+
+/*
+ * Get music (MIDI) preference setting - 1 = music can be played, 0 = music
+ * is not being played because the user turned off music in the
+ * preferences.
+ *
+ * See the general notes on preferences queries above.
+ */
+#define SYSINFO_PREF_MUSIC 13
+
+/*
+ * Get link display preference setting - 0 = links are not being displayed
+ * because the user set a preference item that suppresses all links (which
+ * doesn't actually hide them, but merely displays them and otherwise
+ * treats them as ordinary text). 1 = links are to be displayed normally.
+ * 2 = links can be displayed temporarily by the user by pressing a key or
+ * some similar action, but aren't being displayed at all times.
+ *
+ * See the general note on preferences queries above.
+ */
+#define SYSINFO_PREF_LINKS 14
+
+/* can the system play MPEG sounds of any kind? */
+#define SYSINFO_MPEG 15
+
+/* can the system play MPEG audio 2.0 layer I/II/III sounds? */
+#define SYSINFO_MPEG1 16
+#define SYSINFO_MPEG2 17
+#define SYSINFO_MPEG3 18
+
+/*
+ * is the system *currently* in HTML mode? os_get_sysinfo never gets
+ * this code, since the portable output layer keeps track of this
+ */
+#define SYSINFO_HTML_MODE 19
+
+/*
+ * Does the system allow following external URL links of the various
+ * types? These return true if the system is capable of following these
+ * types of hypertext links, false if not. Note that, if the system is
+ * capable of following these links, these should return true regardless
+ * of any current mode settings; in particular, these should not be
+ * sensitive to the current HTML mode or the current link display mode,
+ * since the question is not whether a link now displayed can be
+ * followed by the user, but rather whether the system has the
+ * capability to follow these types of links at all.
+ */
+#define SYSINFO_LINKS_HTTP 20
+#define SYSINFO_LINKS_FTP 21
+#define SYSINFO_LINKS_NEWS 22
+#define SYSINFO_LINKS_MAILTO 23
+#define SYSINFO_LINKS_TELNET 24
+
+/* is PNG transparency supported? */
+#define SYSINFO_PNG_TRANS 25
+
+/* is PNG alpha blending supported? */
+#define SYSINFO_PNG_ALPHA 26
+
+/* is the Ogg Vorbis audio format supported? */
+#define SYSINFO_OGG 27
+
+/* can the system display MNG's? */
+#define SYSINFO_MNG 28
+
+/* can the system display MNG's with transparency? */
+#define SYSINFO_MNG_TRANS 29
+
+/* can the system display MNG's with alpha blending? */
+#define SYSINFO_MNG_ALPHA 30
+
+/* can we display highlighted text in its own appearance? */
+#define SYSINFO_TEXT_HILITE 31
+
+/*
+ * Can we display text colors? This returns a SYSINFO_TXC_xxx code
+ * indicating the level of color support.
+ *
+ * The os_xxx interfaces don't presently support anything beyond the ANSI
+ * colors; however, HTML-enabled interpreters generally support full RGB
+ * colors, so we call this out as a separate level.
+ */
+#define SYSINFO_TEXT_COLORS 32
+
+/* no text color support */
+#define SYSINFO_TXC_NONE 0
+
+/* parameterized color names only (OS_COLOR_P_TEXT, etc) */
+#define SYSINFO_TXC_PARAM 1
+
+/*
+ * we support only the basic ANSI colors, foreground control only (white,
+ * black, blue, red, green, yellow, cyan, magenta)
+ */
+#define SYSINFO_TXC_ANSI_FG 2
+
+/* ANSI colors, foreground and background */
+#define SYSINFO_TXC_ANSI_FGBG 3
+
+/* full RGB support */
+#define SYSINFO_TXC_RGB 4
+
+/* are the os_banner_xxx() interfaces supported? */
+#define SYSINFO_BANNERS 33
+
+/* Interpreter Class - this returns one of the SYSINFO_ICLASS_xxx codes */
+#define SYSINFO_INTERP_CLASS 34
+
+/*
+ * Interpreter class: Character-mode Text-Only. Interpreters of this class
+ * use a single, fixed-pitch font to display all text, and use the
+ * text-only HTML subset, and cannot display graphics.
+ */
+#define SYSINFO_ICLASS_TEXT 1
+
+/*
+ * Interpreter class: Text-Only GUI. Interpreters of this class are
+ * traditional text-only interpreters running on graphical operating
+ * systems. These interpreters might use multiple fonts (for example, they
+ * might display highlighted text in boldface), and might use
+ * proportionally-spaced text for some windows. These interpreters support
+ * the text-only HTML subset, and cannot display graphics.
+ *
+ * Text-only GUI interpreters act essentially the same as character-mode
+ * text-only interpreters, from the perspective of the client program.
+ */
+#define SYSINFO_ICLASS_TEXTGUI 2
+
+/*
+ * Interpreter class: HTML. Interpreters of this class can display
+ * graphics and sounds, can display multiple fonts and font sizes, can use
+ * proportional fonts, and support the full HTML TADS markup language for
+ * formatting.
+ */
+#define SYSINFO_ICLASS_HTML 3
+
+/*
+ * Audio fade information.
+ *
+ * SYSINFO_AUDIO_FADE: basic fade-in and fade-out support. Interpreters
+ * that don't support audio fade at all should return 0. Interpreters that
+ * support fades should return a bitwise OR'd combination of
+ * SYSINFO_AUDIOFADE_xxx flags below indicating which formats can be used
+ * with fades.
+ *
+ * SYSINFO_AUDIO_CROSSFADE: cross-fades are supported (i.e., simultaneous
+ * play of overlapping tracks, one fading out while the other fades in).
+ * If cross-fades aren't supported, return 0. If they're supported, return
+ * a combination of SYSINFO_AUDIOFADE_xxx flags indicating which formats
+ * can be used with cross-fades.
+ */
+#define SYSINFO_AUDIO_FADE 35
+#define SYSINFO_AUDIO_CROSSFADE 36
+
+/*
+ * Specific audio fading features. These are bit flags that can be
+ * combined to indicate the fading capabilities of the interpreter.
+ */
+#define SYSINFO_AUDIOFADE_MPEG 0x0001 /* supported for MPEG audio */
+#define SYSINFO_AUDIOFADE_OGG 0x0002 /* supported for Ogg Vorbis */
+#define SYSINFO_AUDIOFADE_WAV 0x0004 /* supported for WAV */
+#define SYSINFO_AUDIOFADE_MIDI 0x0008 /* supported for MIDI */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Integer division operators. For any compiler that follows ANSI C
+ * rules, no definitions are required for these routine, since the
+ * standard definitions below will work properly. However, if your
+ * compiler does not follow ANSI standards with respect to integer
+ * division of negative numbers, you must provide implementations of
+ * these routines that produce the correct results.
+ *
+ * Division must "truncate towards zero," which means that any
+ * fractional part is dropped from the result. If the result is
+ * positive, the result must be the largest integer less than the
+ * algebraic result: 11/3 yields 3. If the result is negative, the
+ * result must be the smallest integer less than the result: (-11)/3
+ * yields -3.
+ *
+ * The remainder must obey the relationship (a/b)*b + a%b == a, for any
+ * integers a and b (b != 0).
+ *
+ * If your compiler does not obey the ANSI rules for the division
+ * operators, make the following changes in your osxxx.h file
+ *
+ * - define the symbol OS_NON_ANSI_DIVIDE in the osxxx.h file
+ *
+ * - either define your own macros for os_divide_long() and
+ * os_remainder_long(), or put actual prototypes for these functions
+ * into your osxxx.h file and write appropriate implementations of these
+ * functions in one of your osxxx.c or .cpp files.
+ */
+/* long os_divide_long(long a, long b); // returns (a/b) with ANSI rules */
+/* long os_remainder_long(long a, long b); // returns (a%b) with ANSI rules */
+
+/* standard definitions for any ANSI compiler */
+#ifndef OS_NON_ANSI_DIVIDE
+#define os_divide_long(a, b) ((a) / (b))
+#define os_remainder_long(a, b) ((a) % (b))
+#endif
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Special "switch" statement optimization flags. These definitions are
+ * OPTIONAL - if a platform doesn't provide these definitions, suitable
+ * code that's fully portable will be used.
+ *
+ * On some compilers, the performance of a "switch" statement can be
+ * improved by fully populating the switch with all possible "case"
+ * values. When the compiler can safely assume that every possible "case"
+ * value is specifically called out in the switch, the compiler can
+ * generate somewhat faster code by omitting any range check for the
+ * controlling expression of the switch: a range check is unnecessary
+ * because the compiler knows that the value can never be outside the
+ * "case" table.
+ *
+ * This type of optimization doesn't apply to all compilers. When a given
+ * platform's compiler can be coerced to produce faster "switch"
+ * statements, though, there might be some benefit in defining these
+ * symbols according to local platform rules.
+ *
+ * First, #define OS_FILL_OUT_CASE_TABLES if you want this type of switch
+ * optimization at all. This symbol is merely a flag, so it doesn't need
+ * a value - #defining it is enough to activate the special code. If you
+ * don't define this symbol, then the code that cares about this will
+ * simply generate ordinary switches, leaving holes in the case table and
+ * using "default:" to cover them. If the platform's osxxx.h header does
+ * #define OS_FILL_OUT_CASE_TABLES, then the portable code will know to
+ * fill out case tables with all possible alternatives where it's possible
+ * and potentially beneficial to do so.
+ *
+ * Second, if you #define OS_FILL_OUT_CASE_TABLES, you MUST ALSO #define
+ * OS_IMPOSSIBLE_DEFAULT_CASE. The value for this symbol must be some
+ * code to insert into a "switch" statement at the point where a
+ * "default:" case would normally go. On some compilers, special
+ * extensions allow the program to explicitly indicate within a switch
+ * that all possible cases are covered, so that the compiler doesn't have
+ * to be relied upon to infer this for itself (which would be impossible
+ * for it to do in many cases anyway). You can use an empty definition
+ * for this symbol if your compiler doesn't have any special construct for
+ * indicating a fully-populated case table.
+ *
+ * Note that *most* switch statements in portable code won't care one way
+ * or the other about these definitions. There's a time/space trade-off
+ * in fully populating a switch's case table, so only the most
+ * time-critical code will bother trying.
+ */
+
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * TADS 2 swapping configuration. Define OS_DEFAULT_SWAP_ENABLED to 0
+ * if you want swapping turned off, 1 if you want it turned on. If we
+ * haven't defined a default swapping mode yet, turn swapping on by
+ * default.
+ */
+#ifndef OS_DEFAULT_SWAP_ENABLED
+# define OS_DEFAULT_SWAP_ENABLED 1
+#endif
+
+/*
+ * If the system "long description" (for the banner) isn't defined, make
+ * it the same as the platform ID string.
+ */
+#ifndef OS_SYSTEM_LDESC
+# define OS_SYSTEM_LDESC OS_SYSTEM_NAME
+#endif
+
+/*
+ * TADS 2 Usage Lines
+ *
+ * If the "usage" lines (i.e., the descriptive lines of text describing
+ * how to run the various programs) haven't been otherwise defined,
+ * define defaults for them here. Some platforms use names other than
+ * tc, tr, and tdb for the tools (for example, on Unix they're usually
+ * tadsc, tadsr, and tadsdb), so the usage lines should be adjusted
+ * accordingly; simply define them earlier than this point (in this file
+ * or in one of the files included by this file, such as osunixt.h) for
+ * non-default text.
+ */
+#ifndef OS_TC_USAGE
+# define OS_TC_USAGE "usage: tc [options] file"
+#endif
+#ifndef OS_TR_USAGE
+# define OS_TR_USAGE "usage: tr [options] file"
+#endif
+#ifndef OS_TDB_USAGE
+# define OS_TDB_USAGE "usage: tdb [options] file"
+#endif
+
+/*
+ * Ports can define a special TDB startup message, which is displayed
+ * after the copyright/version banner. If it's not defined at this
+ * point, define it to an empty string.
+ */
+#ifndef OS_TDB_STARTUP_MSG
+# define OS_TDB_STARTUP_MSG ""
+#endif
+
+/*
+ * If a system patch sub-level isn't defined, define it here as zero.
+ * The patch sub-level is used on some systems where a number of ports
+ * are derived from a base port (for example, a large number of ports
+ * are based on the generic Unix port). For platforms like the Mac,
+ * where the porting work applies only to that one platform, this
+ * sub-level isn't meaningful.
+ */
+#ifndef OS_SYSTEM_PATCHSUBLVL
+# define OS_SYSTEM_PATCHSUBLVL "0"
+#endif
} // End of namespace TADS2
} // End of namespace TADS
diff --git a/engines/glk/tads/tads2/post_compilation.h b/engines/glk/tads/tads2/post_compilation.h
new file mode 100644
index 0000000000..1dde838d02
--- /dev/null
+++ b/engines/glk/tads/tads2/post_compilation.h
@@ -0,0 +1,96 @@
+/* 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.
+ *
+ */
+
+/* definitions for post-compilation setup
+ *
+ * Does post-compilation setup, such as setting up contents lists
+ * faster run-time performance, but run - time errors could be disastrous.
+ */
+
+#ifndef GLK_TADS_TADS2_POST_COMPILATION
+#define GLK_TADS_TADS2_POST_COMPILATION
+
+#include "glk/tads/tads2/run.h"
+#include "glk/tads/tads2/tokenizer.h"
+#include "glk/tads/tads2/vocabulary.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* setup context */
+struct supcxdef {
+ errcxdef *supcxerr;
+ mcmcxdef *supcxmem; /* memory manager client context */
+ voccxdef *supcxvoc; /* player command parsing context */
+ tokthdef *supcxtab; /* top-level symbol table */
+ runcxdef *supcxrun; /* execution context */
+ uchar *supcxbuf; /* space for building a list */
+ ushort supcxlen; /* size of buffer */
+};
+
+/* set up contents list for one object for demand-on-load */
+void supcont(void *ctx, objnum obj, prpnum prp);
+
+/* set up inherited vocabulary (called before executing game) */
+void supivoc(supcxdef *ctx);
+
+/* find required objects/functions */
+void supfind(errcxdef *ctx, tokthdef *tab, voccxdef *voc,
+ objnum *preinit, int warnlevel, int casefold);
+
+/* set up reserved words */
+void suprsrv(supcxdef *sup, void (*bif[])(struct bifcxdef *, int),
+ toktdef *tab, int fncntmax, int v1compat, char *new_do,
+ int casefold);
+
+/* set up built-in functions without symbol table (for run-time) */
+void supbif(supcxdef *sup, void (*bif[])(struct bifcxdef *, int),
+ int bifsiz);
+
+/* log an undefined-object error */
+void sup_log_undefobj(mcmcxdef *mctx, errcxdef *ec, int err,
+ char *sym_name, int sym_name_len, objnum objn);
+
+/* set up inherited vocabulary for a particular object */
+void supivoc1(supcxdef *sup, voccxdef *ctx, vocidef *v, objnum target,
+ int inh_from_obj, int flags);
+
+/* get name of an object out of symbol table */
+void supgnam(char *buf, tokthdef *tab, objnum objn);
+
+/* table of built-in functions */
+typedef struct supbidef supbidef;
+struct supbidef
+{
+ char *supbinam; /* name of function */
+ void (*supbifn)(struct bifcxdef *, int); /* C routine to call */
+};
+
+/* external definition for special token table */
+extern tokldef supsctab[];
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/property.h b/engines/glk/tads/tads2/property.h
new file mode 100644
index 0000000000..86a4b69d67
--- /dev/null
+++ b/engines/glk/tads/tads2/property.h
@@ -0,0 +1,167 @@
+/* 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.
+ *
+ */
+
+/* property definitions
+ *
+ * A property structure must be binary-portable, because properties are
+ * stored in objects, which must be binary-portable. Hence, the internal
+ * structure of a property header is not a C structure, but a portable
+ * sequence of bytes. Multi-byte quantities are stored in Intel format.
+ *
+ * property number - 2 bytes
+ * property datatype - 1 byte
+ * property size - 2 bytes
+ * property flags - 1 byte
+ *
+ * This header is followed immediately by the property value. For
+ * convenience, a set of macros is defined to provide access to the
+ * fields of a property header.
+ */
+
+#ifndef GLK_TADS_TADS2_PROPERTY
+#define GLK_TADS_TADS2_PROPERTY
+
+#include "glk/tads/tads.h"
+#include "glk/tads/tads2/data.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/**
+ * a property number, used to look up all properties */
+typedef ushort prpnum;
+
+typedef uchar prpdef; /* prpdef is just an array of bytes */
+#define PRPHDRSIZ 6 /* "sizeof(prpdef)" - size of property header */
+
+/* Macros to provide access to property header entries */
+#define prpprop(p) osrp2((uchar *)(p))
+#define prptype(p) (*(((uchar *)(p)) + 2))
+#define prpsize(p) osrp2((((uchar *)(p)) + 3))
+#define prpflg(p) (*(((uchar *)(p)) + 5))
+#define prpvalp(p) (((uchar *)(p)) + 6)
+
+#define prpsetprop(p,n) oswp2((uchar *)(p), n)
+#define prpsetsize(p,s) oswp2((((uchar *)(p)) + 3), s)
+
+/* property flag values */
+#define PRPFORG 0x01 /* property is original startup value */
+#define PRPFIGN 0x02 /* ignore this prop (has been changed) */
+#define PRPFDEL 0x04 /* property has been permanently deleted */
+
+/**
+ * Invalid property number - this number will never be used as an actual
+ * property, so it can be used to signify the lack of a valid property
+ */
+#define PRP_INVALID 0
+
+/* certain property types are special, and are reserved here */
+#define PRP_DOACTION 1 /* doAction property */
+
+/* vocabulary properties - keep these contiguous, and must start at 2 */
+#define PRP_VERB 2 /* verb vocabulary property */
+#define PRP_NOUN 3 /* noun vocabulary property */
+#define PRP_ADJ 4 /* adjective vocabulary property */
+#define PRP_PREP 5 /* preposition vocabulary property */
+#define PRP_ARTICLE 6 /* article vocabulary property */
+#define PRP_PLURAL 7 /* plural vocabulary property */
+
+/* determine if a property is a vocab property */
+/* int prpisvoc(prpnum p); */
+#define prpisvoc(p) ((p) >= PRP_VERB && (p) <= PRP_PLURAL)
+
+/* more properties... */
+#define PRP_SDESC 8
+#define PRP_THEDESC 9
+#define PRP_DODEFAULT 10
+#define PRP_IODEFAULT 11
+#define PRP_IOACTION 12
+#define PRP_LOCATION 13
+#define PRP_VALUE 14
+#define PRP_ROOMACTION 15
+#define PRP_ACTORACTION 16
+#define PRP_CONTENTS 17
+#define PRP_TPL 18 /* special built-in TEMPLATE structure */
+#define PRP_PREPDEFAULT 19
+#define PRP_VERACTOR 20
+#define PRP_VALIDDO 21
+#define PRP_VALIDIO 22
+#define PRP_LOOKAROUND 23
+#define PRP_ROOMCHECK 24
+#define PRP_STATUSLINE 25
+#define PRP_LOCOK 26
+#define PRP_ISVIS 27
+#define PRP_NOREACH 28
+#define PRP_ISHIM 29
+#define PRP_ISHER 30
+#define PRP_ACTION 31 /* action method */
+#define PRP_VALDOLIST 32 /* validDoList */
+#define PRP_VALIOLIST 33 /* validIoList */
+#define PRP_IOBJGEN 34 /* iobjGen */
+#define PRP_DOBJGEN 35 /* dobjGen */
+#define PRP_NILPREP 36 /* nilPrep */
+#define PRP_REJECTMDO 37 /* rejectMultiDobj */
+#define PRP_MOVEINTO 38 /* moveInto */
+#define PRP_CONSTRUCT 39 /* construct */
+#define PRP_DESTRUCT 40 /* destruct */
+#define PRP_VALIDACTOR 41 /* validActor */
+#define PRP_PREFACTOR 42 /* preferredActor */
+#define PRP_ISEQUIV 43 /* isEquivalent */
+#define PRP_ADESC 44
+#define PRP_MULTISDESC 45
+#define PRP_TPL2 46 /* new-style built-in TEMPLATE structure */
+#define PRP_ANYVALUE 47 /* anyvalue(n) - value to use for '#' with ANY */
+#define PRP_NEWNUMOBJ 48 /* newnumbered(n) - create new numbered object */
+#define PRP_UNKNOWN 49 /* internal property for unknown words */
+#define PRP_PARSEUNKNOWNDOBJ 50 /* parseUnknownDobj */
+#define PRP_PARSEUNKNOWNIOBJ 51 /* parseUnknownIobj */
+#define PRP_DOBJCHECK 52 /* dobjCheck */
+#define PRP_IOBJCHECK 53 /* iobjCheck */
+#define PRP_VERBACTION 54 /* verbAction */
+#define PRP_DISAMBIGDO 55 /* disambigDobj */
+#define PRP_DISAMBIGIO 56 /* disambigIobj */
+#define PRP_PREFIXDESC 57 /* prefixdesc */
+#define PRP_ISTHEM 58 /* isThem */
+
+/* properties used by TADS/Graphic */
+#define PRP_GP_PIC 100 /* gp_picture */
+#define PRP_GP_NAME 101 /* gp_name */
+#define PRP_GP_DEFVERB 102 /* gp_defverb */
+#define PRP_GP_ACTIVE 103 /* gp_active */
+#define PRP_GP_HOTLIST 104 /* gp_hotlist */
+#define PRP_GP_ICON 105 /* gp_icon */
+#define PRP_GP_DEFVERB2 106 /* gp_defverb2 */
+#define PRP_GP_DEFPREP 107 /* gp_defprep */
+#define PRP_GP_HOTID 108 /* gp_hotid */
+#define PRP_GP_OVERLAY 109 /* gp_overlay */
+#define PRP_GP_HOTX 110 /* gp_hotx */
+#define PRP_GP_HOTY 111 /* gp_hoty */
+
+/* highest reserved property number - must match last one above */
+#define PRP_LASTRSV 111
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/regex.cpp b/engines/glk/tads/tads2/regex.cpp
index a7f7d99369..5d608dfa4a 100644
--- a/engines/glk/tads/tads2/regex.cpp
+++ b/engines/glk/tads/tads2/regex.cpp
@@ -68,10 +68,8 @@ Notes
*/
#include "glk/tads/tads2/regex.h"
-#include "glk/tads/tads2/ler.h"
-#include "glk/tads/tads2/os.h"
-//#include "glk/tads/tads2/std.h"
-#//include "engines/glk/tads/tads2/ler.h"
+#include "glk/tads/tads2/memory_cache_heap.h"
+#include "common/util.h"
namespace Glk {
namespace TADS {
@@ -128,1148 +126,1461 @@ enum {
RE_GROUP_MATCH_9 = (RE_GROUP_MATCH_0 + 9)
};
-re_context::re_context(errcxdef *errctx) {
- // save the error context
- _errctx = errctx;
-
- // clear states
- _next_state = RE_STATE_FIRST_VALID;
+/* ------------------------------------------------------------------------ */
+/*
+ * A machine description. Machines are fully described by their initial
+ * and final state ID's.
+ */
+struct re_machine {
+ /* the machine's initial state */
+ re_state_id init;
- // clear groups
- _cur_group = 0;
+ /* the machine's final state */
+ re_state_id final;
+};
- // no string buffer yet
- _strbuf = 0;
+/* ------------------------------------------------------------------------ */
+/*
+ * Initialize the context. The memory for the context structure itself
+ * is allocated and maintained by the caller.
+ */
+void re_init(re_context *ctx, errcxdef *errctx)
+{
+ /* save the error context */
+ ctx->errctx = errctx;
+
+ /* no tuple array yet */
+ ctx->tuple_arr = 0;
+ ctx->tuples_alloc = 0;
+
+ /* clear states */
+ ctx->next_state = RE_STATE_FIRST_VALID;
+
+ /* clear groups */
+ ctx->cur_group = 0;
+
+ /* no string buffer yet */
+ ctx->strbuf = 0;
}
-re_context::~re_context() {
- // reset state
- reset();
-
- // if we allocated a string buffer, delete it
- if (_strbuf != 0) {
- mchfre(_strbuf);
- _strbuf = nullptr;
- }
+/* ------------------------------------------------------------------------ */
+/*
+ * Reset compiler - clears states and tuples
+ */
+static void re_reset(re_context *ctx)
+{
+ int i;
+
+ /* delete any range tables we've allocated */
+ for (i = 0 ; i < ctx->next_state ; ++i)
+ {
+ if (ctx->tuple_arr[i].char_range != 0)
+ {
+ mchfre(ctx->tuple_arr[i].char_range);
+ ctx->tuple_arr[i].char_range = 0;
+ }
+ }
+
+ /* clear states */
+ ctx->next_state = RE_STATE_FIRST_VALID;
+
+ /* clear groups */
+ ctx->cur_group = 0;
}
-void re_context::reset() {
- int i;
-
- // delete any range tables we've allocated
- for (i = 0 ; i < _next_state ; ++i) {
- if (_tuple_arr[i].char_range != 0) {
- mchfre(_tuple_arr[i].char_range);
- _tuple_arr[i].char_range = 0;
- }
- }
-
- // clear states
- _next_state = RE_STATE_FIRST_VALID;
-
- // clear groups
- _cur_group = 0;
+/* ------------------------------------------------------------------------ */
+/*
+ * Delete the context - frees structures associated with the context.
+ * Does NOT free the memory used by the context structure itself.
+ */
+void re_delete(re_context *ctx)
+{
+ /* reset state */
+ re_reset(ctx);
+
+ /* if we've allocated an array, delete it */
+ if (ctx->tuple_arr != 0)
+ {
+ mchfre(ctx->tuple_arr);
+ ctx->tuple_arr = 0;
+ }
+
+ /* if we allocated a string buffer, delete it */
+ if (ctx->strbuf != 0)
+ {
+ mchfre(ctx->strbuf);
+ ctx->strbuf = 0;
+ }
}
-re_state_id re_context::alloc_state() {
- // If we don't have enough room for another state, expand the array
- if (_next_state >= (int)_tuple_arr.size()) {
- // bump the size by a bit
- _tuple_arr.resize(_tuple_arr.size() + 100);
- }
-
- // initialize the next state
- _tuple_arr[_next_state].next_state_1 = RE_STATE_INVALID;
- _tuple_arr[_next_state].next_state_2 = RE_STATE_INVALID;
- _tuple_arr[_next_state].ch = RE_EPSILON;
- _tuple_arr[_next_state].flags = 0;
- _tuple_arr[_next_state].char_range = 0;
-
- // return the new state's ID
- return _next_state++;
+/* ------------------------------------------------------------------------ */
+/*
+ * Allocate a new state ID
+ */
+static re_state_id re_alloc_state(re_context *ctx)
+{
+ /*
+ * If we don't have enough room for another state, expand the array
+ */
+ if (ctx->next_state >= ctx->tuples_alloc)
+ {
+ uint new_alloc;
+
+ /* bump the size by a bit */
+ new_alloc = ctx->tuples_alloc + 100;
+
+ /* allocate or expand the array */
+ if (ctx->tuples_alloc == 0)
+ {
+ /* allocate the initial memory block */
+ ctx->tuple_arr =
+ (re_tuple *)mchalo(ctx->errctx,
+ (new_alloc * sizeof(re_tuple)),
+ "regex");
+ }
+ else
+ {
+ re_tuple *ptr;
+
+ /* allocate a new memory block */
+ ptr = (re_tuple *)mchalo(ctx->errctx,
+ (new_alloc * sizeof(re_tuple)),
+ "regex");
+
+ /* copy the old memory to the new memory */
+ memcpy(ptr, ctx->tuple_arr, ctx->tuples_alloc * sizeof(re_tuple));
+
+ /* free the old block */
+ mchfre(ctx->tuple_arr);
+
+ /* use the new block */
+ ctx->tuple_arr = ptr;
+ }
+
+ /* remember the new allocation size */
+ ctx->tuples_alloc = new_alloc;
+ }
+
+ /* initialize the next state */
+ ctx->tuple_arr[ctx->next_state].next_state_1 = RE_STATE_INVALID;
+ ctx->tuple_arr[ctx->next_state].next_state_2 = RE_STATE_INVALID;
+ ctx->tuple_arr[ctx->next_state].ch = RE_EPSILON;
+ ctx->tuple_arr[ctx->next_state].flags = 0;
+ ctx->tuple_arr[ctx->next_state].char_range = 0;
+
+ /* return the new state's ID */
+ return ctx->next_state++;
}
-void re_context::set_trans(re_state_id id, re_state_id dest_id, char ch) {
- re_tuple *tuple;
-
- /*
- * get the tuple containing the transitions for this state ID - the
- * state ID is the index of the state's transition tuple in the array
- */
- tuple = &_tuple_arr[id];
-
- /*
- * If the first state pointer hasn't been set yet, set it to the new
- * destination. Otherwise, set the second state pointer.
- *
- * Only set the character on setting the first state. When setting
- * the second state, we must assume that the character for the state
- * has already been set, since any given state can have only one
- * character setting.
- */
- if (tuple->next_state_1 == RE_STATE_INVALID) {
- /*
- * set the character ID, unless the state has been marked with a
- * special flag which indicates that the character value has
- * another meaning (in particular, a group marker)
- */
- if (!(tuple->flags & (RE_STATE_GROUP_BEGIN | RE_STATE_GROUP_END)))
- tuple->ch = ch;
-
- // set the first transition
- tuple->next_state_1 = dest_id;
- } else {
- // set only the second transition state - don't set the character
- tuple->next_state_2 = dest_id;
- }
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Set a transition from a state to a given destination state.
+ */
+static void re_set_trans(re_context *ctx,
+ re_state_id id, re_state_id dest_id, char ch)
+{
+ re_tuple *tuple;
+
+ /*
+ * get the tuple containing the transitions for this state ID - the
+ * state ID is the index of the state's transition tuple in the
+ * array
+ */
+ tuple = &ctx->tuple_arr[id];
+
+ /*
+ * If the first state pointer hasn't been set yet, set it to the new
+ * destination. Otherwise, set the second state pointer.
+ *
+ * Only set the character on setting the first state. When setting
+ * the second state, we must assume that the character for the state
+ * has already been set, since any given state can have only one
+ * character setting.
+ */
+ if (tuple->next_state_1 == RE_STATE_INVALID)
+ {
+ /*
+ * set the character ID, unless the state has been marked with a
+ * special flag which indicates that the character value has
+ * another meaning (in particular, a group marker)
+ */
+ if (!(tuple->flags & (RE_STATE_GROUP_BEGIN | RE_STATE_GROUP_END)))
+ tuple->ch = ch;
+
+ /* set the first transition */
+ tuple->next_state_1 = dest_id;
+ }
+ else
+ {
+ /* set only the second transition state - don't set the character */
+ tuple->next_state_2 = dest_id;
+ }
}
-void re_context::init_machine(re_machine *machine) {
- machine->init = alloc_state();
- machine->final = alloc_state();
+/* ------------------------------------------------------------------------ */
+/*
+ * Initialize a new machine, giving it an initial and final state
+ */
+static void re_init_machine(re_context *ctx, re_machine *machine)
+{
+ machine->init = re_alloc_state(ctx);
+ machine->final = re_alloc_state(ctx);
}
-void re_context::build_char(re_machine *machine, char ch) {
- // initialize our new machine
- init_machine(machine);
+/*
+ * Build a character recognizer
+ */
+static void re_build_char(re_context *ctx, re_machine *machine, char ch)
+{
+ /* initialize our new machine */
+ re_init_machine(ctx, machine);
- // allocate a transition tuple for the new state
- set_trans(machine->init, machine->final, ch);
+ /* allocate a transition tuple for the new state */
+ re_set_trans(ctx, machine->init, machine->final, ch);
}
-void re_context::build_char_range(re_machine *machine, unsigned char *range, int exclusion) {
- unsigned char *range_copy;
-
- // initialize our new machine
- init_machine(machine);
-
- // allocate a transition table for the new state
- set_trans(machine->init, machine->final, (char)(exclusion ? RE_RANGE_EXCL : RE_RANGE));
+/*
+ * Build a character range recognizer. 'range' is a 256-bit (32-byte)
+ * bit vector.
+ */
+static void re_build_char_range(re_context *ctx, re_machine *machine,
+ unsigned char *range, int exclusion)
+{
+ unsigned char *range_copy;
+
+ /* initialize our new machine */
+ re_init_machine(ctx, machine);
+
+ /* allocate a transition table for the new state */
+ re_set_trans(ctx, machine->init, machine->final,
+ (char)(exclusion ? RE_RANGE_EXCL : RE_RANGE));
+
+ /* allocate a copy of the range bit vector */
+ range_copy = (unsigned char *)mchalo(ctx->errctx, 32, "regex range");
+
+ /* copy the caller's range */
+ memcpy(range_copy, range, 32);
+
+ /* store it in the tuple */
+ ctx->tuple_arr[machine->init].char_range = range_copy;
+}
+
- // allocate a copy of the range bit vector
- range_copy = (unsigned char *)mchalo(_errctx, 32, "regex range");
+/*
+ * Build a group recognizer. This is almost the same as a character
+ * recognizer, but matches a previous group rather than a literal
+ * character.
+ */
+static void re_build_group_matcher(re_context *ctx,
+ re_machine *machine, int group_num)
+{
+ /* initialize our new machine */
+ re_init_machine(ctx, machine);
+
+ /*
+ * Allocate a transition tuple for the new state, using the group ID
+ * as the character code. Store the special code for a group
+ * recognizer rather than the normal literal character code.
+ */
+ re_set_trans(ctx, machine->init, machine->final,
+ (char)(group_num + RE_GROUP_MATCH_0));
+}
- // copy the caller's range
- memcpy(range_copy, range, 32);
- // store it in the tuple
- _tuple_arr[machine->init].char_range = range_copy;
+/*
+ * Build a concatenation recognizer
+ */
+static void re_build_concat(re_context *ctx, re_machine *new_machine,
+ re_machine *lhs, re_machine *rhs)
+{
+ /* initialize the new machine */
+ re_init_machine(ctx, new_machine);
+
+ /*
+ * set up an epsilon transition from the new machine's initial state
+ * to the first submachine's initial state
+ */
+ re_set_trans(ctx, new_machine->init, lhs->init, RE_EPSILON);
+
+ /*
+ * Set up an epsilon transition from the first submachine's final
+ * state to the second submachine's initial state
+ */
+ re_set_trans(ctx, lhs->final, rhs->init, RE_EPSILON);
+
+ /*
+ * Set up an epsilon transition from the second submachine's final
+ * state to our new machine's final state
+ */
+ re_set_trans(ctx, rhs->final, new_machine->final, RE_EPSILON);
}
-void re_context::build_group_matcher(re_machine *machine, int group_num) {
- // initialize our new machine
- init_machine(machine);
-
- /*
- * Allocate a transition tuple for the new state, using the group ID
- * as the character code. Store the special code for a group
- * recognizer rather than the normal literal character code.
- */
- set_trans(machine->init, machine->final, (char)(group_num + RE_GROUP_MATCH_0));
+/*
+ * Build a group machine. sub_machine contains the machine that
+ * expresses the group's contents; we'll fill in new_machine with a
+ * newly-created machine that encloses and marks the group.
+ */
+static void re_build_group(re_context *ctx, re_machine *new_machine,
+ re_machine *sub_machine, int group_id)
+{
+ /* initialize the container machine */
+ re_init_machine(ctx, new_machine);
+
+ /*
+ * set up an epsilon transition from the new machine's initial state
+ * into the initial state of the group, and another transition from
+ * the group's final state into the container's final state
+ */
+ re_set_trans(ctx, new_machine->init, sub_machine->init, RE_EPSILON);
+ re_set_trans(ctx, sub_machine->final, new_machine->final, RE_EPSILON);
+
+ /*
+ * Mark the initial and final states of the group machine as being
+ * group markers.
+ */
+ ctx->tuple_arr[new_machine->init].flags |= RE_STATE_GROUP_BEGIN;
+ ctx->tuple_arr[new_machine->final].flags |= RE_STATE_GROUP_END;
+
+ /* store the group ID in the 'ch' member of the start and end states */
+ ctx->tuple_arr[new_machine->init].ch = group_id;
+ ctx->tuple_arr[new_machine->final].ch = group_id;
}
-void re_context::build_concat(re_machine *new_machine, re_machine *lhs, re_machine *rhs) {
- // initialize the new machine
- init_machine(new_machine);
-
- /*
- * Set up an epsilon transition from the new machine's initial state
- * to the first submachine's initial state
- */
- set_trans(new_machine->init, lhs->init, RE_EPSILON);
-
- /*
- * Set up an epsilon transition from the first submachine's final
- * state to the second submachine's initial state
- */
- set_trans(lhs->final, rhs->init, RE_EPSILON);
-
- /*
- * Set up an epsilon transition from the second submachine's final
- * state to our new machine's final state
- */
- set_trans(rhs->final, new_machine->final, RE_EPSILON);
+/*
+ * Build an alternation recognizer
+ */
+static void re_build_alter(re_context *ctx, re_machine *new_machine,
+ re_machine *lhs, re_machine *rhs)
+{
+ /* initialize the new machine */
+ re_init_machine(ctx, new_machine);
+
+ /*
+ * Set up an epsilon transition from our new machine's initial state
+ * to the initial state of each submachine
+ */
+ re_set_trans(ctx, new_machine->init, lhs->init, RE_EPSILON);
+ re_set_trans(ctx, new_machine->init, rhs->init, RE_EPSILON);
+
+ /*
+ * Set up an epsilon transition from the final state of each
+ * submachine to our final state
+ */
+ re_set_trans(ctx, lhs->final, new_machine->final, RE_EPSILON);
+ re_set_trans(ctx, rhs->final, new_machine->final, RE_EPSILON);
}
-void re_context::build_group(re_machine *new_machine, re_machine *sub_machine, int group_id) {
- // initialize the container machine
- init_machine(new_machine);
-
- /*
- * set up an epsilon transition from the new machine's initial state
- * into the initial state of the group, and another transition from
- * the group's final state into the container's final state
- */
- set_trans(new_machine->init, sub_machine->init, RE_EPSILON);
- set_trans(sub_machine->final, new_machine->final, RE_EPSILON);
-
- // Mark the initial and final states of the group machine as being group markers
- _tuple_arr[new_machine->init].flags |= RE_STATE_GROUP_BEGIN;
- _tuple_arr[new_machine->final].flags |= RE_STATE_GROUP_END;
-
- // store the group ID in the 'ch' member of the start and end states
- _tuple_arr[new_machine->init].ch = group_id;
- _tuple_arr[new_machine->final].ch = group_id;
+/*
+ * Build a closure recognizer
+ */
+static void re_build_closure(re_context *ctx,
+ re_machine *new_machine, re_machine *sub,
+ char specifier)
+{
+ /* initialize the new machine */
+ re_init_machine(ctx, new_machine);
+
+ /*
+ * set up an epsilon transition from our initial state to the
+ * submachine's initial state, and from the submachine's final state
+ * to our final state
+ */
+ re_set_trans(ctx, new_machine->init, sub->init, RE_EPSILON);
+ re_set_trans(ctx, sub->final, new_machine->final, RE_EPSILON);
+
+ /*
+ * If this is an unbounded closure ('*' or '+', but not '?'), set up
+ * the loop transition that takes us from the new machine's final
+ * state back to its initial state. We don't do this on the
+ * zero-or-one closure, because we can only match the expression
+ * once.
+ */
+ if (specifier != '?')
+ re_set_trans(ctx, sub->final, sub->init, RE_EPSILON);
+
+ /*
+ * If this is a zero-or-one closure or a zero-or-more closure, set
+ * up an epsilon transition from our initial state to our final
+ * state, since we can skip the entire subexpression. We don't do
+ * this on the one-or-more closure, because we can't skip the
+ * subexpression in this case.
+ */
+ if (specifier != '+')
+ re_set_trans(ctx, new_machine->init, new_machine->final, RE_EPSILON);
}
-void re_context::build_alter(re_machine *new_machine, re_machine *lhs, re_machine *rhs) {
- // initialize the new machine
- init_machine(new_machine);
-
- /*
- * Set up an epsilon transition from our new machine's initial state
- * to the initial state of each submachine
- */
- set_trans(new_machine->init, lhs->init, RE_EPSILON);
- set_trans(new_machine->init, rhs->init, RE_EPSILON);
-
- /*
- * Set up an epsilon transition from the final state of each
- * submachine to our final state
- */
- set_trans(lhs->final, new_machine->final, RE_EPSILON);
- set_trans(rhs->final, new_machine->final, RE_EPSILON);
+/*
+ * Build a null machine
+ */
+static void re_build_null_machine(re_context *ctx, re_machine *machine)
+{
+ machine->init = machine->final = RE_STATE_INVALID;
}
-void re_context::build_closure(re_machine *new_machine, re_machine *sub, char specifier) {
- // initialize the new machine
- init_machine(new_machine);
-
- /*
- * Set up an epsilon transition from our initial state to the submachine's initial
- * state, and from the submachine's final state to our final state
- */
- set_trans(new_machine->init, sub->init, RE_EPSILON);
- set_trans(sub->final, new_machine->final, RE_EPSILON);
-
- /*
- * If this is an unbounded closure ('*' or '+', but not '?'), set up
- * the loop transition that takes us from the new machine's final
- * state back to its initial state. We don't do this on the
- * zero-or-one closure, because we can only match the expression
- * once.
- */
- if (specifier != '?')
- set_trans(sub->final, sub->init, RE_EPSILON);
-
- /*
- * If this is a zero-or-one closure or a zero-or-more closure, set
- * up an epsilon transition from our initial state to our final
- * state, since we can skip the entire subexpression. We don't do
- * this on the one-or-more closure, because we can't skip the
- * subexpression in this case.
- */
- if (specifier != '+')
- set_trans(new_machine->init, new_machine->final, RE_EPSILON);
+/* ------------------------------------------------------------------------ */
+/*
+ * Determine if a machine is null
+ */
+static int re_is_machine_null(re_context *ctx, re_machine *machine)
+{
+ return (machine->init == RE_STATE_INVALID);
}
-void re_context::concat_onto(re_machine *dest, re_machine *rhs) {
- // check for a null destination machine
- if (dest->isNull()) {
- /*
- * the first machine is null - simply copy the second machine
- * onto the first unchanged
- */
- *dest = *rhs;
- } else {
- re_machine new_machine;
-
- // build the concatenated machine
- build_concat(&new_machine, dest, rhs);
-
- // copy the concatenated machine onto the first machine
- *dest = new_machine;
- }
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Concatenate the second machine onto the first machine, replacing the
+ * first machine with the resulting machine. If the first machine is a
+ * null machine (created with re_build_null_machine), we'll simply copy
+ * the second machine into the first.
+ */
+static void re_concat_onto(re_context *ctx,
+ re_machine *dest, re_machine *rhs)
+{
+ /* check for a null destination machine */
+ if (re_is_machine_null(ctx, dest))
+ {
+ /*
+ * the first machine is null - simply copy the second machine
+ * onto the first unchanged
+ */
+ *dest = *rhs;
+ }
+ else
+ {
+ re_machine new_machine;
+
+ /* build the concatenated machine */
+ re_build_concat(ctx, &new_machine, dest, rhs);
+
+ /* copy the concatenated machine onto the first machine */
+ *dest = new_machine;
+ }
}
-void re_context::alternate_onto(re_machine *dest, re_machine *rhs) {
- // check to see if the first machine is null
- if (dest->isNull()) {
- /*
- * the first machine is null - simply copy the second machine
- * onto the first
- */
- *dest = *rhs;
- } else {
- /*
- * if the second machine is null, don't do anything; otherwise,
- * build the alternation
- */
- if (!rhs->isNull()) {
- re_machine new_machine;
-
- // build the alternation
- build_alter(&new_machine, dest, rhs);
-
- // replace the first machine with the alternation
- *dest = new_machine;
- }
- }
+/*
+ * Alternate the second machine onto the first machine, replacing the
+ * first machine with the resulting machine. If the first machine is a
+ * null machine, this simply replaces the first machine with the second
+ * machine. If the second machine is null, this simply leaves the first
+ * machine unchanged.
+ */
+static void re_alternate_onto(re_context *ctx,
+ re_machine *dest, re_machine *rhs)
+{
+ /* check to see if the first machine is null */
+ if (re_is_machine_null(ctx, dest))
+ {
+ /*
+ * the first machine is null - simply copy the second machine
+ * onto the first
+ */
+ *dest = *rhs;
+ }
+ else
+ {
+ /*
+ * if the second machine is null, don't do anything; otherwise,
+ * build the alternation
+ */
+ if (!re_is_machine_null(ctx, rhs))
+ {
+ re_machine new_machine;
+
+ /* build the alternation */
+ re_build_alter(ctx, &new_machine, dest, rhs);
+
+ /* replace the first machine with the alternation */
+ *dest = new_machine;
+ }
+ }
}
-/**
- * Set a bit in a bit vector.
+/* ------------------------------------------------------------------------ */
+/*
+ * Set a bit in a bit vector.
*/
#define re_set_bit(set, bit) \
- (((unsigned char *)(set))[(bit) >> 3] |= (1 << ((bit) & 7)))
+ (((unsigned char *)(set))[(bit) >> 3] |= (1 << ((bit) & 7)))
-/**
- * Test a bit in a bit vector
+/*
+ * Test a bit in a bit vector
*/
#define re_is_bit_set(set, bit) \
- ((((unsigned char *)(set))[(bit) >> 3] & (1 << ((bit) & 7))) != 0)
-
-re_status_t re_context::compile(const char *expr, size_t exprlen, re_machine *result_machine) {
- re_machine cur_machine;
- re_machine alter_machine;
- re_machine new_machine;
- size_t group_stack_level;
- struct {
- re_machine old_cur;
- re_machine old_alter;
- int group_id;
- } group_stack[50];
-
- // reset everything
- reset();
-
- // start out with no current machine and no alternate machine
- cur_machine.build_null_machine();
- alter_machine.build_null_machine();
-
- // nothing on the stack yet
- group_stack_level = 0;
-
- // loop until we run out of expression to parse
- for (; exprlen != 0 ; ++expr, --exprlen) {
- switch(*expr) {
- case '^':
- /*
- * beginning of line - if we're not at the beginning of the
- * current expression (i.e., we already have some
- * concatentations accumulated), treat it as an ordinary
- * character
- */
- if (!cur_machine.isNull())
- goto normal_char;
-
- // build a new start-of-text recognizer
- build_char(&new_machine, RE_TEXT_BEGIN);
-
- /*
- * concatenate it onto the string - note that this can't
- * have any postfix operators
- */
- concat_onto(&cur_machine, &new_machine);
- break;
-
- case '$':
- /*
- * End of line specifier - if there's anything left after
- * the '$' other than a close parens or alternation
- * specifier, great it as a normal character
- */
- if (exprlen > 1
- && (*(expr+1) != ')' && *(expr+1) != '|'))
- goto normal_char;
-
- // build a new end-of-text recognizer
- build_char(&new_machine, RE_TEXT_END);
-
- /*
- * concatenate it onto the string - note that this can't
- * have any postfix operators
- */
- concat_onto(&cur_machine, &new_machine);
- break;
-
- case '(':
- /*
- * Add a nesting level. Push the current machine and
- * alternate machines onto the group stack, and clear
- * everything out for the new group.
- */
- if (group_stack_level > sizeof(group_stack)/sizeof(group_stack[0])) {
- /* we cannot proceed - return an error */
- return RE_STATUS_GROUP_NESTING_TOO_DEEP;
- }
-
- // save the current state on the stack
- group_stack[group_stack_level].old_cur = cur_machine;
- group_stack[group_stack_level].old_alter = alter_machine;
-
- /*
- * Assign the group a group ID - groups are numbered in
- * order of their opening (left) parentheses, so we want to
- * assign a group number now. We won't actually need to
- * know the group number until we get to the matching close
- * paren, but we need to assign it now, so store it in the
- * group stack.
- */
- group_stack[group_stack_level].group_id = _cur_group;
-
- // consume the group number
- _cur_group++;
-
- // push the level
- ++group_stack_level;
-
- // start the new group with empty machines
- cur_machine.build_null_machine();
- alter_machine.build_null_machine();
- break;
-
- case ')':
- // if there's nothing on the stack, ignore this
- if (group_stack_level == 0)
- break;
-
- // take a level off the stack
- --group_stack_level;
-
- /*
- * Remove a nesting level. If we have a pending alternate
- * expression, build the alternation expression. This will
- * leave the entire group expression in alter_machine,
- * regardless of whether an alternation was in progress or
- * not.
- */
- alternate_onto(&alter_machine, &cur_machine);
-
- /*
- * Create a group machine that encloses the group and marks
- * it with a group number. We assigned the group number
- * when we parsed the open paren, so read that group number
- * from the stack.
- *
- * Note that this will leave 'new_machine' with the entire
- * group machine.
- */
- build_group(&new_machine, &alter_machine,
- group_stack[group_stack_level].group_id);
-
- /*
- * Pop the stack - restore the alternation and current
- * machines that were in progress before the group started.
- */
- cur_machine = group_stack[group_stack_level].old_cur;
- alter_machine = group_stack[group_stack_level].old_alter;
-
- /*
- * Check the group expression (in new_machine) for postfix
- * expressions
- */
- goto apply_postfix;
-
- case '|':
- /*
- * Start a new alternation. This ends the current
- * alternation; if we have a previous pending alternate,
- * build an alternation machine out of the previous
- * alternate and the current machine and move that to the
- * alternate; otherwise, simply move the current machine to
- * the pending alternate.
- */
- alternate_onto(&alter_machine, &cur_machine);
-
- /*
- * the alternation starts out with a blank slate, so null
- * out the current machine
- */
- cur_machine.build_null_machine();
- break;
-
- case '%':
- // quoted character - skip the quote mark and see what we have
- ++expr;
- --exprlen;
-
- // check to see if we're at the end of the expression
- if (exprlen == 0) {
- /*
- * end of the string - ignore it, but undo the extra
- * increment of the expression index so that we exit the
- * enclosing loop properly
- */
- --expr;
- ++exprlen;
- break;
- }
-
- // see what we have
- switch(*expr) {
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- // group match - build a new literal group recognizer
- build_group_matcher(&new_machine, (int)(*expr - '1'));
-
- // apply any postfix expression to the group recognizer
- goto apply_postfix;
-
- case '<':
- // build a beginning-of-word recognizer
- build_char(&new_machine, RE_WORD_BEGIN);
-
- // it can't be postfixed - just concatenate it
- concat_onto(&cur_machine, &new_machine);
- break;
-
- case '>':
- // build an end-of-word recognizer */
- build_char(&new_machine, RE_WORD_END);
-
- // it can't be postfixed - just concatenate it
- concat_onto(&cur_machine, &new_machine);
- break;
-
- case 'w':
- // word character
- build_char(&new_machine, RE_WORD_CHAR);
- goto apply_postfix;
-
- case 'W':
- // non-word character
- build_char(&new_machine, RE_NON_WORD_CHAR);
- goto apply_postfix;
-
- case 'b':
- // word boundary
- build_char(&new_machine, RE_WORD_BOUNDARY);
-
- // it can't be postfixed
- concat_onto(&cur_machine, &new_machine);
- break;
-
- case 'B':
- // not a word boundary
- build_char(&new_machine, RE_NON_WORD_BOUNDARY);
-
- // it can't be postfixed
- concat_onto(&cur_machine, &new_machine);
- break;
-
- default:
- // build a new literal character recognizer
- build_char(&new_machine, *expr);
-
- // apply any postfix expression to the character
- goto apply_postfix;
- }
- break;
-
- case '.':
- /*
- * wildcard character - build a single character recognizer
- * for the special wildcard symbol, then go check it for a
- * postfix operator
- */
- build_char(&new_machine, RE_WILDCARD);
- goto apply_postfix;
- break;
-
- case '[': {
- // range expression
- int is_exclusive = false;
- unsigned char set[32];
-
- // clear out the set of characters in the range
- memset(set, 0, sizeof(set));
-
- // first, skip the open bracket
- ++expr;
- --exprlen;
-
- // check to see if starts with the exclusion character
- if (exprlen != 0 && *expr == '^') {
- // skip the exclusion specifier
- ++expr;
- --exprlen;
-
- // note it
- is_exclusive = true;
- }
-
- // if the first character is a ']', include it in the range
- if (exprlen != 0 && *expr == ']') {
- re_set_bit(set, (int)']');
- ++expr;
- --exprlen;
- }
-
- // if the next character is a '-', include it in the range
- if (exprlen != 0 && *expr == '-') {
- re_set_bit(set, (int)'-');
- ++expr;
- --exprlen;
- }
-
- // scan the character set
- while (exprlen != 0 && *expr != ']') {
- int ch;
-
- // note this character
- ch = (int)(unsigned char)*expr;
-
- // set it
- re_set_bit(set, ch);
-
- // skip this character of the expression
- ++expr;
- --exprlen;
-
- // check for a range
- if (exprlen != 0 && *expr == '-') {
- int ch2;
-
- // skip the '-'
- ++expr;
- --exprlen;
- if (exprlen != 0) {
- // get the other end of the range
- ch2 = (int)(unsigned char)*expr;
-
- // skip the second character
- ++expr;
- --exprlen;
-
- // if the range is reversed, swap it
- if (ch > ch2)
- SWAP(ch, ch2);
-
- // fill in the range
- for ( ; ch <= ch2 ; ++ch)
- re_set_bit(set, ch);
- }
- }
- }
-
- // create a character range machine
- build_char_range(&new_machine, set, is_exclusive);
-
- // apply any postfix operator
- goto apply_postfix;
- break;
- }
-
- default:
- normal_char:
- /*
- * it's an ordinary character - build a single character
- * recognizer machine, and then concatenate it onto any
- * existing machine
- */
- build_char(&new_machine, *expr);
-
- apply_postfix:
- /*
- * Check for a postfix operator, and apply it to the machine
- * in 'new_machine' if present. In any case, concatenate
- * the 'new_machine' (modified by a postix operator or not)
- * to the current machien.
- */
- if (exprlen > 1) {
- switch(*(expr+1)) {
- case '*':
- case '+':
- case '?':
- /*
- * We have a postfix closure operator. Build a new
- * closure machine out of 'new_machine'.
- */
- {
- re_machine closure_machine;
-
- // move onto the closure operator
- ++expr;
- --exprlen;
-
- // build the closure machine
- build_closure(&closure_machine, &new_machine, *expr);
-
- // replace the original machine with the closure
- new_machine = closure_machine;
-
- /*
- * skip any redundant closure symbols, keeping
- * only the first one we saw
- */
- while (exprlen > 1 && (*(expr+1) == '?'
- || *(expr+1) == '+'
- || *(expr+1) == '*')) {
- ++expr;
- --exprlen;
- }
- }
- break;
-
- default:
- /* no postfix operator */
- break;
- }
- }
-
- /*
- * Concatenate the new machine onto the current machine
- * under construction.
- */
- concat_onto(&cur_machine, &new_machine);
- break;
- }
- }
-
- // complete any pending alternation
- alternate_onto(&alter_machine, &cur_machine);
-
- // store the resulting machine in the caller's machine descriptor
- *result_machine = alter_machine;
-
- // no errors encountered
- return RE_STATUS_SUCCESS;
+ ((((unsigned char *)(set))[(bit) >> 3] & (1 << ((bit) & 7))) != 0)
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Compile an expression
+ */
+static re_status_t re_compile(re_context *ctx,
+ const char *expr, size_t exprlen,
+ re_machine *result_machine)
+{
+ re_machine cur_machine;
+ re_machine alter_machine;
+ re_machine new_machine;
+ size_t group_stack_level;
+ struct
+ {
+ re_machine old_cur;
+ re_machine old_alter;
+ int group_id;
+ } group_stack[50];
+
+ /* reset everything */
+ re_reset(ctx);
+
+ /* start out with no current machine and no alternate machine */
+ re_build_null_machine(ctx, &cur_machine);
+ re_build_null_machine(ctx, &alter_machine);
+
+ /* nothing on the stack yet */
+ group_stack_level = 0;
+
+ /* loop until we run out of expression to parse */
+ for ( ; exprlen != 0 ; ++expr, --exprlen)
+ {
+ switch(*expr)
+ {
+ case '^':
+ /*
+ * beginning of line - if we're not at the beginning of the
+ * current expression (i.e., we already have some
+ * concatentations accumulated), treat it as an ordinary
+ * character
+ */
+ if (!re_is_machine_null(ctx, &cur_machine))
+ goto normal_char;
+
+ /* build a new start-of-text recognizer */
+ re_build_char(ctx, &new_machine, RE_TEXT_BEGIN);
+
+ /*
+ * concatenate it onto the string - note that this can't
+ * have any postfix operators
+ */
+ re_concat_onto(ctx, &cur_machine, &new_machine);
+ break;
+
+ case '$':
+ /*
+ * End of line specifier - if there's anything left after
+ * the '$' other than a close parens or alternation
+ * specifier, great it as a normal character
+ */
+ if (exprlen > 1
+ && (*(expr+1) != ')' && *(expr+1) != '|'))
+ goto normal_char;
+
+ /* build a new end-of-text recognizer */
+ re_build_char(ctx, &new_machine, RE_TEXT_END);
+
+ /*
+ * concatenate it onto the string - note that this can't
+ * have any postfix operators
+ */
+ re_concat_onto(ctx, &cur_machine, &new_machine);
+ break;
+
+ case '(':
+ /*
+ * Add a nesting level. Push the current machine and
+ * alternate machines onto the group stack, and clear
+ * everything out for the new group.
+ */
+ if (group_stack_level
+ > sizeof(group_stack)/sizeof(group_stack[0]))
+ {
+ /* we cannot proceed - return an error */
+ return RE_STATUS_GROUP_NESTING_TOO_DEEP;
+ }
+
+ /* save the current state on the stack */
+ group_stack[group_stack_level].old_cur = cur_machine;
+ group_stack[group_stack_level].old_alter = alter_machine;
+
+ /*
+ * Assign the group a group ID - groups are numbered in
+ * order of their opening (left) parentheses, so we want to
+ * assign a group number now. We won't actually need to
+ * know the group number until we get to the matching close
+ * paren, but we need to assign it now, so store it in the
+ * group stack.
+ */
+ group_stack[group_stack_level].group_id = ctx->cur_group;
+
+ /* consume the group number */
+ ctx->cur_group++;
+
+ /* push the level */
+ ++group_stack_level;
+
+ /* start the new group with empty machines */
+ re_build_null_machine(ctx, &cur_machine);
+ re_build_null_machine(ctx, &alter_machine);
+ break;
+
+ case ')':
+ /* if there's nothing on the stack, ignore this */
+ if (group_stack_level == 0)
+ break;
+
+ /* take a level off the stack */
+ --group_stack_level;
+
+ /*
+ * Remove a nesting level. If we have a pending alternate
+ * expression, build the alternation expression. This will
+ * leave the entire group expression in alter_machine,
+ * regardless of whether an alternation was in progress or
+ * not.
+ */
+ re_alternate_onto(ctx, &alter_machine, &cur_machine);
+
+ /*
+ * Create a group machine that encloses the group and marks
+ * it with a group number. We assigned the group number
+ * when we parsed the open paren, so read that group number
+ * from the stack.
+ *
+ * Note that this will leave 'new_machine' with the entire
+ * group machine.
+ */
+ re_build_group(ctx, &new_machine, &alter_machine,
+ group_stack[group_stack_level].group_id);
+
+ /*
+ * Pop the stack - restore the alternation and current
+ * machines that were in progress before the group started.
+ */
+ cur_machine = group_stack[group_stack_level].old_cur;
+ alter_machine = group_stack[group_stack_level].old_alter;
+
+ /*
+ * Check the group expression (in new_machine) for postfix
+ * expressions
+ */
+ goto apply_postfix;
+
+ case '|':
+ /*
+ * Start a new alternation. This ends the current
+ * alternation; if we have a previous pending alternate,
+ * build an alternation machine out of the previous
+ * alternate and the current machine and move that to the
+ * alternate; otherwise, simply move the current machine to
+ * the pending alternate.
+ */
+ re_alternate_onto(ctx, &alter_machine, &cur_machine);
+
+ /*
+ * the alternation starts out with a blank slate, so null
+ * out the current machine
+ */
+ re_build_null_machine(ctx, &cur_machine);
+ break;
+
+ case '%':
+ /*
+ * quoted character - skip the quote mark and see what we
+ * have
+ */
+ ++expr;
+ --exprlen;
+
+ /* check to see if we're at the end of the expression */
+ if (exprlen == 0)
+ {
+ /*
+ * end of the string - ignore it, but undo the extra
+ * increment of the expression index so that we exit the
+ * enclosing loop properly
+ */
+ --expr;
+ ++exprlen;
+ break;
+ }
+
+ /* see what we have */
+ switch(*expr)
+ {
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ /* group match - build a new literal group recognizer */
+ re_build_group_matcher(ctx, &new_machine, (int)(*expr - '1'));
+
+ /* apply any postfix expression to the group recognizer */
+ goto apply_postfix;
+
+ case '<':
+ /* build a beginning-of-word recognizer */
+ re_build_char(ctx, &new_machine, RE_WORD_BEGIN);
+
+ /* it can't be postfixed - just concatenate it */
+ re_concat_onto(ctx, &cur_machine, &new_machine);
+ break;
+
+ case '>':
+ /* build an end-of-word recognizer */
+ re_build_char(ctx, &new_machine, RE_WORD_END);
+
+ /* it can't be postfixed - just concatenate it */
+ re_concat_onto(ctx, &cur_machine, &new_machine);
+ break;
+
+ case 'w':
+ /* word character */
+ re_build_char(ctx, &new_machine, RE_WORD_CHAR);
+ goto apply_postfix;
+
+ case 'W':
+ /* non-word character */
+ re_build_char(ctx, &new_machine, RE_NON_WORD_CHAR);
+ goto apply_postfix;
+
+ case 'b':
+ /* word boundary */
+ re_build_char(ctx, &new_machine, RE_WORD_BOUNDARY);
+
+ /* it can't be postfixed */
+ re_concat_onto(ctx, &cur_machine, &new_machine);
+ break;
+
+ case 'B':
+ /* not a word boundary */
+ re_build_char(ctx, &new_machine, RE_NON_WORD_BOUNDARY);
+
+ /* it can't be postfixed */
+ re_concat_onto(ctx, &cur_machine, &new_machine);
+ break;
+
+ default:
+ /* build a new literal character recognizer */
+ re_build_char(ctx, &new_machine, *expr);
+
+ /* apply any postfix expression to the character */
+ goto apply_postfix;
+ }
+ break;
+
+ case '.':
+ /*
+ * wildcard character - build a single character recognizer
+ * for the special wildcard symbol, then go check it for a
+ * postfix operator
+ */
+ re_build_char(ctx, &new_machine, RE_WILDCARD);
+ goto apply_postfix;
+ break;
+
+ case '[':
+ /* range expression */
+ {
+ int is_exclusive = FALSE;
+ unsigned char set[32];
+
+ /* clear out the set of characters in the range */
+ memset(set, 0, sizeof(set));
+
+ /* first, skip the open bracket */
+ ++expr;
+ --exprlen;
+
+ /* check to see if starts with the exclusion character */
+ if (exprlen != 0 && *expr == '^')
+ {
+ /* skip the exclusion specifier */
+ ++expr;
+ --exprlen;
+
+ /* note it */
+ is_exclusive = TRUE;
+ }
+
+ /*
+ * if the first character is a ']', include it in the
+ * range
+ */
+ if (exprlen != 0 && *expr == ']')
+ {
+ re_set_bit(set, (int)']');
+ ++expr;
+ --exprlen;
+ }
+
+ /*
+ * if the next character is a '-', include it in the
+ * range
+ */
+ if (exprlen != 0 && *expr == '-')
+ {
+ re_set_bit(set, (int)'-');
+ ++expr;
+ --exprlen;
+ }
+
+ /* scan the character set */
+ while (exprlen != 0 && *expr != ']')
+ {
+ int ch;
+
+ /* note this character */
+ ch = (int)(unsigned char)*expr;
+
+ /* set it */
+ re_set_bit(set, ch);
+
+ /* skip this character of the expression */
+ ++expr;
+ --exprlen;
+
+ /* check for a range */
+ if (exprlen != 0 && *expr == '-')
+ {
+ int ch2;
+
+ /* skip the '-' */
+ ++expr;
+ --exprlen;
+ if (exprlen != 0)
+ {
+ /* get the other end of the range */
+ ch2 = (int)(unsigned char)*expr;
+
+ /* skip the second character */
+ ++expr;
+ --exprlen;
+
+ /* if the range is reversed, swap it */
+ if (ch > ch2)
+ {
+ int tmp = ch;
+ ch = ch2;
+ ch2 = tmp;
+ }
+
+ /* fill in the range */
+ for ( ; ch <= ch2 ; ++ch)
+ re_set_bit(set, ch);
+ }
+ }
+ }
+
+ /* create a character range machine */
+ re_build_char_range(ctx, &new_machine, set, is_exclusive);
+
+ /* apply any postfix operator */
+ goto apply_postfix;
+ }
+ break;
+
+ default:
+ normal_char:
+ /*
+ * it's an ordinary character - build a single character
+ * recognizer machine, and then concatenate it onto any
+ * existing machine
+ */
+ re_build_char(ctx, &new_machine, *expr);
+
+ apply_postfix:
+ /*
+ * Check for a postfix operator, and apply it to the machine
+ * in 'new_machine' if present. In any case, concatenate
+ * the 'new_machine' (modified by a postix operator or not)
+ * to the current machien.
+ */
+ if (exprlen > 1)
+ {
+ switch(*(expr+1))
+ {
+ case '*':
+ case '+':
+ case '?':
+ /*
+ * We have a postfix closure operator. Build a new
+ * closure machine out of 'new_machine'.
+ */
+ {
+ re_machine closure_machine;
+
+ /* move onto the closure operator */
+ ++expr;
+ --exprlen;
+
+ /* build the closure machine */
+ re_build_closure(ctx, &closure_machine,
+ &new_machine, *expr);
+
+ /* replace the original machine with the closure */
+ new_machine = closure_machine;
+
+ /*
+ * skip any redundant closure symbols, keeping
+ * only the first one we saw
+ */
+ while (exprlen > 1 && (*(expr+1) == '?'
+ || *(expr+1) == '+'
+ || *(expr+1) == '*'))
+ {
+ ++expr;
+ --exprlen;
+ }
+ }
+ break;
+
+ default:
+ /* no postfix operator */
+ break;
+ }
+ }
+
+ /*
+ * Concatenate the new machine onto the current machine
+ * under construction.
+ */
+ re_concat_onto(ctx, &cur_machine, &new_machine);
+ break;
+ }
+ }
+
+ /* complete any pending alternation */
+ re_alternate_onto(ctx, &alter_machine, &cur_machine);
+
+ /* store the resulting machine in the caller's machine descriptor */
+ *result_machine = alter_machine;
+
+ /* no errors encountered */
+ return RE_STATUS_SUCCESS;
}
-void re_context::note_group(re_group_register *regs, re_state_id id, const char *p) {
- int group_index;
-
- /*
- * Check to see if this is a valid state and it's a group marker -
- * if not, there's nothing to do
- */
- if (id == RE_STATE_INVALID
- || !(_tuple_arr[id].flags
- & (RE_STATE_GROUP_BEGIN | RE_STATE_GROUP_END))
- || (group_index = (int)_tuple_arr[id].ch) >= RE_GROUP_REG_CNT)
- return;
-
- // It's a valid group marker - note the appropriate register value
- if ((_tuple_arr[id].flags & RE_STATE_GROUP_BEGIN) != 0)
- regs[group_index].start_ofs = p;
- else
- regs[group_index].end_ofs = p;
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Pattern recognizer
+ */
+
+/*
+ * Note a group position if appropriate
+ */
+static void re_note_group(re_context *ctx, re_group_register *regs,
+ re_state_id id, const char *p)
+{
+ int group_index;
+
+ /*
+ * Check to see if this is a valid state and it's a group marker -
+ * if not, there's nothing to do
+ */
+ if (id == RE_STATE_INVALID
+ || !(ctx->tuple_arr[id].flags
+ & (RE_STATE_GROUP_BEGIN | RE_STATE_GROUP_END))
+ || (group_index = (int)ctx->tuple_arr[id].ch) >= RE_GROUP_REG_CNT)
+ return;
+
+ /*
+ * It's a valid group marker - note the appropriate register value
+ */
+ if ((ctx->tuple_arr[id].flags & RE_STATE_GROUP_BEGIN) != 0)
+ regs[group_index].start_ofs = p;
+ else
+ regs[group_index].end_ofs = p;
}
-bool re_context::is_word_char(char c) const {
- return Common::isAlnum(c);
+/*
+ * Determine if a character is part of a word. We consider letters and
+ * numbers to be word characters.
+ */
+static int re_is_word_char(char c) {
+ return Common::isAlnum((unsigned char)c);
}
-int re_context::match(const char *entire_str, const char *str, size_t origlen,
- const re_machine *machine, re_group_register *regs) {
- re_state_id cur_state;
- const char *p;
- size_t curlen;
-
- // start at the machine's initial state
- cur_state = machine->init;
-
- // start at the beginning of the string
- p = str;
- curlen = origlen;
-
- // note any group involved in the initial state
- note_group(regs, cur_state, p);
-
- /*
- * if we're starting in the final state, immediately return success
- * with a zero-length match
- */
- if (cur_state == machine->final) {
- // return success with a zero-length match
- return 0;
- }
-
- // run the machine
- for (;;) {
- re_tuple *tuple;
-
- // get the tuple for this state
- tuple = &_tuple_arr[cur_state];
-
- // if this is a group state, adjust the group registers
- note_group(regs, cur_state, p);
-
- // see what kind of state we're in
- if (!(tuple->flags & (RE_STATE_GROUP_BEGIN | RE_STATE_GROUP_END))
- && tuple->ch != RE_EPSILON) {
- /*
- * This is a character or group recognizer state. If we
- * match the character or group, continue on to the next
- * state; otherwise, return failure.
- */
- switch(tuple->ch) {
- case RE_GROUP_MATCH_0:
- case RE_GROUP_MATCH_0 + 1:
- case RE_GROUP_MATCH_0 + 2:
- case RE_GROUP_MATCH_0 + 3:
- case RE_GROUP_MATCH_0 + 4:
- case RE_GROUP_MATCH_0 + 5:
- case RE_GROUP_MATCH_0 + 6:
- case RE_GROUP_MATCH_0 + 7:
- case RE_GROUP_MATCH_0 + 8:
- case RE_GROUP_MATCH_0 + 9: {
- int group_num;
- re_group_register *group_reg;
- size_t reg_len;
-
- // it's a group - get the group number
- group_num = tuple->ch - RE_GROUP_MATCH_0;
- group_reg = &regs[group_num];
-
- /*
- * if this register isn't defined, there's nothing
- * to match, so fail
- */
- if (group_reg->start_ofs == 0 || group_reg->end_ofs == 0)
- return -1;
-
- // calculate the length of the register value
- reg_len = group_reg->end_ofs - group_reg->start_ofs;
-
- // if we don't have enough left to match, it fails
- if (curlen < reg_len)
- return -1;
-
- // if the string doesn't match exactly, we fail
- if (memcmp(p, group_reg->start_ofs, reg_len) != 0)
- return -1;
-
- /*
- * It matches exactly - skip the entire length of
- * the register in the source string
- */
- p += reg_len;
- curlen -= reg_len;
- break;
- }
-
- case RE_TEXT_BEGIN:
- /*
- * Match only the exact beginning of the string - if
- * we're anywhere else, this isn't a match. If this
- * succeeds, we don't skip any characters.
- */
- if (p != entire_str)
- return -1;
- break;
-
- case RE_TEXT_END:
- /*
- * Match only the exact end of the string - if we're
- * anywhere else, this isn't a match. Don't skip any
- * characters on success.
- */
- if (curlen != 0)
- return -1;
- break;
-
- case RE_WORD_BEGIN:
- /*
- * if the previous character is a word character, we're
- * not at the beginning of a word
- */
- if (p != entire_str && is_word_char(*(p - 1)))
- return -1;
-
- /*
- * if we're at the end of the string, or the current
- * character isn't the start of a word, we're not at the
- * beginning of a word
- */
- if (curlen == 0 || !is_word_char(*p))
- return -1;
- break;
-
- case RE_WORD_END:
- /*
- * if the current character is a word character, we're not
- * at the end of a word
- */
- if (curlen != 0 && is_word_char(*p))
- return -1;
-
- /*
- * if we're at the beginning of the string, or the
- * previous character is not a word character, we're not
- * at the end of a word
- */
- if (p == entire_str || !is_word_char(*(p - 1)))
- return -1;
- break;
-
- case RE_WORD_CHAR:
- /* if it's not a word character, it's a failure */
- if (curlen == 0 || !is_word_char(*p))
- return -1;
-
- /* skip this character of input */
- ++p;
- --curlen;
- break;
-
- case RE_NON_WORD_CHAR:
- /* if it's a word character, it's a failure */
- if (curlen == 0 || is_word_char(*p))
- return -1;
-
- /* skip the input */
- ++p;
- --curlen;
- break;
-
- case RE_WORD_BOUNDARY:
- case RE_NON_WORD_BOUNDARY:
- {
- int prev_is_word;
- int next_is_word;
- int boundary;
-
- /*
- * Determine if the previous character is a word
- * character -- if we're at the beginning of the
- * string, it's obviously not, otherwise check its
- * classification
- */
- prev_is_word = (p != entire_str
- && is_word_char(*(p - 1)));
-
- /* make the same check for the current character */
- next_is_word = (curlen != 0
- && is_word_char(*p));
-
- /*
- * Determine if this is a boundary - it is if the
- * two states are different
- */
- boundary = ((prev_is_word != 0) ^ (next_is_word != 0));
-
- /*
- * make sure it matches what was desired, and return
- * failure if not
- */
- if ((tuple->ch == RE_WORD_BOUNDARY && !boundary)
- || (tuple->ch == RE_NON_WORD_BOUNDARY && boundary))
- return -1;
- }
- break;
-
- case RE_WILDCARD:
- // make sure we have a character to match
- if (curlen == 0)
- return -1;
-
- // skip this character
- ++p;
- --curlen;
- break;
-
- case RE_RANGE:
- case RE_RANGE_EXCL: {
- int match_val;
-
- // make sure we have a character to match
- if (curlen == 0)
- return -1;
-
- // see if we match
- match_val = re_is_bit_set(tuple->char_range, (int)(unsigned char)*p);
-
- // make sure we got what we wanted
- if ((tuple->ch == RE_RANGE && !match_val)
- || (tuple->ch == RE_RANGE_EXCL && match_val))
- return -1;
-
- // skip this character of the input
- ++p;
- --curlen;
- break;
- }
-
- default:
- // make sure we have an exact match
- if (curlen == 0 || tuple->ch != *p)
- return -1;
-
- // skip this character of the input
- ++p;
- --curlen;
- break;
- }
-
- /*
- * if we got this far, we were successful - move on to the
- * next state
- */
- cur_state = tuple->next_state_1;
- } else if (tuple->next_state_2 == RE_STATE_INVALID) {
- /*
- * We have only one transition, so this state is entirely
- * deterministic. Simply move on to the next state.
- */
- cur_state = tuple->next_state_1;
- } else {
- re_machine sub_machine;
- re_group_register regs1[RE_GROUP_REG_CNT];
- re_group_register regs2[RE_GROUP_REG_CNT];
- int ret1;
- int ret2;
-
- /*
- * This state has two possible transitions, and we don't
- * know which one to take. So, try both, see which one
- * works better, and return the result. Try the first
- * transition first. Note that each separate attempt must
- * use a separate copy of the registers.
- */
- memcpy(regs1, regs, sizeof(regs1));
- sub_machine.init = tuple->next_state_1;
- sub_machine.final = machine->final;
- ret1 = match(entire_str, p, curlen, &sub_machine, regs1);
-
- /*
- * Now try the second transition
- */
- memcpy(regs2, regs, sizeof(regs2));
- sub_machine.init = tuple->next_state_2;
- sub_machine.final = machine->final;
- ret2 = match(entire_str, p, curlen, &sub_machine, regs2);
-
- /*
- * If they both failed, the whole thing failed. Otherwise,
- * return the longer of the two, plus the length we
- * ourselves matched previously. Note that we return the
- * register set from the winning match.
- */
- if (ret1 < 0 && ret2 < 0) {
- // they both failed
- return -1;
- } else if (ret1 > ret2) {
- // use the first register set and result length
- memcpy(regs, regs1, sizeof(regs1));
- return ret1 + (p - str);
- } else {
- // use the second register set and result length
- memcpy(regs, regs2, sizeof(regs2));
- return ret2 + (p - str);
- }
- }
-
- // If we're in the final state, return success
- if (cur_state == machine->final) {
- // finish off any group involved in the final state
- note_group(regs, cur_state, p);
-
- // return the length we matched
- return p - str;
- }
- }
+/*
+ * Match a string to a compiled expression. Returns the length of the
+ * match if successful, or -1 if no match was found.
+ */
+static int re_match(re_context *ctx, const char *entire_str,
+ const char *str, size_t origlen,
+ const re_machine *machine, re_group_register *regs)
+{
+ re_state_id cur_state;
+ const char *p;
+ size_t curlen;
+
+ /* start at the machine's initial state */
+ cur_state = machine->init;
+
+ /* start at the beginning of the string */
+ p = str;
+ curlen = origlen;
+
+ /* note any group involved in the initial state */
+ re_note_group(ctx, regs, cur_state, p);
+
+ /*
+ * if we're starting in the final state, immediately return success
+ * with a zero-length match
+ */
+ if (cur_state == machine->final)
+ {
+ /* return success with a zero-length match */
+ return 0;
+ }
+
+ /* run the machine */
+ for (;;)
+ {
+ re_tuple *tuple;
+
+ /* get the tuple for this state */
+ tuple = &ctx->tuple_arr[cur_state];
+
+ /* if this is a group state, adjust the group registers */
+ re_note_group(ctx, regs, cur_state, p);
+
+ /* see what kind of state we're in */
+ if (!(tuple->flags & (RE_STATE_GROUP_BEGIN | RE_STATE_GROUP_END))
+ && tuple->ch != RE_EPSILON)
+ {
+ /*
+ * This is a character or group recognizer state. If we
+ * match the character or group, continue on to the next
+ * state; otherwise, return failure.
+ */
+ switch(tuple->ch)
+ {
+ case RE_GROUP_MATCH_0:
+ case RE_GROUP_MATCH_0 + 1:
+ case RE_GROUP_MATCH_0 + 2:
+ case RE_GROUP_MATCH_0 + 3:
+ case RE_GROUP_MATCH_0 + 4:
+ case RE_GROUP_MATCH_0 + 5:
+ case RE_GROUP_MATCH_0 + 6:
+ case RE_GROUP_MATCH_0 + 7:
+ case RE_GROUP_MATCH_0 + 8:
+ case RE_GROUP_MATCH_0 + 9:
+ {
+ int group_num;
+ re_group_register *group_reg;
+ size_t reg_len;
+
+ /* it's a group - get the group number */
+ group_num = tuple->ch - RE_GROUP_MATCH_0;
+ group_reg = &regs[group_num];
+
+ /*
+ * if this register isn't defined, there's nothing
+ * to match, so fail
+ */
+ if (group_reg->start_ofs == 0 || group_reg->end_ofs == 0)
+ return -1;
+
+ /* calculate the length of the register value */
+ reg_len = group_reg->end_ofs - group_reg->start_ofs;
+
+ /* if we don't have enough left to match, it fails */
+ if (curlen < reg_len)
+ return -1;
+
+ /* if the string doesn't match exactly, we fail */
+ if (memcmp(p, group_reg->start_ofs, reg_len) != 0)
+ return -1;
+
+ /*
+ * It matches exactly - skip the entire length of
+ * the register in the source string
+ */
+ p += reg_len;
+ curlen -= reg_len;
+ }
+ break;
+
+ case RE_TEXT_BEGIN:
+ /*
+ * Match only the exact beginning of the string - if
+ * we're anywhere else, this isn't a match. If this
+ * succeeds, we don't skip any characters.
+ */
+ if (p != entire_str)
+ return -1;
+ break;
+
+ case RE_TEXT_END:
+ /*
+ * Match only the exact end of the string - if we're
+ * anywhere else, this isn't a match. Don't skip any
+ * characters on success.
+ */
+ if (curlen != 0)
+ return -1;
+ break;
+
+ case RE_WORD_BEGIN:
+ /*
+ * if the previous character is a word character, we're
+ * not at the beginning of a word
+ */
+ if (p != entire_str && re_is_word_char(*(p-1)))
+ return -1;
+
+ /*
+ * if we're at the end of the string, or the current
+ * character isn't the start of a word, we're not at the
+ * beginning of a word
+ */
+ if (curlen == 0 || !re_is_word_char(*p))
+ return -1;
+ break;
+
+ case RE_WORD_END:
+ /*
+ * if the current character is a word character, we're not
+ * at the end of a word
+ */
+ if (curlen != 0 && re_is_word_char(*p))
+ return -1;
+
+ /*
+ * if we're at the beginning of the string, or the
+ * previous character is not a word character, we're not
+ * at the end of a word
+ */
+ if (p == entire_str || !re_is_word_char(*(p-1)))
+ return -1;
+ break;
+
+ case RE_WORD_CHAR:
+ /* if it's not a word character, it's a failure */
+ if (curlen == 0 || !re_is_word_char(*p))
+ return -1;
+
+ /* skip this character of input */
+ ++p;
+ --curlen;
+ break;
+
+ case RE_NON_WORD_CHAR:
+ /* if it's a word character, it's a failure */
+ if (curlen == 0 || re_is_word_char(*p))
+ return -1;
+
+ /* skip the input */
+ ++p;
+ --curlen;
+ break;
+
+ case RE_WORD_BOUNDARY:
+ case RE_NON_WORD_BOUNDARY:
+ {
+ int prev_is_word;
+ int next_is_word;
+ int boundary;
+
+ /*
+ * Determine if the previous character is a word
+ * character -- if we're at the beginning of the
+ * string, it's obviously not, otherwise check its
+ * classification
+ */
+ prev_is_word = (p != entire_str
+ && re_is_word_char(*(p-1)));
+
+ /* make the same check for the current character */
+ next_is_word = (curlen != 0
+ && re_is_word_char(*p));
+
+ /*
+ * Determine if this is a boundary - it is if the
+ * two states are different
+ */
+ boundary = ((prev_is_word != 0) ^ (next_is_word != 0));
+
+ /*
+ * make sure it matches what was desired, and return
+ * failure if not
+ */
+ if ((tuple->ch == RE_WORD_BOUNDARY && !boundary)
+ || (tuple->ch == RE_NON_WORD_BOUNDARY && boundary))
+ return -1;
+ }
+ break;
+
+ case RE_WILDCARD:
+ /* make sure we have a character to match */
+ if (curlen == 0)
+ return -1;
+
+ /* skip this character */
+ ++p;
+ --curlen;
+ break;
+
+ case RE_RANGE:
+ case RE_RANGE_EXCL:
+ {
+ int match;
+
+ /* make sure we have a character to match */
+ if (curlen == 0)
+ return -1;
+
+ /* see if we match */
+ match = re_is_bit_set(tuple->char_range,
+ (int)(unsigned char)*p);
+
+ /* make sure we got what we wanted */
+ if ((tuple->ch == RE_RANGE && !match)
+ || (tuple->ch == RE_RANGE_EXCL && match))
+ return -1;
+
+ /* skip this character of the input */
+ ++p;
+ --curlen;
+ }
+ break;
+
+ default:
+ /* make sure we have an exact match */
+ if (curlen == 0 || tuple->ch != *p)
+ return -1;
+
+ /* skip this character of the input */
+ ++p;
+ --curlen;
+ break;
+ }
+
+ /*
+ * if we got this far, we were successful - move on to the
+ * next state
+ */
+ cur_state = tuple->next_state_1;
+ }
+ else if (tuple->next_state_2 == RE_STATE_INVALID)
+ {
+ /*
+ * We have only one transition, so this state is entirely
+ * deterministic. Simply move on to the next state.
+ */
+ cur_state = tuple->next_state_1;
+ }
+ else
+ {
+ re_machine sub_machine;
+ re_group_register regs1[RE_GROUP_REG_CNT];
+ re_group_register regs2[RE_GROUP_REG_CNT];
+ int ret1;
+ int ret2;
+
+ /*
+ * This state has two possible transitions, and we don't
+ * know which one to take. So, try both, see which one
+ * works better, and return the result. Try the first
+ * transition first. Note that each separate attempt must
+ * use a separate copy of the registers.
+ */
+ memcpy(regs1, regs, sizeof(regs1));
+ sub_machine.init = tuple->next_state_1;
+ sub_machine.final = machine->final;
+ ret1 = re_match(ctx, entire_str, p, curlen, &sub_machine, regs1);
+
+ /*
+ * Now try the second transition
+ */
+ memcpy(regs2, regs, sizeof(regs2));
+ sub_machine.init = tuple->next_state_2;
+ sub_machine.final = machine->final;
+ ret2 = re_match(ctx, entire_str, p, curlen, &sub_machine, regs2);
+
+ /*
+ * If they both failed, the whole thing failed. Otherwise,
+ * return the longer of the two, plus the length we
+ * ourselves matched previously. Note that we return the
+ * register set from the winning match.
+ */
+ if (ret1 < 0 && ret2 < 0)
+ {
+ /* they both failed */
+ return -1;
+ }
+ else if (ret1 > ret2)
+ {
+ /* use the first register set and result length */
+ memcpy(regs, regs1, sizeof(regs1));
+ return ret1 + (p - str);
+ }
+ else
+ {
+ /* use the second register set and result length */
+ memcpy(regs, regs2, sizeof(regs2));
+ return ret2 + (p - str);
+ }
+ }
+
+ /*
+ * If we're in the final state, return success
+ */
+ if (cur_state == machine->final)
+ {
+ /* finish off any group involved in the final state */
+ re_note_group(ctx, regs, cur_state, p);
+
+ /* return the length we matched */
+ return p - str;
+ }
+ }
}
-int re_context::search(const char *str, size_t len, const re_machine *machine,
- re_group_register *regs, int *result_len) {
- int ofs;
-
- /*
- * Starting at the first character in the string, search for the
- * pattern at each subsequent character until we either find the
- * pattern or run out of string to test.
- */
- for (ofs = 0 ; ofs < (int)len ; ++ofs) {
- int matchlen;
-
- // check for a match
- matchlen = match(str, str + ofs, len - ofs, machine, regs);
- if (matchlen >= 0) {
- // we found a match here - return the length and offset
- *result_len = matchlen;
- return ofs;
- }
- }
-
- // we didn't find a match
- return -1;
+/* ------------------------------------------------------------------------ */
+/*
+ * Search for a regular expression within a string. Returns -1 if the
+ * string cannot be found, otherwise returns the offset from the start
+ * of the string to be searched of the start of the first match for the
+ * pattern.
+ */
+static int re_search(re_context *ctx, const char *str, size_t len,
+ const re_machine *machine, re_group_register *regs,
+ int *result_len)
+{
+ int ofs;
+
+ /*
+ * Starting at the first character in the string, search for the
+ * pattern at each subsequent character until we either find the
+ * pattern or run out of string to test.
+ */
+ for (ofs = 0 ; ofs < (int)len ; ++ofs)
+ {
+ int matchlen;
+
+ /* check for a match */
+ matchlen = re_match(ctx, str, str + ofs, len - ofs,
+ machine, regs);
+ if (matchlen >= 0)
+ {
+ /* we found a match here - return the length and offset */
+ *result_len = matchlen;
+ return ofs;
+ }
+ }
+
+ /* we didn't find a match */
+ return -1;
}
-void re_context::save_search_str(const char *str, size_t len) {
- // if the string is empty, this is easy
- if (len == 0) {
- // nothing to store - just save the length and return
- _curlen = 0;
- return;
- }
-
- // if the current buffer isn't big enough, allocate a new one
- if (_strbuf == 0 || _strbufsiz < len) {
- /*
- * free any previous buffer - its contents are no longer
- * important, since we're about to overwrite it with a new
- * string
- */
- if (_strbuf != 0)
- mchfre(_strbuf);
-
- /*
- * allocate a new buffer; round up to the next 256-byte
- * increment to make sure we're not constantly reallocating to
- * random sizes
- */
- _strbufsiz = ((len + 255) & ~255);
-
- // allocate it
- _strbuf = (char *)mchalo(_errctx, _strbufsiz, "regex str");
- }
-
- // copy the string
- memcpy(_strbuf, str, len);
-
- // save the length
- _curlen = len;
+/* ------------------------------------------------------------------------ */
+/*
+ * Make a copy of a search string in our private buffer.
+ */
+static void re_save_search_str(re_context *ctx, const char *str, size_t len)
+{
+ /* if the string is empty, this is easy */
+ if (len == 0)
+ {
+ /* nothing to store - just save the length and return */
+ ctx->curlen = 0;
+ return;
+ }
+
+ /* if the current buffer isn't big enough, allocate a new one */
+ if (ctx->strbuf == 0 || ctx->strbufsiz < len)
+ {
+ /*
+ * free any previous buffer - its contents are no longer
+ * important, since we're about to overwrite it with a new
+ * string
+ */
+ if (ctx->strbuf != 0)
+ mchfre(ctx->strbuf);
+
+ /*
+ * allocate a new buffer; round up to the next 256-byte
+ * increment to make sure we're not constantly reallocating to
+ * random sizes
+ */
+ ctx->strbufsiz = ((len + 255) & ~255);
+
+ /* allocate it */
+ ctx->strbuf = (char *)mchalo(ctx->errctx, ctx->strbufsiz,
+ "regex str");
+ }
+
+ /* copy the string */
+ memcpy(ctx->strbuf, str, len);
+
+ /* save the length */
+ ctx->curlen = len;
}
-int re_context::compile_and_search(const char *pattern, size_t patlen,
- const char *searchstr, size_t searchlen, int *result_len) {
- re_machine machine;
-
- // compile the expression - return failure if we get an error
- if (compile(pattern, patlen, &machine) != RE_STATUS_SUCCESS)
- return -1;
-
- // save the search string in our internal buffer
- save_search_str(searchstr, searchlen);
-
- // clear the group registers
- memset(_regs, 0, sizeof(_regs));
-
- /*
- * search for the pattern in our copy of the string - use the copy
- * so that the group registers stay valid even if the caller
- * deallocates the original string after we return
- */
- return search(_strbuf, _curlen, &machine, _regs, result_len);
+/* ------------------------------------------------------------------------ */
+/*
+ * Compile an expression and search for a match within the given string.
+ * Returns the offset of the match, or -1 if no match was found.
+ */
+int re_compile_and_search(re_context *ctx,
+ const char *pattern, size_t patlen,
+ const char *searchstr, size_t searchlen,
+ int *result_len)
+{
+ re_machine machine;
+
+ /* compile the expression - return failure if we get an error */
+ if (re_compile(ctx, pattern, patlen, &machine) != RE_STATUS_SUCCESS)
+ return -1;
+
+ /* save the search string in our internal buffer */
+ re_save_search_str(ctx, searchstr, searchlen);
+
+ /* clear the group registers */
+ memset(ctx->regs, 0, sizeof(ctx->regs));
+
+ /*
+ * search for the pattern in our copy of the string - use the copy
+ * so that the group registers stay valid even if the caller
+ * deallocates the original string after we return
+ */
+ return re_search(ctx, ctx->strbuf, ctx->curlen, &machine,
+ ctx->regs, result_len);
}
-int re_context::compile_and_match(const char *pattern, size_t patlen,
- const char *searchstr, size_t searchlen) {
- re_machine machine;
+/* ------------------------------------------------------------------------ */
+/*
+ * Compile an expression and check for a match. Returns the length of
+ * the match if we found a match, -1 if we found no match. This is not
+ * a search function; we merely match the leading substring of the given
+ * string to the given pattern.
+ */
+int re_compile_and_match(re_context *ctx,
+ const char *pattern, size_t patlen,
+ const char *searchstr, size_t searchlen)
+{
+ re_machine machine;
- // compile the expression - return failure if we get an error
- if (compile(pattern, patlen, &machine) != RE_STATUS_SUCCESS)
- return 0;
+ /* compile the expression - return failure if we get an error */
+ if (re_compile(ctx, pattern, patlen, &machine) != RE_STATUS_SUCCESS)
+ return FALSE;
- // save the search string in our internal buffer
- save_search_str(searchstr, searchlen);
+ /* save the search string in our internal buffer */
+ re_save_search_str(ctx, searchstr, searchlen);
- // clear the group registers
- memset(_regs, 0, sizeof(_regs));
+ /* clear the group registers */
+ memset(ctx->regs, 0, sizeof(ctx->regs));
- // match the string
- return match(_strbuf, _strbuf, _curlen, &machine, _regs);
+ /* match the string */
+ return re_match(ctx, ctx->strbuf, ctx->strbuf, ctx->curlen,
+ &machine, ctx->regs);
}
+
} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/regex.h b/engines/glk/tads/tads2/regex.h
index cd975e066b..48c2dd0ad4 100644
--- a/engines/glk/tads/tads2/regex.h
+++ b/engines/glk/tads/tads2/regex.h
@@ -24,289 +24,165 @@
#define GLK_TADS_TADS2_REGEX
#include "common/array.h"
-#include "glk/tads/tads2/ler.h"
+#include "glk/tads/tads2/error_handling.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
-/**
- * state ID
- */
+
+/* state ID */
typedef int re_state_id;
-/**
- * invalid state ID - used to mark null machines
- */
+/* invalid state ID - used to mark null machines */
#define RE_STATE_INVALID ((re_state_id)-1)
-/**
- * first valid state ID
- */
+/* first valid state ID */
#define RE_STATE_FIRST_VALID ((re_state_id)0)
-/**
+/* ------------------------------------------------------------------------ */
+/*
* Group register structure. Each register keeps track of the starting
- * and ending offset of the group's text.
+ * and ending offset of the group's text.
*/
-struct re_group_register {
- const char *start_ofs;
- const char *end_ofs;
-};
+typedef struct
+{
+ const char *start_ofs;
+ const char *end_ofs;
+} re_group_register;
-/**
- * number of group registers we keep
- */
+/* number of group registers we keep */
#define RE_GROUP_REG_CNT 10
-/**
- * Denormalized state transition tuple. Each tuple represents the
- * complete set of transitions out of a particular state. A particular
- * state can have one character transition, or two epsilon transitions.
- * Note that we don't need to store the state ID in the tuple, because
- * the state ID is the index of the tuple in an array of state tuples.
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Denormalized state transition tuple. Each tuple represents the
+ * complete set of transitions out of a particular state. A particular
+ * state can have one character transition, or two epsilon transitions.
+ * Note that we don't need to store the state ID in the tuple, because
+ * the state ID is the index of the tuple in an array of state tuples.
*/
-struct re_tuple {
- // the character we must match to transition to the target state
- char ch;
+typedef struct
+{
+ /* the character we must match to transition to the target state */
+ char ch;
- // the target states
- re_state_id next_state_1;
- re_state_id next_state_2;
+ /* the target states */
+ re_state_id next_state_1;
+ re_state_id next_state_2;
- // character range match table, if used
- unsigned char *char_range;
+ /* character range match table, if used */
+ unsigned char *char_range;
- // flags
- byte flags;
-};
+ /* flags */
+ unsigned char flags;
+} re_tuple;
-/**
- * Tuple flags
+/*
+ * Tuple flags
*/
-enum {
- // this state is the start of a group - the 'ch' value is the group ID
- RE_STATE_GROUP_BEGIN = 0x02,
- // this state is the end of a group - 'ch' is the group ID */
- RE_STATE_GROUP_END = 0x04
-};
+/* this state is the start of a group - the 'ch' value is the group ID */
+#define RE_STATE_GROUP_BEGIN 0x02
+
+/* this state is the end of a group - 'ch' is the group ID */
+#define RE_STATE_GROUP_END 0x04
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Regular expression compilation context structure. This tracks the
+ * state of the compilation and stores the resources associated with the
+ * compiled expression.
+ */
+typedef struct
+{
+ /* error context */
+ errcxdef *errctx;
+
+ /* next available state ID */
+ re_state_id next_state;
+
+ /*
+ * The array of transition tuples. We'll allocate this array and
+ * expand it as necessary.
+ */
+ re_tuple *tuple_arr;
-/**
- * Status codes
+ /* number of transition tuples allocated in the array */
+ int tuples_alloc;
+
+ /* current group ID */
+ int cur_group;
+
+ /* group registers */
+ re_group_register regs[RE_GROUP_REG_CNT];
+
+ /*
+ * Buffer for retaining a copy of the last string we scanned. We
+ * retain our own copy of each string, and point the group registers
+ * into this copy rather than the caller's original string -- this
+ * ensures that the group registers remain valid even after the
+ * caller has deallocated the original string.
+ */
+ char *strbuf;
+
+ /* length of the string currently in the buffer */
+ size_t curlen;
+
+ /* size of the buffer allocated to strbuf */
+ size_t strbufsiz;
+} re_context;
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Status codes
*/
-typedef enum {
- // success
- RE_STATUS_SUCCESS = 0,
+typedef enum
+{
+ /* success */
+ RE_STATUS_SUCCESS = 0,
- // compilation error - group nesting too deep
- RE_STATUS_GROUP_NESTING_TOO_DEEP
+ /* compilation error - group nesting too deep */
+ RE_STATUS_GROUP_NESTING_TOO_DEEP
} re_status_t;
-/**
- * Regular expression compilation. This tracks the state of the compilation and
- * stores the resources associated with the compiled expression.
+/* ------------------------------------------------------------------------ */
+/*
+ * Initialize the context. The memory for the context structure itself
+ * must be allocated and maintained by the caller.
+ */
+void re_init(re_context *ctx, errcxdef *errctx);
+
+/*
+ * Delete the context - frees structures associated with the context.
+ * Does NOT free the memory used by the context structure itself.
+ */
+void re_delete(re_context *ctx);
+
+/*
+ * Compile an expression and search for a match within the given string.
+ * Returns the offset of the match, or -1 if no match was found.
+ */
+int re_compile_and_search(re_context *ctx,
+ const char *pattern, size_t patlen,
+ const char *searchstr, size_t searchlen,
+ int *result_len);
+
+/*
+ * Compile an expression and check for a match. Returns the length of
+ * the match if we found a match, -1 if we found no match. This is not
+ * a search function; we merely match the leading substring of the given
+ * string to the given pattern.
*/
-class re_context {
- /**
- * A machine description. Machines are fully described by their initial
- * and final state ID's.
- */
- struct re_machine {
- re_state_id init; ///< the machine's initial state
- re_state_id final; ///< the machine's final state
-
- re_machine() : init(0), final(0) {}
-
- /**
- * Build a null machine
- */
- void build_null_machine() {
- init = final = RE_STATE_INVALID;
- }
-
- /**
- * Determine if a machine is null
- */
- bool isNull() const {
- return (init == RE_STATE_INVALID);
- }
- };
-private:
- /**
- * Reset compiler - clears states and tuples
- */
- void reset();
-
- /**
- * Set a transition from a state to a given destination state
- */
- void set_trans(re_state_id id, re_state_id dest_id, char ch);
-
- /**
- * Initialize a new machine, giving it an initial and final state
- */
- void init_machine(re_machine *machine);
-
- /**
- * Build a character recognizer
- */
- void build_char(re_machine *machine, char ch);
-
- /**
- * Build a character range recognizer. 'range' is a 256-bit (32-byte) bit vector.
- */
- void build_char_range(re_machine *machine, unsigned char *range, int exclusion);
-
- /**
- * Build a group recognizer. This is almost the same as a character
- * recognizer, but matches a previous group rather than a literal character.
- */
- void build_group_matcher(re_machine *machine, int group_num);
-
- /**
- * Build a concatenation recognizer
- */
- void build_concat(re_machine *new_machine, re_machine *lhs, re_machine *rhs);
-
- /**
- * Build a group machine. sub_machine contains the machine that
- * expresses the group's contents; we'll fill in new_machine with a
- * newly-created machine that encloses and marks the group.
- */
- void build_group(re_machine *new_machine, re_machine *sub_machine, int group_id);
-
- /**
- * Build an alternation recognizer
- */
- void build_alter(re_machine *new_machine, re_machine *lhs, re_machine *rhs);
-
- /**
- * Build a closure recognizer
- */
- void build_closure(re_machine *new_machine, re_machine *sub, char specifier);
-
- /**
- * Concatenate the second machine onto the first machine, replacing the
- * first machine with the resulting machine. If the first machine is a
- * null machine (created with re_build_null_machine), we'll simply copy
- * the second machine into the first.
- */
- void concat_onto(re_machine *dest, re_machine *rhs);
-
- /**
- * Alternate the second machine onto the first machine, replacing the
- * first machine with the resulting machine. If the first machine is a
- * null machine, this simply replaces the first machine with the second
- * machine. If the second machine is null, this simply leaves the first
- * machine unchanged.
- */
- void alternate_onto(re_machine *dest, re_machine *rhs);
-
- /**
- * Compile an expression
- */
- re_status_t compile(const char *expr, size_t exprlen, re_machine *result_machine);
-
- /**
- * Note a group position if appropriate
- */
- void note_group(re_group_register *regs, re_state_id id, const char *p);
-
- /**
- * Determine if a character is part of a word. We consider letters and
- * numbers to be word characters.
- */
- bool is_word_char(char c) const;
-
- /**
- * Match a string to a compiled expression. Returns the length of the
- * match if successful, or -1 if no match was found.
- */
- int match(const char *entire_str, const char *str, size_t origlen,
- const re_machine *machine, re_group_register *regs);
-
- /**
- * Search for a regular expression within a string. Returns -1 if the string
- * cannot be found, otherwise returns the offset from the start of the string
- * to be searched of the start of the first match for the pattern.
- */
- int search(const char *str, size_t len, const re_machine *machine,
- re_group_register *regs, int *result_len);
-
- /**
- * Make a copy of a search string in our private buffer.
- */
- void save_search_str(const char *str, size_t len);
-public:
- errcxdef *_errctx; ///< error context
- re_state_id _next_state; ///< next available state ID
-
- /**
- * The array of transition tuples. We'll allocate this array and
- * expand it as necessary.
- */
- Common::Array<re_tuple> _tuple_arr;
-
- // current group ID
- int _cur_group;
-
- // group registers
- re_group_register _regs[RE_GROUP_REG_CNT];
-
- /**
- * Buffer for retaining a copy of the last string we scanned. We
- * retain our own copy of each string, and point the group registers
- * into this copy rather than the caller's original string -- this
- * ensures that the group registers remain valid even after the
- * caller has deallocated the original string.
- */
- char *_strbuf;
-
- /**
- * length of the string currently in the buffer
- */
- size_t _curlen;
-
- /**
- * size of the buffer allocated to strbuf
- */
- size_t _strbufsiz;
-public:
- /**
- * Constructor. The memory for the context structure itself
- * must be allocated and maintained by the caller.
- */
- re_context(errcxdef *errctx);
-
- /**
- * Destructor
- */
- ~re_context();
-
- /**
- * Allocate a new state ID
- */
- re_state_id alloc_state();
-
- /**
- * Compile an expression and search for a match within the given string.
- * Returns the offset of the match, or -1 if no match was found.
- */
- int compile_and_search(const char *pattern, size_t patlen,
- const char *searchstr, size_t searchlen, int *result_len);
-
- /**
- * Compile an expression and check for a match. Returns the length of the match
- * if we found a match, -1 if we found no match. This is not a search function;
- * we merely match the leading substring of the given string to the given pattern.
- */
- int compile_and_match(const char *pattern, size_t patlen,
- const char *searchstr, size_t searchlen);
-};
+int re_compile_and_match(re_context *ctx,
+ const char *pattern, size_t patlen,
+ const char *searchstr, size_t searchlen);
} // End of namespace TADS2
} // End of namespace TADS
diff --git a/engines/glk/tads/tads2/run.cpp b/engines/glk/tads/tads2/run.cpp
new file mode 100644
index 0000000000..5123d650fe
--- /dev/null
+++ b/engines/glk/tads/tads2/run.cpp
@@ -0,0 +1,52 @@
+/* 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/run.h"
+#include "glk/tads/tads2/data.h"
+#include "glk/tads/tads2/vocabulary.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+int runsdef::runsiz() const {
+ switch (runstyp) {
+ case DAT_NUMBER:
+ return 4;
+ case DAT_SSTRING:
+ case DAT_LIST:
+ return osrp2(runsv.runsvstr);
+ case DAT_PROPNUM:
+ case DAT_OBJECT:
+ case DAT_FNADDR:
+ return 2;
+ default:
+ return 0;
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/run.h b/engines/glk/tads/tads2/run.h
new file mode 100644
index 0000000000..b6694bf523
--- /dev/null
+++ b/engines/glk/tads/tads2/run.h
@@ -0,0 +1,393 @@
+/* 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.
+ *
+ */
+
+/* Definitions for code execution
+ *
+ * The preprocessor symbol RUNFAST can be defined if run - time checking
+ * of stack overflow, stack underflow, and other unusual but potentially
+ * dangerous conditions is to be turned off.This will result in somewhat
+ * faster run-time performance, but run - time errors could be disastrous.
+ */
+
+#ifndef GLK_TADS_TADS2_RUN
+#define GLK_TADS_TADS2_RUN
+
+#include "common/scummsys.h"
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/debug.h"
+#include "glk/tads/tads2/object.h"
+#include "glk/tads/tads2/memory_cache.h"
+#include "glk/tads/tads2/memory_cache_swap.h"
+#include "glk/tads/tads2/opcode_defs.h"
+#include "glk/tads/tads2/property.h"
+#include "glk/tads/tads2/text_io.h"
+#include "glk/tads/tads2/tokenizer.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* Forward declarations */
+struct runcxdef;
+struct runufdef;
+struct voccxdef;
+
+/**
+ * Stack element - the stack is an array of these structures
+ */
+struct runsdef {
+ uchar runstyp; /* type of element */
+ union {
+ long runsvnum; /* numeric value */
+ objnum runsvobj; /* object value */
+ prpnum runsvprp; /* property number value */
+ uchar *runsvstr; /* string/list value */
+ } runsv;
+
+
+ /**
+ * Determine size of a data item
+ */
+ int runsiz() const;
+};
+
+/**
+ * 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
+ */
+struct runuxdef {
+ runcxdef *runuxctx; /* run-time context */
+ runufdef *runuxvec; /* vector of functions */
+ int runuxargc; /* count of arguments to function */
+};
+
+/**
+ * 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 */
+};
+
+/**
+ * Execution context
+ */
+struct runcxdef {
+ errcxdef *runcxerr; /* error management context */
+ mcmcxdef *runcxmem; /* cache manager context for object references */
+ runsdef *runcxstk; /* base of interpreter stack */
+ runsdef *runcxstop; /* top of stack */
+ runsdef *runcxsp; /* current stack pointer (stack grows upwards) */
+ runsdef *runcxbp; /* base pointer */
+ uchar *runcxheap; /* run-time variable-length object heap */
+ uchar *runcxhp; /* current heap pointer */
+ uchar *runcxhtop; /* top of heap */
+ objucxdef *runcxundo; /* undo context */
+ tiocxdef *runcxtio; /* text I/O context */
+ 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 */
+ void (*runcxdmd)(void *ctx, objnum obj, prpnum prp);
+ /* demand-loader callback function */
+ void *runcxdmc; /* demand-loader callback context */
+ runxdef *runcxext; /* external function array */
+ int runcxexc; /* count of external functions */
+ 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);
+};
+
+/* top level runpprop, when caller is not executing in an object */
+/* void runppr(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)
+
+/* discard top element on stack */
+/* void rundisc(runcxdef *ctx); */
+#define rundisc(ctx) (runstkund(ctx), (--((ctx)->runcxsp)))
+
+/* pop the top element on the stack */
+/* void runpop(runsdef *val); */
+#define runpop(ctx, v) \
+ (runstkund(ctx), memcpy(v, (--((ctx)->runcxsp)), (size_t)sizeof(runsdef)))
+
+/* pop a numeric value, signalling an error if not a number */
+/* long runpopnum(runcxdef *ctx); */
+#define runpopnum(ctx) \
+ (runstkund(ctx), ((--((ctx)->runcxsp))->runstyp!=DAT_NUMBER ? \
+ (runsig(ctx,ERR_REQNUM), (long)0) : \
+ ((ctx)->runcxsp->runsv.runsvnum)))
+
+/* pop an object, signalling an error if not an object */
+/* objnum runpopobj(runcxdef *ctx); */
+#define runpopobj(ctx) \
+ (runstkund(ctx), ((--(ctx)->runcxsp))->runstyp!=DAT_OBJECT ? \
+ (runsig(ctx,ERR_REQVOB), (objnum)0) : \
+ ((ctx)->runcxsp->runsv.runsvobj))
+
+/* pop an object or nil - returns MCMONINV if the value is nil */
+#define runpopobjnil(ctx) \
+ (runstkund(ctx), ((--(ctx)->runcxsp))->runstyp==DAT_OBJECT ? \
+ ((ctx)->runcxsp->runsv.runsvobj) : \
+ ((ctx)->runcxsp->runstyp==DAT_NIL ? MCMONINV : \
+ (runsig(ctx,ERR_REQVOB), (objnum)0)))
+
+/* pop a list, signalling an error if not a list */
+/* uchar *runpoplst(runcxdef *ctx); */
+#define runpoplst(ctx) \
+ (runstkund(ctx), ((--(ctx)->runcxsp))->runstyp!=DAT_LIST ? \
+ (runsig(ctx,ERR_REQVLS), (uchar *)0) : \
+ (uchar *)((ctx)->runcxsp->runsv.runsvstr))
+
+/* pop a property number, signalling an error if not a property number */
+/* prpnum runpopprp(runcxdef *ctx); */
+#define runpopprp(ctx) \
+ (runstkund(ctx), ((--(ctx)->runcxsp))->runstyp!=DAT_PROPNUM ? \
+ (runsig(ctx,ERR_REQVPR), (prpnum)0) : \
+ ((ctx)->runcxsp->runsv.runsvprp))
+
+
+/* pop function pointer */
+/* objnum runpopfn(runcxdef *ctx); */
+#define runpopfn(ctx) \
+ ((objnum)(runstkund(ctx), ((--(ctx)->runcxsp))->runstyp!=DAT_FNADDR ? \
+ (runsig(ctx,ERR_REQVFN), (objnum)0) : \
+ ((ctx)->runcxsp->runsv.runsvobj)))
+
+/* pop a string value */
+/* char *runpopstr(runcxdef *ctx); */
+#define runpopstr(ctx) \
+ (runstkund(ctx), ((--((ctx)->runcxsp))->runstyp!=DAT_SSTRING ? \
+ (runsig(ctx,ERR_REQSTR), (uchar *)0) : \
+ ((ctx)->runcxsp->runsv.runsvstr)))
+
+
+/* pop a logical value - TRUE for DAT_TRUE, FALSE for DAT_NIL */
+/* int runpoplog(runcxdef *ctx); */
+#define runpoplog(ctx) \
+ ((--((ctx)->runcxsp))->runstyp==DAT_TRUE ? TRUE : \
+ (ctx)->runcxsp->runstyp==DAT_NIL ? FALSE : \
+ (runsig(ctx, ERR_REQLOG), 0))
+
+/* get type of top of stack */
+/* int runtostyp(runcxdef *ctx); */
+#define runtostyp(ctx) (((ctx)->runcxsp - 1)->runstyp)
+
+/* determine if top of stack is logical value (returns TRUE if so) */
+/* int runtoslog(runcxdef *ctx); */
+#define runtoslog(ctx) \
+ (runtostyp(ctx) == DAT_TRUE || runtostyp(ctx) == DAT_NIL)
+
+/* convert C logical to TADS logical (TRUE->DAT_TRUE, FALSE->DAT_NIL) */
+/* int runclog(int log); */
+#define runclog(l) ((l) ? DAT_TRUE : DAT_NIL)
+
+
+/*
+ * Check to ensure we have enough arguments to pass to a function or method
+ * call - this simply ensures we have enough data in the current frame.
+ * This is important because the called function will be able to write to
+ * our frame. If we don't have enough arguments, we'll push enough 'nil'
+ * values to meet the need.
+ */
+#define runcheckargc(ctx, nargc) \
+ while ((ctx)->runcxsp - (ctx)->runcxbp < *(nargc)) \
+ runpnil(ctx)
+
+#ifdef RUNFAST
+# define runstkovf(ctx) (DISCARD 0)
+# define runstkund(ctx) (DISCARD 0)
+#else /* RUNFAST */
+# define runstkovf(ctx) \
+ ((ctx)->runcxsp >= (ctx)->runcxstop ? (runsig(ctx, ERR_STKOVF), \
+ DISCARD 0) : DISCARD 0)
+# define runstkund(ctx) \
+ ((ctx)->runcxsp == (ctx)->runcxstk ? runsig(ctx, ERR_STKUND), \
+ DISCARD 0 : DISCARD 0)
+#endif /* RUNFAST */
+
+/* reserve space in heap, collecting garbage if necessary */
+/* void runhres(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),\
+ DISCARD 0))
+
+/* reserve space, with various amounts of saving */
+#define runhres1(ctx, siz, below, val1) \
+ ((uint)((ctx)->runcxhtop - (ctx)->runcxhp) > (uint)(siz) ? DISCARD 0 : \
+ (runhcmp(ctx, siz, below, val1, (runsdef *)0, (runsdef *)0), DISCARD 0))
+
+#define runhres2(ctx, siz, below, val1, val2) \
+ ((uint)((ctx)->runcxhtop - (ctx)->runcxhp) > (uint)(siz) ? DISCARD 0 : \
+ (runhcmp(ctx, siz, below, val1, val2, (runsdef *)0), DISCARD 0))
+
+#define runhres3(ctx, siz, below, val1, val2, val3) \
+ ((uint)((ctx)->runcxhtop - (ctx)->runcxhp) > (uint)(siz) ? DISCARD 0 : \
+ (runhcmp(ctx, siz, below, val1, val2, val3), DISCARD 0))
+
+
+/* leave a stack frame, removing arguments */
+/* void runleave(uint parms); */
+#define runleave(ctx, parms) \
+ (((ctx)->runcxsp = (ctx)->runcxbp), \
+ ((ctx)->runcxbp = (runsdef *)((--((ctx)->runcxsp))->runsv.runsvstr)), \
+ ((ctx)->runcxsp -= (parms)))
+
+/* reset run-time: throw away entire stack and heap */
+/* void runrst(runcxdef *ctx); */
+#define runrst(ctx) (((ctx)->runcxsp = (ctx)->runcxstk), \
+ ((ctx)->runcxhp = (ctx)->runcxheap), \
+ dbgrst(ctx->runcxdbg))
+
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/tads2.cpp b/engines/glk/tads/tads2/tads2.cpp
index a772b44aed..b391857613 100644
--- a/engines/glk/tads/tads2/tads2.cpp
+++ b/engines/glk/tads/tads2/tads2.cpp
@@ -21,13 +21,18 @@
*/
#include "glk/tads/tads2/tads2.h"
+#include "glk/tads/tads2/built_in.h"
+#include "glk/tads/tads2/debug.h"
+#include "glk/tads/tads2/file_io.h"
+#include "glk/tads/tads2/post_compilation.h"
+#include "glk/tads/tads2/run.h"
+#include "glk/tads/tads2/vocabulary.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
-TADS2::TADS2(OSystem *syst, const GlkGameDescription &gameDesc) : OS(syst, gameDesc) {
- cmap_init_default();
+TADS2::TADS2(OSystem *syst, const GlkGameDescription &gameDesc) : TADS(syst, gameDesc) {
}
void TADS2::runGame() {
@@ -35,7 +40,7 @@ void TADS2::runGame() {
errctx.errcxlgc = &errctx;
errctx.errcxfp = nullptr;
errctx.errcxofs = 0;
- errctx.errcxappctx = this;
+ errctx.errcxappctx = nullptr;
/* copyright-date-string */
#ifdef T2_COPYRIGHT_NOTICE
@@ -48,13 +53,519 @@ void TADS2::runGame() {
#endif
trdmain1(&errctx);
-
- // pause before exiting if the OS desires it
- os_expause();
}
void TADS2::trdmain1(errcxdef *errctx) {
+ osfildef *swapfp = (osfildef *)0;
+ runcxdef runctx;
+ bifcxdef bifctx;
+ voccxdef vocctx;
+ void(*bif[100])(bifcxdef *, int);
+ mcmcxdef *mctx = 0;
+ mcmcx1def *globalctx = 0;
+ dbgcxdef dbg;
+ supcxdef supctx;
+ char *swapname = 0;
+ char swapbuf[OSFNMAX];
+ char **argp;
+ char *arg;
+ char *infile;
+ char infile_abs[OSFNMAX]; /* fully-qualified input file name */
+ char infile_path[OSFNMAX]; /* absolute path to input file */
+ char *exefile; /* try with executable file if no infile */
+ ulong swapsize = 0xffffffffL; /* allow unlimited swap space */
+ int swapena = OS_DEFAULT_SWAP_ENABLED; /* swapping enabled? */
+ int i;
+ int pause = FALSE; /* pause after finishing game */
+ fiolcxdef fiolctx;
+ noreg int loadopen = FALSE;
+ char inbuf[OSFNMAX];
+ ulong cachelimit = 0xffffffff;
+ ushort undosiz = TRD_UNDOSIZ; /* default undo context size 16k */
+ objucxdef *undoptr = 0;
+ uint flags; /* flags used to write the file we're reading */
+ objnum preinit; /* preinit object, if we need to execute it */
+ uint heapsiz = TRD_HEAPSIZ;
+ uint stksiz = TRD_STKSIZ;
+ runsdef *mystack;
+ uchar *myheap;
+ extern osfildef *cmdfile; /* hacky v1 qa interface - command log fp */
+ extern osfildef *logfp; /* hacky v1 qa interface - output log fp */
+ int preload = FALSE; /* TRUE => preload all objects */
+ ulong totsize;
+ extern voccxdef *main_voc_ctx;
+ int safety_read, safety_write; /* file I/O safety level */
+ char *restore_file = 0; /* .SAV file to restore */
+ char *charmap = 0; /* character map file */
+ int charmap_none; /* explicitly do not use a character set */
+ int doublespace = TRUE; /* formatter double-space setting */
+#ifdef TODO
+ NOREG((&loadopen))
+
+ /* initialize the output formatter */
+ out_init();
+
+ /* set safety level to 2 by default - read any/write current dir only */
+ safety_read = safety_write = 2;
+
+ /* no -ctab- yet */
+ charmap_none = FALSE;
+
+ /* parse arguments */
+ for (i = 1, argp = argv + 1; i < argc; ++argp, ++i)
+ {
+ arg = *argp;
+ if (*arg == '-')
+ {
+ switch (*(arg + 1))
+ {
+ case 'c':
+ if (!strcmp(arg + 1, "ctab"))
+ {
+ /* get the character mapping table */
+ charmap = cmdarg(ec, &argp, &i, argc, 4, trdusage);
+ }
+ else if (!strcmp(arg + 1, "ctab-"))
+ {
+ /* use the default mapping */
+ charmap_none = TRUE;
+ }
+ else
+ trdusage(ec);
+ break;
+
+ case 'r':
+ /* restore a game */
+ restore_file = cmdarg(ec, &argp, &i, argc, 1, trdusage);
+ break;
+
+ case 'i':
+ qasopn(cmdarg(ec, &argp, &i, argc, 1, trdusage), TRUE);
+ break;
+
+ case 'o':
+ cmdfile = osfopwt(cmdarg(ec, &argp, &i, argc, 1, trdusage),
+ OSFTCMD);
+ break;
+
+ case 'l':
+ logfp = osfopwt(cmdarg(ec, &argp, &i, argc, 1, trdusage),
+ OSFTCMD);
+ break;
+
+ case 'p':
+ if (!stricmp(arg, "-plain"))
+ {
+ os_plain();
+ break;
+ }
+ pause = cmdtog(ec, pause, arg, 1, trdusage);
+ break;
+
+ case 'd':
+ if (!strnicmp(arg, "-double", 7))
+ {
+ /* get the argument value */
+ doublespace = cmdtog(ec, doublespace, arg, 6, trdusage);
+
+ /* set the double-space mode in the formatter */
+ out_set_doublespace(doublespace);
+ break;
+ }
+ break;
+
+ case 's':
+ {
+ char *p;
+
+ /* get the option */
+ p = cmdarg(ec, &argp, &i, argc, 1, trdusage);
+
+ /* if they're asking for help, display detailed usage */
+ if (*p == '?')
+ trdusage_s(ec);
+
+ /* get the safety level from the argument */
+ safety_read = *p - '0';
+ safety_write = (*(p + 1) != '\0' ? *(p + 1) - '0' :
+ safety_read);
+
+ /* range-check the values */
+ if (safety_read < 0 || safety_read > 4
+ || safety_write < 0 || safety_write > 4)
+ trdusage_s(ec);
+
+ /* tell the host system about the setting */
+ if (appctx != 0 && appctx->set_io_safety_level != 0)
+ (*appctx->set_io_safety_level)
+ (appctx->io_safety_level_ctx,
+ safety_read, safety_write);
+ }
+ break;
+
+ case 'm':
+ switch (*(arg + 2))
+ {
+ case 's':
+ stksiz = atoi(cmdarg(ec, &argp, &i, argc, 2, trdusage));
+ break;
+
+ case 'h':
+ heapsiz = atoi(cmdarg(ec, &argp, &i, argc, 2, trdusage));
+ break;
+
+ default:
+ cachelimit = atol(cmdarg(ec, &argp, &i, argc, 1,
+ trdusage));
+ break;
+ }
+ break;
+
+ case 't':
+ /* swap file options: -tf file, -ts size, -t- (no swap) */
+ switch (*(arg + 2))
+ {
+ case 'f':
+ swapname = cmdarg(ec, &argp, &i, argc, 2, trdusage);
+ break;
+
+ case 's':
+ swapsize = atol(cmdarg(ec, &argp, &i, argc, 2, trdusage));
+ break;
+
+ case 'p':
+ preload = cmdtog(ec, preload, arg, 2, trdusage);
+ break;
+
+ default:
+ swapena = cmdtog(ec, swapena, arg, 1, trdusage);
+ break;
+ }
+ break;
+
+ case 'u':
+ undosiz = atoi(cmdarg(ec, &argp, &i, argc, 1, trdusage));
+ break;
+
+ default:
+ trdusage(ec);
+ }
+ }
+ else break;
+ }
+
+ /* presume we won't take the .gam from the application executable */
+ exefile = 0;
+
+ /* get input name argument, and make sure it's the last argument */
+ if (i == argc)
+ {
+ osfildef *fp;
+ ulong curpos;
+ ulong endpos;
+ int use_exe;
+
+ /*
+ * There's no input name argument, so we need to find the game
+ * to play some other way. First, check to see if we have a
+ * game to restore, and if so whether it has the .GAM name
+ * encoded into it. Next, look to see if there's a game
+ * attached to the executable file; if so, use it. If not, see
+ * if the host system wants to provide a name through its
+ * callback.
+ */
+
+ /* presume we won't find a game attached to the executable file */
+ infile = 0;
+ use_exe = FALSE;
+
+ /*
+ * see if we have a saved game to restore, and it specifies the
+ * GAM file that saved it
+ */
+ if (restore_file != 0)
+ {
+ /* try getting the game name from the restore file */
+ if (fiorso_getgame(restore_file, inbuf, sizeof(inbuf)))
+ {
+ /* got it - use this file */
+ infile = inbuf;
+ }
+ }
+
+ /*
+ * it that didn't work, try to read from os-dependent part of
+ * program being executed
+ */
+ if (infile == 0)
+ {
+ /* try opening the executable file */
+ exefile = (argv && argv[0] ? argv[0] : "TRX");
+ fp = os_exeseek(exefile, "TGAM");
+ if (fp != 0)
+ {
+ /* see if there's a game file attached to the executable */
+ curpos = osfpos(fp);
+ osfseek(fp, 0L, OSFSK_END);
+ endpos = osfpos(fp);
+ osfcls(fp);
+
+ /* if we found it, use it */
+ if (endpos != curpos)
+ use_exe = TRUE;
+ }
+ }
+
+ /*
+ * if we didn't find a game in the executable, try the host
+ * system callback
+ */
+ if (infile == 0 && !use_exe)
+ {
+ /*
+ * ask the host system callback what to do - if we don't
+ * have a host system callback, or the callback
+ */
+ if (appctx != 0 && appctx->get_game_name != 0)
+ {
+ /* call the host system callback */
+ if ((*appctx->get_game_name)(appctx->get_game_name_ctx,
+ inbuf, sizeof(inbuf)))
+ {
+ /* the host system provided a name - use it */
+ infile = inbuf;
+ }
+ else
+ {
+ /*
+ * the host didn't provide a name - simply display a
+ * message indicating that no game file has been
+ * chosen, and return
+ */
+ trdptf("\n");
+ trdptf("(No game has been selected.)\n");
+ return;
+ }
+ }
+ else
+ {
+ /*
+ * we've run out of ways to get a filename - give the
+ * user the usage message and quit
+ */
+ trdusage(ec);
+ }
+ }
+ }
+ else
+ {
+ infile = *argp;
+ if (i + 1 != argc)
+ trdusage(ec);
+#ifndef OS_HATES_EXTENSIONS
+ /*
+ * If original name exists, use it; otherwise, try adding .GAM.
+ * Note that this code is ifdef'd so that platforms that don't
+ * use filename extensions in the manner conventional for DOS
+ * and Unix won't use this code.
+ */
+ if (osfacc(infile))
+ {
+ strcpy(inbuf, infile);
+ os_defext(inbuf, "gam");
+ infile = inbuf;
+ }
+#endif /* !defined(OS_HATES_EXTENSIONS) */
+ }
+
+ /* open up the swap file */
+ if (swapena && swapsize)
+ {
+ swapfp = os_create_tempfile(swapname, swapbuf);
+ if (swapname == 0) swapname = swapbuf;
+ if (swapfp == 0) errsig(ec, ERR_OPSWAP);
+ }
+
+ /* load the character map */
+ if (charmap_none)
+ cmap_override();
+ else if (cmap_load(charmap))
+ errsig(ec, ERR_INVCMAP);
+
+ ERRBEGIN(ec)
+
+ /* initialize cache manager context */
+ globalctx = mcmini(cachelimit, 128, swapsize, swapfp, swapname, ec);
+ mctx = mcmcini(globalctx, 128, fioldobj, &fiolctx,
+ objrevert, (void *)0);
+ mctx->mcmcxrvc = mctx;
+
+ /* set up an undo context */
+ if (undosiz)
+ undoptr = objuini(mctx, undosiz, vocdundo, vocdusz, &vocctx);
+ else
+ undoptr = (objucxdef *)0;
+
+ /* set up vocabulary context */
+ vocini(&vocctx, ec, mctx, &runctx, undoptr, 100, 100, 200);
+
+ /*
+ * save a pointer to the voc context globally, so that certain
+ * external routines (such as Unix-style signal handlers) can reach
+ * it
+ */
+ main_voc_ctx = &vocctx;
+
+ /* allocate stack and heap */
+ totsize = (ulong)stksiz * (ulong)sizeof(runsdef);
+ if (totsize != (size_t)totsize)
+ errsig1(ec, ERR_STKSIZE, ERRTINT, (uint)(65535 / sizeof(runsdef)));
+ mystack = (runsdef *)mchalo(ec, (size_t)totsize, "runtime stack");
+ myheap = mchalo(ec, heapsiz, "runtime heap");
+
+ /* get the absolute path for the input file */
+ if (infile != 0)
+ os_get_abs_filename(infile_abs, sizeof(infile_abs), infile);
+ else if (exefile != 0)
+ os_get_abs_filename(infile_abs, sizeof(infile_abs), exefile);
+ else
+ infile_abs[0] = '\0';
+ os_get_path_name(infile_path, sizeof(infile_path), infile_abs);
+
+ /* set up execution context */
+ runctx.runcxerr = ec;
+ runctx.runcxmem = mctx;
+ runctx.runcxstk = mystack;
+ runctx.runcxstop = &mystack[stksiz];
+ runctx.runcxsp = mystack;
+ runctx.runcxbp = mystack;
+ runctx.runcxheap = myheap;
+ runctx.runcxhp = myheap;
+ runctx.runcxhtop = &myheap[heapsiz];
+ runctx.runcxundo = undoptr;
+ runctx.runcxbcx = &bifctx;
+ runctx.runcxbi = bif;
+ runctx.runcxtio = (tiocxdef *)0;
+ runctx.runcxdbg = &dbg;
+ runctx.runcxvoc = &vocctx;
+ runctx.runcxdmd = supcont;
+ runctx.runcxdmc = &supctx;
+ runctx.runcxext = 0;
+ runctx.runcxgamename = infile;
+ runctx.runcxgamepath = infile_path;
+
+ /* set up setup context */
+ supctx.supcxerr = ec;
+ supctx.supcxmem = mctx;
+ supctx.supcxtab = (tokthdef *)0;
+ supctx.supcxbuf = (uchar *)0;
+ supctx.supcxlen = 0;
+ supctx.supcxvoc = &vocctx;
+ supctx.supcxrun = &runctx;
+
+ /* set up debug context */
+ dbg.dbgcxtio = (tiocxdef *)0;
+ dbg.dbgcxmem = mctx;
+ dbg.dbgcxerr = ec;
+ dbg.dbgcxtab = (tokthdef *)0;
+ dbg.dbgcxfcn = 0;
+ dbg.dbgcxdep = 0;
+ dbg.dbgcxflg = 0;
+ dbg.dbgcxlin = (lindef *)0; /* no line sources yet */
+
+ /* set up built-in function context */
+ CLRSTRUCT(bifctx);
+ bifctx.bifcxerr = ec;
+ bifctx.bifcxrun = &runctx;
+ bifctx.bifcxtio = (tiocxdef *)0;
+ bifctx.bifcxrnd = 0;
+ bifctx.bifcxrndset = FALSE;
+ bifctx.bifcxappctx = appctx;
+ bifctx.bifcxsafetyr = safety_read;
+ bifctx.bifcxsafetyw = safety_write;
+ bifctx.bifcxsavext = save_ext;
+
+ /* initialize the regular expression parser context */
+ re_init(&bifctx.bifcxregex, ec);
+
+ /* add the built-in functions, keywords, etc */
+ supbif(&supctx, bif, (int)(sizeof(bif) / sizeof(bif[0])));
+
+ /* set up status line hack */
+ runistat(&vocctx, &runctx, (tiocxdef *)0);
+
+ /* turn on the "busy" cursor before loading */
+ os_csr_busy(TRUE);
+
+ /* read the game from the binary file */
+ fiord(mctx, &vocctx, (struct tokcxdef *)0,
+ infile, exefile, &fiolctx, &preinit, &flags,
+ (struct tokpdef *)0, (uchar **)0, (uint *)0, (uint *)0,
+ (preload ? 2 : 0), appctx, argv[0]);
+ loadopen = TRUE;
+
+ /* turn off the "busy" cursor */
+ os_csr_busy(FALSE);
+
+ /* play the game */
+ plygo(&runctx, &vocctx, (tiocxdef *)0, preinit, restore_file);
+
+ /* close load file */
+ fiorcls(&fiolctx);
+
+ if (pause)
+ {
+ trdptf("[press a key to exit]");
+ os_waitc();
+ trdptf("\n");
+ }
+
+ /* close and delete swapfile, if one was opened */
+ trd_close_swapfile(&runctx);
+
+ /* make sure the script file is closed, if we have one */
+ qasclose();
+
+ ERRCLEAN(ec)
+ /* close and delete swapfile, if one was opened */
+ trd_close_swapfile(&runctx);
+
+ /* close the load file if one was opened */
+ if (loadopen)
+ fiorcls(&fiolctx);
+
+ /* vocctx is going out of scope - forget the global reference to it */
+ main_voc_ctx = 0;
+
+ /* delete the voc context */
+ vocterm(&vocctx);
+
+ /* delete the undo context */
+ if (undoptr != 0)
+ objuterm(undoptr);
+
+ /* release the object cache structures */
+ if (mctx != 0)
+ mcmcterm(mctx);
+ if (globalctx != 0)
+ mcmterm(globalctx);
+ ERRENDCLN(ec)
+
+ /* vocctx is going out of scope - forget the global reference to it */
+ main_voc_ctx = 0;
+
+ /* delete the voc contxt */
+ vocterm(&vocctx);
+
+ /* delete the undo context */
+ if (undoptr != 0)
+ objuterm(undoptr);
+
+ /* release the object cache structures */
+ mcmcterm(mctx);
+ mcmterm(globalctx);
+#endif
}
void TADS2::trdptf(const char *fmt, ...) {
diff --git a/engines/glk/tads/tads2/tads2.h b/engines/glk/tads/tads2/tads2.h
index 2472929840..0ea1662b00 100644
--- a/engines/glk/tads/tads2/tads2.h
+++ b/engines/glk/tads/tads2/tads2.h
@@ -23,38 +23,35 @@
#ifndef GLK_TADS_TADS2
#define GLK_TADS_TADS2
-#include "glk/tads/tads2/os.h"
-#include "glk/tads/tads2/ler.h"
+#include "glk/tads/tads.h"
+#include "glk/tads/tads2/error_handling.h"
+#include "glk/tads/tads2/appctx.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
-/**
- * map a native character (read externally) into an internal character
+/*
+ * Run-time version number
*/
-#define cmap_n2i(c) (G_cmap_input[(unsigned char)(c)])
+#define TADS_RUNTIME_VERSION "2.5.17"
-/**
- * map an internal character into a native character (for display)
- */
-#define cmap_i2n(c) (G_cmap_output[(unsigned char)(c)])
-
-/**
- * the full name (for display purposes) of the loaded character set
- */
-#define CMAP_LDESC_MAX_LEN 40
-
-/**
- * Maximum expansion for an HTML entity mapping
- */
-#define CMAP_MAX_ENTITY_EXPANSION 50
+# define TRD_HEAPSIZ 4096
+# define TRD_STKSIZ 200
+# define TRD_UNDOSIZ (16 * 1024)
+# define TDD_HEAPSIZ 4096
+# define TDD_STKSIZ 200
+# define TDD_UNDOSIZ (16 * 1024)
+# define TDD_POOLSIZ (2 * 1024)
+# define TDD_LCLSIZ 0
+# define ERR_TRUS_OS_FIRST 100
+# define ERR_TRUS_OS_LAST 99
/**
* TADS 2 game interpreter
*/
-class TADS2 : public OS {
+class TADS2 : public TADS {
private:
// STUBS
void os_printz(const Common::String &s) {}
@@ -62,41 +59,6 @@ private:
const char *expansion, size_t expansion_len) {}
private:
/**
- * \defgroup cmap
- * @{
- */
-
- /**
- * flag: true -> a character set has been explicitly loaded, so we
- * should ignore any game character set setting
- */
- bool S_cmap_loaded;
-
- /**
- * input-mapping table - for native character 'n', cmap_input[n] yields
- * the internal character code
- */
- unsigned char G_cmap_input[256];
-
- /**
- * output-mapping table - for internal character 'n', cmap_output[n]
- * yields the output character code
- */
- unsigned char G_cmap_output[256];
-
- /**
- * the ID of the loaded character set
- */
- char G_cmap_id[5];
-
- /**
- * the full name (for display purposes) of the loaded character set
- */
- char G_cmap_ldesc[CMAP_LDESC_MAX_LEN + 1];
-
- /**@}*/
-private:
- /**
* \defgroup trd
* @{
*/
@@ -109,55 +71,6 @@ private:
void trdptf(const char *fmt, ...);
/**@}*/
-
- /**
- * \defgroup cmap
- * @{
- */
-
- /**
- * Initialize the default character mappings. If no mapping file is to
- * be read, this function will establish identify mappings that leave
- * characters untranslated.
- */
- void cmap_init_default();
-
- /**
- * Load a character map file. Returns zero on success, non-zero on
- * failure. If filename is null, we'll use the default mapping.
- */
- int cmap_load(const char *filename);
-
- /**
- * Turn off character translation. This overrides any game character
- * set that we find and simply uses the default translation.
- */
- void cmap_override(void);
-
- /**
- * Set the game's internal character set. This should be called when a
- * game is loaded, and the game specifies an internal character set. If
- * there is no character map file explicitly loaded, we will attempt to
- * load a character mapping file that maps this character set to the
- * current native character set. Signals an error on failure. This
- * routine will succeed (without doing anything) if a character set has
- * already been explicitly loaded, since an explicitly-loaded character
- * set overrides the automatic character set selection that we attempt
- * when loading a game.
- *
- * argv0 must be provided so that we know where to look for our mapping
- * file on systems where mapping files are stored in the same directory
- * as the TADS executables.
- */
- void cmap_set_game_charset(errcxdef *errctx, const char *internal_id,
- const char *internal_ldesc, const char *argv0);
-
- /**
- * Internal routine to load a character map from a file
- */
- int cmap_load_internal(const char *filename);
-
- /**@}*/
public:
/**
* Constructor
@@ -175,7 +88,7 @@ public:
virtual InterpreterType getInterpreterType() const override { return INTERPRETER_TADS2; }
};
-typedef TADS2 appctxdef;
+//typedef TADS2 appctxdef;
} // End of namespace TADS2
} // End of namespace TADS
diff --git a/engines/glk/tads/tads2/tads2_cmap.cpp b/engines/glk/tads/tads2/tads2_cmap.cpp
deleted file mode 100644
index 68945f7336..0000000000
--- a/engines/glk/tads/tads2/tads2_cmap.cpp
+++ /dev/null
@@ -1,268 +0,0 @@
-/* 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/tads2.h"
-#include "glk/tads/tads2/types.h"
-
-namespace Glk {
-namespace TADS {
-namespace TADS2 {
-
-/**
- * Signatures for character map files. The signature is stored at the
- * beginning of the file.
- */
-// single-byte character map version 1.0.0
-#define CMAP_SIG_S100 "TADS2 charmap S100\n\r\01a"
-
-void TADS2::cmap_init_default() {
- size_t i;
-
- // initialize the input table
- for (i = 0 ; i < sizeof(G_cmap_input)/sizeof(G_cmap_input[0]) ; ++i)
- G_cmap_input[i] = (unsigned char)i;
-
- // initialize the output table
- for (i = 0 ; i < sizeof(G_cmap_output)/sizeof(G_cmap_output[0]) ; ++i)
- G_cmap_output[i] = (unsigned char)i;
-
- // we have a null ID
- memset(G_cmap_id, 0, sizeof(G_cmap_id));
-
- // indicate that it's the default
- strcpy(G_cmap_ldesc, "(native/no mapping)");
-
- // note that we have no character set loaded
- S_cmap_loaded = false;
-}
-
-int TADS2::cmap_load_internal(const char *filename) {
- osfildef *fp;
- static char sig1[] = CMAP_SIG_S100;
- char buf[256];
- uchar lenbuf[2];
- size_t len;
- int sysblk;
-
- // if there's no mapping file, use the default mapping
- if (filename == 0) {
- // initialize with the default mapping
- cmap_init_default();
-
- // return success
- return 0;
- }
-
- // open the file
- fp = osfoprb(filename, OSFTCMAP);
- if (fp == 0)
- return 1;
-
- // check the signature
- if (osfrb(fp, buf, sizeof(sig1))
- || memcmp(buf, sig1, sizeof(sig1)) != 0) {
- osfcls(fp);
- return 2;
- }
-
- // load the ID
- G_cmap_id[4] = '\0';
- if (osfrb(fp, G_cmap_id, 4)) {
- osfcls(fp);
- return 3;
- }
-
- // load the long description
- if (osfrb(fp, lenbuf, 2)
- || (len = osrp2(lenbuf)) > sizeof(G_cmap_ldesc)
- || osfrb(fp, G_cmap_ldesc, len)) {
- osfcls(fp);
- return 4;
- }
-
- // load the two tables - input, then output
- if (osfrb(fp, G_cmap_input, sizeof(G_cmap_input))
- || osfrb(fp, G_cmap_output, sizeof(G_cmap_output))) {
- osfcls(fp);
- return 5;
- }
-
- // read the next section header
- if (osfrb(fp, buf, 4)) {
- osfcls(fp);
- return 6;
- }
-
- // if it's "SYSI", read the system information string
- if (!memcmp(buf, "SYSI", 4)) {
- // read the length prefix, then the string
- if (osfrb(fp, lenbuf, 2)
- || (len = osrp2(lenbuf)) > sizeof(buf)
- || osfrb(fp, buf, len)) {
- osfcls(fp);
- return 7;
- }
-
- // we have a system information block
- sysblk = true;
- } else {
- // there's no system information block
- sysblk = false;
- }
-
- /*
- * call the OS code, so that it can do any system-dependent
- * initialization for the new character mapping
- */
- os_advise_load_charmap(G_cmap_id, G_cmap_ldesc, sysblk ? buf : "");
-
- // read the next section header
- if (sysblk && osfrb(fp, buf, 4)) {
- osfcls(fp);
- return 8;
- }
-
- // see if we have an entity list
- if (!memcmp(buf, "ENTY", 4)) {
- // read the entities
- for (;;) {
- unsigned int cval;
- char expansion[CMAP_MAX_ENTITY_EXPANSION];
-
- // read the next item's length and character value
- if (osfrb(fp, buf, 4)) {
- osfcls(fp);
- return 9;
- }
-
- // decode the values
- len = osrp2(buf);
- cval = osrp2(buf+2);
-
- // if we've reached the zero marker, we're done
- if (len == 0 && cval == 0)
- break;
-
- // read the string
- if (len > CMAP_MAX_ENTITY_EXPANSION
- || osfrb(fp, expansion, len)) {
- osfcls(fp);
- return 10;
- }
-
- // tell the output code about the expansion
- tio_set_html_expansion(cval, expansion, len);
- }
- }
-
- /*
- * ignore anything else we find - if the file format is updated to
- * include extra information in the future, and this old code tries
- * to load an updated file, we'll just ignore the new information,
- * which should always be placed after the "SYSI" block (if present)
- * to ensure compatibility with past versions (such as this code)
- */
- // no problems - close the file and return success
- osfcls(fp);
- return 0;
-}
-
-int TADS2::cmap_load(const char *filename) {
- int err;
-
- // try loading the file
- if ((err = cmap_load_internal(filename)) != 0)
- return err;
-
- /*
- * note that we've explicitly loaded a character set, if they named
- * a character set (if not, this simply establishes the default
- * setting, so we haven't explicitly loaded anything)
- */
- if (filename != nullptr)
- S_cmap_loaded = true;
-
- // success
- return 0;
-}
-
-void TADS2::cmap_override() {
- // apply the default mapping
- cmap_init_default();
-
- /*
- * pretend we have a character map loaded, so that we don't try to
- * load another one if the game specifies a character set
- */
- S_cmap_loaded = true;
-}
-
-void TADS2::cmap_set_game_charset(errcxdef *ec, const char *internal_id,
- const char *internal_ldesc, const char *argv0) {
- char filename[OSFNMAX];
-
- /*
- * If a character set is already explicitly loaded, ignore the
- * game's character set - the player asked us to use a particular
- * mapping, so ignore what the game wants. (This will probably
- * result in incorrect display of non-ASCII character values, but
- * the player is most likely to use this to avoid errors when an
- * appropriate mapping file for the game is not available. In this
- * case, the player informs us by setting the option that he or she
- * knows and accepts that the game will not look exactly right.)
- */
- if (S_cmap_loaded)
- return;
-
- /*
- * ask the operating system to name the mapping file -- this routine
- * will determine, if possible, the current native character set,
- * and apply a system-specific naming convention to tell us what
- * mapping file we should open
- */
- os_gen_charmap_filename(filename, internal_id, argv0);
-
- // try loading the mapping file
- if (cmap_load_internal(filename))
- errsig2(ec, ERR_CHRNOFILE,
- ERRTSTR, errstr(ec, filename, strlen(filename)),
- ERRTSTR, errstr(ec, internal_ldesc, strlen(internal_ldesc)));
-
- /**
- * We were successful - the game's internal character set is now
- * mapped to the current native character set. Even though we
- * loaded an ldesc from the mapping file, forget that and store the
- * internal ldesc that the game specified. The reason we do this is
- * that it's possible that the player will dynamically switch native
- * character sets in the future, at which point we'll need to
- * re-load the mapping table, which could raise an error if a
- * mapping file for the new character set isn't available. So, we
- * may need to provide the same explanation later that we needed to
- * provide here. Save the game's character set ldesc for that
- * eventuality, since it describes exactly what the *game* wanted.
- */
- strcpy(G_cmap_ldesc, internal_ldesc);
-}
-
-} // End of namespace TADS2
-} // End of namespace TADS
-} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/text_io.h b/engines/glk/tads/tads2/text_io.h
new file mode 100644
index 0000000000..33a914e0ba
--- /dev/null
+++ b/engines/glk/tads/tads2/text_io.h
@@ -0,0 +1,233 @@
+/* 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.
+ *
+ */
+
+#ifndef GLK_TADS_TADS2_TEXT_IO
+#define GLK_TADS_TADS2_TEXT_IO
+
+/*
+ * Text I/O interface
+ *
+ * Formatted text input and output interface definition
+ */
+
+#include "glk/tads/tads.h"
+#include "glk/tads/tads2/error_handling.h"
+#include "glk/tads/tads2/run.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* forward decls */
+struct runcxdef;
+
+/**
+ * Text i/o context
+ */
+struct tiocxdef {
+ errcxdef *tiocxerr; /* error handling context */
+};
+
+/**
+ * Initialize the output formatter subsystem. This must be called once
+ * at startup.
+ */
+void out_init();
+
+
+/* redirect all tioxxx routines to TADS v1.x outxxx equivalents */
+#define tioflushn(ctx, nl) outflushn(nl)
+#define tioflush(ctx) outflush()
+#define tioblank(ctx) outblank()
+#define tioreset(ctx) outreset()
+#define tiogets(ctx, prompt, str, siz) getstring(prompt, str, siz)
+#define tioputs(ctx, str) outformat(str)
+#define tioputslen(ctx, str, len) outformatlen(str, len)
+#define tiocaps(ctx) outcaps()
+#define tionocaps(ctx) outnocaps()
+#define tioshow(ctx) outshow()
+#define tiohide(ctx) outhide()
+#define tioscore(ctx, s1, s2) os_score(s1, s2)
+#define tiostrsc(ctx, s) os_strsc(s)
+
+/* set up format strings in output subsystem */
+void tiosetfmt(tiocxdef *ctx, runcxdef *rctx, uchar *fmtbase,
+ uint fmtlen);
+
+/* tell tio subsystem the current actor */
+void tiosetactor(tiocxdef *ctx, objnum actor);
+
+/* get the current tio subsystem actor */
+objnum tiogetactor(tiocxdef *ctx);
+
+/* turn output capture on/off */
+void tiocapture(tiocxdef *tioctx, mcmcxdef *memctx, int flag);
+
+/* get the capture object handle */
+mcmon tiogetcapture(tiocxdef *ctx);
+
+/* get the amount of text captured */
+uint tiocapturesize(tiocxdef *ctx);
+
+/* format a length-prefixed (runtime-style) string to the display */
+void outfmt(tiocxdef *ctx, uchar *txt);
+
+/* format a null-terminated (C-style) string to the display */
+int outformat(char *s);
+
+/* format a counted-length string, which may not be null-terminated */
+int outformatlen(char *s, uint len);
+
+/* flush output, with specified newline mode */
+void outflushn(int nl);
+
+/* flush output */
+void outflush(void);
+
+/* reset output state */
+void outreset(void);
+
+/*
+ * Get a string from the keyboard. Returns non-zero if an error occurs
+ * (in particular, if no more input is available from the keyboard),
+ * zero on success.
+ */
+int getstring(char *prompt, char *buf, int bufl);
+
+/* set capitalize-next-character mode on/off */
+void outcaps(void);
+void outnocaps(void);
+
+/* open/close output log file */
+int tiologopn(tiocxdef *ctx, char *fn);
+int tiologcls(tiocxdef *ctx);
+
+/*
+ * 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);
+
+
+/*
+ * Check output status. Indicate whether output is currently hidden,
+ * and whether any hidden output has occurred.
+ */
+void outstat(int *hidden, int *output_occurred);
+
+/* hide/show output */
+void outhide(void);
+int outshow(void);
+
+/* set the flag to indicate that output has occurred */
+void outsethidden(void);
+
+/* write a blank line */
+void outblank(void);
+
+/* start/end watchpoint evaluation */
+void outwx(int flag);
+
+/* Begin/end capturing */
+void tiocapture(tiocxdef *tioctx, mcmcxdef *memctx, int flag);
+
+/* clear all captured output */
+void tioclrcapture(tiocxdef *tioctx);
+
+/*
+ * clear captured output back to a given point -- this can be used to
+ * remove captured output in an inner capture from an enclosing capture
+ */
+void tiopopcapture(tiocxdef *tioctx, uint orig_size);
+
+/* get the object handle of the captured output */
+mcmon tiogetcapture(tiocxdef *ctx);
+
+/* get the amount of text captured */
+uint tiocapturesize(tiocxdef *ctx);
+
+/* turn MORE mode on or off */
+int setmore(int state);
+
+/* explicitly activate the "MORE" prompt */
+void out_more_prompt();
+
+/*
+ * QA controller functions
+ */
+int qasopn(char *scrnam, int quiet);
+void qasclose(void);
+char *qasgets(char *buf, int bufl);
+
+/*
+ * Set an HTML entity expansion. This is called during initialization
+ * when we read a character mapping table that includes HTML entity
+ * expansions. The HTML run-time uses its own expansion mechanism, so
+ * it will ignore this information. The standard character-mode TADS
+ * run-time, however, uses this information to map HTML entities to the
+ * local character set.
+ */
+void tio_set_html_expansion(unsigned int html_char_val,
+ const char *expansion, size_t expansion_len);
+
+/* check for HTML mode - returns true if an "\H+" sequence is active */
+int tio_is_html_mode();
+
+/* set the user output filter function */
+void out_set_filter(objnum filter_fn);
+
+/* set the double-space mode */
+void out_set_doublespace(int dbl);
+
+/*
+ * Ask for a filename, using a system-defined dialog (via os_askfile) if
+ * possible. Uses the same interface as os_askfile(), which we will
+ * call directly for graphical implementations. We'll use formatted
+ * text for text-only implementations.
+ */
+int tio_askfile(const char *prompt, char *fname_buf, int fname_buf_len,
+ int prompt_type, os_filetype_t file_type);
+
+/*
+ * Display a dialog, using a system-defined dialog (via os_input_dialog)
+ * if possible. Uses the same interface as os_input_dialog(), which we
+ * will call directly for graphical implementations. We'll use
+ * formatted text for text-only implementations.
+ */
+int tio_input_dialog(int icon_id, const char *prompt, int standard_button_set,
+ const char **buttons, int button_count,
+ int default_index, int cancel_index);
+
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/tokenizer.cpp b/engines/glk/tads/tads2/tokenizer.cpp
new file mode 100644
index 0000000000..59dceebaf6
--- /dev/null
+++ b/engines/glk/tads/tads2/tokenizer.cpp
@@ -0,0 +1,33 @@
+/* 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/tokenizer.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+// TODO: Rest of tokenizer stuff
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/tokenizer.h b/engines/glk/tads/tads2/tokenizer.h
new file mode 100644
index 0000000000..50a8492cd3
--- /dev/null
+++ b/engines/glk/tads/tads2/tokenizer.h
@@ -0,0 +1,473 @@
+/* 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.
+ *
+ */
+
+#ifndef GLK_TADS_TADS2_TOKENIZER
+#define GLK_TADS_TADS2_TOKENIZER
+
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/error_handling.h"
+#include "glk/tads/tads2/line_source.h"
+#include "glk/tads/tads2/memory_cache.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* number of entries in hash table - must be power of 2 */
+#define TOKHASHSIZE 256
+
+/* symbol types */
+#define TOKSTUNK 0 /* unknown symbol, not yet defined */
+#define TOKSTFUNC 1 /* function; value is object number */
+#define TOKSTOBJ 2 /* object; value is object number */
+#define TOKSTPROP 3 /* property; value is property number */
+#define TOKSTLOCAL 4 /* a local variable or formal parameter */
+#define TOKSTSELF 5 /* the pseudo-object "self" */
+#define TOKSTBIFN 6 /* a built-in function */
+#define TOKSTFWDOBJ 7 /* forward-referenced object */
+#define TOKSTFWDFN 8 /* forward-referenced object */
+#define TOKSTINHERIT 9 /* the pseudo-object "inherited" */
+#define TOKSTEXTERN 10 /* an external function */
+#define TOKSTKW 11 /* keyword; value is token number */
+#define TOKSTLABEL 12 /* statement label */
+#define TOKSTARGC 13 /* 'argcount' pseudo-variable */
+#define TOKSTPROPSPEC 14 /* speculative evaluation property */
+
+/* token types */
+#define TOKTEOF 1
+
+/* binary operators - keep these together (see prsbopl[] in prs.c) */
+#define TOKTPLUS 2
+#define TOKTMINUS 3
+#define TOKTDIV 4
+#define TOKTTIMES 5
+#define TOKTNOT 6 /* ! or "not" */
+#define TOKTEQ 7
+#define TOKTNE 8
+#define TOKTGT 9
+#define TOKTGE 10
+#define TOKTLT 11
+#define TOKTLE 12
+#define TOKTMOD 13
+#define TOKTBAND 14
+#define TOKTBOR 15
+#define TOKTXOR 16
+#define TOKTSHL 17
+#define TOKTSHR 18
+#define TOKTTILDE 30
+
+/*
+ * special 'dot' replacement for speculative evaluation mode -- this is
+ * strictly for marking parse tree nodes, and has the same meaning in a
+ * parse tree node as a regular TOKTDOT, but generates code that can't
+ * call methods
+ */
+#define TOKTDOTSPEC 31
+
+/* special node marker for explicit superclass inheritance nodes */
+#define TOKTEXPINH 32
+
+#define TOKTLPAR 50 /* ( */
+#define TOKTRPAR 51 /* ) */
+#define TOKTCOLON 52
+#define TOKTDSTRING 53 /* string in double quotes */
+#define TOKTSSTRING 54 /* string in single quotes */
+#define TOKTNUMBER 55
+#define TOKTSYMBOL 56
+#define TOKTINVALID 57 /* invalid lexical token */
+#define TOKTLBRACK 58 /* [ */
+#define TOKTRBRACK 59 /* ] */
+#define TOKTLBRACE 60 /* { */
+#define TOKTRBRACE 61 /* } */
+#define TOKTSEM 62 /* ; */
+#define TOKTCOMMA 63
+#define TOKTDOT 64 /* . */
+#define TOKTOR 65 /* | or "if" */
+#define TOKTAND 66 /* & or "and" */
+#define TOKTIF 67 /* keywords */
+#define TOKTELSE 68
+#define TOKTWHILE 69
+#define TOKTFUNCTION 70
+#define TOKTRETURN 71
+#define TOKTLOCAL 72
+#define TOKTOBJECT 73
+#define TOKTBREAK 74
+#define TOKTCONTINUE 75
+#define TOKTLIST 76 /* a list */
+#define TOKTNIL 77
+#define TOKTTRUE 78
+#define TOKTPASS 79
+#define TOKTCLASS 80
+#define TOKTEXIT 81
+#define TOKTABORT 82
+#define TOKTASKDO 83
+#define TOKTASKIO 84
+#define TOKTPOUND 85 /* # */
+#define TOKTQUESTION 86 /* ? */
+#define TOKTCOMPOUND 87
+#define TOKTIOSYN 88
+#define TOKTDOSYN 89
+#define TOKTEXTERN 90
+#define TOKTFORMAT 91
+#define TOKTDO 92
+#define TOKTFOR 93
+#define TOKTNEW 94
+#define TOKTDELETE 95
+
+/* assignment operators - keep these together */
+#define TOKTINC 150 /* ++ */
+#define TOKTPOSTINC 151 /* MUST BE TOKTINC + 1 */
+#define TOKTDEC 152 /* -- */
+#define TOKTPOSTDEC 153 /* MUST BE TOKTDEC + 1 */
+#define TOKTPLEQ 154 /* += */
+#define TOKTMINEQ 155 /* -= */
+#define TOKTDIVEQ 156 /* /= */
+#define TOKTTIMEQ 157 /* *= */
+#define TOKTASSIGN 158 /* simple assignment */
+#define TOKTMODEQ 159 /* %= (mod and assign) operator */
+#define TOKTBANDEQ 160 /* &= */
+#define TOKTBOREQ 161 /* |= */
+#define TOKTXOREQ 162 /* ^= (xor and assign) */
+#define TOKTSHLEQ 163 /* <<= (shift left and assign) */
+#define TOKTSHREQ 164 /* >>= (shift right and assign */
+
+#define TOKTSWITCH 200
+#define TOKTCASE 201
+#define TOKTDEFAULT 202
+#define TOKTGOTO 203
+#define TOKTELLIPSIS 204 /* ... */
+#define TOKTSPECIAL 205 /* "specialWords" */
+#define TOKTREPLACE 206 /* replace */
+#define TOKTMODIFY 207 /* modify */
+
+#define TOKTEQEQ 208 /* the '==' operator */
+#define TOKTPOINTER 209 /* the -> operator */
+
+/* the longest a symbol name can be */
+#define TOKNAMMAX 39
+
+/* symbol table entry */
+struct toksdef {
+ uchar tokstyp; /* type of the symbol */
+ uchar tokshsh; /* hash value of symbol */
+ ushort toksval; /* value of the symbol (depends on type) */
+ ushort toksfr; /* frame offset of symbol (for debugger) */
+ uchar tokslen; /* length of the symbol's name */
+ char toksnam[TOKNAMMAX]; /* name of symbol */
+};
+
+/* symbol table entry without 'name' portion - for allocation purposes */
+struct toks1def {
+ uchar tokstyp;
+ uchar tokshsh;
+ ushort toksval;
+ ushort toksfr;
+ uchar tokslen;
+ char toksnam[1];
+};
+
+/* generic symbol table object - other symbol tables are subclasses */
+struct toktdef {
+ void (*toktfadd)(toktdef *tab, char *name, int namel, int typ,
+ int val, int hash); /* add symbol */
+ int (*toktfsea)(toktdef *tab, char *name, int namel, int hash,
+ toksdef *ret); /* search symbol table */
+ void (*toktfset)(toktdef *tab, toksdef *sym);
+ /* update val & typ of symbol to those in *sym */
+ void (*toktfeach)(toktdef *tab,
+ void (*fn)(void *ctx, toksdef *sym),
+ void *fnctx); /* call fn for each sym */
+ toktdef *toktnxt; /* next symbol table to be searched */
+ errcxdef *tokterr; /* error handling context */
+};
+
+/* maximum number of pools (TOKTSIZE bytes each) for symbols */
+#define TOKPOOLMAX 128
+
+/* pointer to a symbol in a hashed symbol table */
+struct tokthpdef {
+ mcmon tokthpobj; /* cache manager object number of page */
+ uint tokthpofs; /* offset within page of this symbol */
+};
+
+/* extended symbol entry in a hashed symbol table */
+struct tokshdef {
+ tokthpdef tokshnxt; /* pointer to next symbol in the table */
+ toksdef tokshsc; /* superclass - normal symbol entry */
+};
+
+/* hashing symbol table (subclass of generic symbol table) */
+struct tokthdef {
+ toktdef tokthsc; /* generic symbol table superclass data */
+ mcmcxdef *tokthmem; /* memory manager context */
+ tokthpdef tokthhsh[TOKHASHSIZE]; /* hash table */
+ uint tokthpcnt; /* number of memory pools for toksdef's */
+ mcmon tokthpool[TOKPOOLMAX]; /* memory pools for toksdef's */
+ uint tokthfinal[TOKPOOLMAX]; /* actual sizes of these pools */
+ uchar *tokthcpool; /* current pool pointer */
+ ushort tokthsize; /* remaining size of top memory pool */
+ ushort tokthofs; /* allocation offset in top memory pool */
+};
+
+/* size of toksdef pools to allocate for hashed symbol tables */
+#define TOKTHSIZE 4096
+
+/*
+ * Linear cache-object-embedded symbol table. This type of symbol
+ * table is used for frame parameter/local variable lists. It is best
+ * for small tables, because it isn't broken up into hash buckets, so it
+ * is searched linearly. As a result, it's small enough to be embedded
+ * in code.
+ */
+struct toktldef {
+ toktdef toktlsc; /* generic symbol table superclass data */
+ uchar *toktlptr; /* base of linear symbol table */
+ uchar *toktlnxt; /* next free byte in table */
+ uint toktlcnt; /* number of objects in the table */
+ uint toktlsiz; /* bytes remaining in the table */
+};
+
+struct tokdef {
+ int toktyp; /* type of the token */
+ int toklen; /* length of token text, if a symbolic token */
+ long tokval; /* numeric value, if applicable */
+ ushort tokofs;
+ uint tokhash; /* token hash value, if a symbolic token */
+ char toknam[TOKNAMMAX+1]; /* text of token, if a symbolic token */
+ toksdef toksym; /* symbol from table matching token */
+};
+
+/* special character sequence */
+#define TOKSCMAX 3 /* maximum length of a special char sequence */
+struct tokscdef {
+ tokscdef *tokscnxt; /* next sequence with same first character */
+ int toksctyp; /* token type corresponding to sequence */
+ int toksclen; /* length of the sequence */
+ char tokscstr[TOKSCMAX+1]; /* the sequence itself */
+};
+
+/*
+ * Compare a special character sequence - for efficiency, define
+ * something special for the maximum length available (TOKSCMAX).
+ * Note that the first character will always be equal, or the
+ * string wouldn't even get to the point of being tested by this
+ * macro.
+ */
+#if TOKSCMAX == 3
+# define toksceq(str1, str2, len1, len2) \
+ ((len2) >= (len1) \
+ && ((len1) == 1 \
+ || ((str1)[1] == (str2)[1] \
+ && ((len1) == 2 \
+ || (str1)[2] == (str2)[2]))))
+#endif /* TOKSCMAX == 3 */
+#ifndef toksceq
+# define toksceq(str1, str2, len) (!memcmp(str1, str2, (size_t)(len)))
+#endif /* toksceq */
+
+/* special character sequence list table entry */
+struct tokldef {
+ int tokltyp; /* token type corresponding to sequence */
+ char toklstr[TOKSCMAX+1]; /* the text of the sequence */
+};
+
+/* include path structure */
+struct tokpdef {
+ tokpdef *tokpnxt; /* next path in list */
+ int tokplen; /* length of directory name */
+ char tokpdir[1]; /* directory to search */
+};
+
+/* #define symbol structure */
+struct tokdfdef {
+ tokdfdef *nxt; /* next symbol in the same hash chain */
+ char *nm; /* name of the symbol */
+ int len; /* length of the symbol */
+ int explen; /* length of the expansion */
+ char expan[1]; /* expansion buffer */
+};
+
+/* #define hash table information */
+#define TOKDFHSHSIZ 64
+#define TOKDFHSHMASK 63
+
+/* maximum #if nesting */
+#define TOKIFNEST 64
+
+/* #if state */
+#define TOKIF_IF_YES 1 /* processing a true #if/#ifdef block */
+#define TOKIF_IF_NO 2 /* processing a false #if/#ifdef block */
+#define TOKIF_ELSE_YES 3 /* processing a true #else part */
+#define TOKIF_ELSE_NO 4 /* processing a false #else part */
+
+/* maximum macro expansion nesting */
+#define TOKMACNEST 20
+
+/* lexical analysis context */
+struct tokcxdef {
+ errcxdef *tokcxerr; /* error handling context */
+ mcmcxdef *tokcxmem; /* cache manager context */
+ struct dbgcxdef *tokcxdbg; /* debugger context */
+ lindef *tokcxlin; /* line source */
+ tokpdef *tokcxinc; /* head of include path list */
+ toktdef *tokcxstab; /* current head of symbol table chain */
+ void *tokcxscx; /* context for string storage callback functions */
+ ushort (*tokcxsst)(void *ctx);
+ /* start storing a string; return offset of string's storage */
+ void (*tokcxsad)(void *ctx, char *str, ushort len);
+ /* add characters to a string */
+ void (*tokcxsend)(void *ctx); /* finish storing string */
+ char *tokcxmsav[TOKMACNEST]; /* saved positions for macro expansion */
+ ushort tokcxmsvl[TOKMACNEST]; /* saved lengths for macro expansion */
+ int tokcxmlvl; /* macro nesting level */
+ int tokcxflg; /* flags */
+# define TOKCXFINMAC 0x01 /* doing <<expr>> macro expansion */
+# define TOKCXCASEFOLD 0x02 /* fold upper and lower case */
+# define TOKCXFCMODE 0x04 /* parse using C operators */
+# define TOKCXF_EMBED_PAREN_PRE 0x08 /* embedded expr - did '(' */
+# define TOKCXF_EMBED_PAREN_AFT 0x10 /* embedded expr - must do ')' */
+# define TOKCXFLIN2 0x20 /* new-style line records */
+ tokdef tokcxcur; /* current token */
+ char *tokcxbuf; /* buffer for long lines */
+ ushort tokcxbsz; /* size of long line buffer */
+ char *tokcxptr; /* pointer into line source */
+ ushort tokcxlen; /* length of text in buffer */
+ uchar tokcxinx[256]; /* special character indices */
+ tokdfdef *tokcxdf[TOKDFHSHSIZ]; /* hash table for #define symbols */
+ int tokcxifcnt; /* number of #endif's we expect to find */
+ char tokcxif[TOKIFNEST]; /* #if state for each nesting level */
+ int tokcxifcur; /* current #if state, obeying nesting */
+ struct linfdef *tokcxhdr; /* list of previously included headers */
+ tokscdef *tokcxsc[1]; /* special character table */
+};
+
+
+/* allocate and initialize a lexical analysis context */
+tokcxdef *tokcxini(errcxdef *errctx, mcmcxdef *mctx, tokldef *sctab);
+
+/* add an include path to a token handling context */
+void tokaddinc(tokcxdef *ctx, char *path, int pathlen);
+
+/* compute the hash value of a string */
+uint tokhsh(char *nam);
+
+/*
+ * Fold case of a token if we're in case-insensitive mode. This should
+ * be called any time a token is constructed artificially; it need not
+ * be used the token is read through the tokenizer, because the
+ * tokenizer will always adjust a token as needed before returning it.
+ */
+void tok_case_fold(tokcxdef *ctx, tokdef *tok);
+
+/* initialize a hashed symbol table */
+void tokthini(errcxdef *errctx, mcmcxdef *memctx, toktdef *toktab1);
+
+/* add a symbol to a hashed symbol table */
+void tokthadd(toktdef *toktab, char *name, int namel,
+ int typ, int val, int hash);
+
+/* update a symbol in a hashed symbol table */
+void tokthset(toktdef *toktab, toksdef *sym);
+
+/* search a hashed symbol table for a symbol */
+int tokthsea(toktdef *tab, char *name, int namel, int hash,
+ toksdef *ret);
+
+/* call a function for each symbol in a hashed symbol table */
+void toktheach(toktdef *tab, void (*cb)(void *ctx, toksdef *sym),
+ void *ctx);
+
+/* find a symbol given type and value */
+int tokthfind(toktdef *tab, int typ, uint val, toksdef *sym);
+
+/* initialize a linear symbol table */
+void toktlini(errcxdef *errctx, toktldef *toktab,
+ uchar *mem, uint siz);
+
+/* add a symbol to a linear symbol table */
+void toktladd(toktdef *toktab, char *name, int namel,
+ int typ, int val, int hash);
+
+/* search a linear symbol table */
+int toktlsea(toktdef *tab, char *name, int namel, int hash,
+ toksdef *ret);
+
+/* update a symbol in a linear symbol table */
+void toktlset(toktdef *toktab, toksdef *sym);
+
+/* call a function for each symbol in a local symbol table */
+void toktleach(toktdef *tab, void (*cb)(void *ctx, toksdef *sym),
+ void *ctx);
+
+/* delete all symbols from a linear table */
+void toktldel(toktldef *tab);
+
+/* get next token, removing it from input stream */
+int toknext(tokcxdef *ctx);
+
+/* general function to get/peek at next token */
+int tokget1(tokcxdef *ctx, tokdef *tok, int consume);
+
+/* add a symbol to the #define symbol table */
+void tok_add_define(tokcxdef *ctx, char *sym, int len,
+ char *expan, int explen);
+
+/*
+ * add a symbol to the #define symbol table, folding case if we're
+ * operating in case-insensitive mode
+ */
+void tok_add_define_cvtcase(tokcxdef *ctx, char *sym, int len,
+ char *expan, int explen);
+
+/* add a symbol to the #define symbol table as a number */
+void tok_add_define_num_cvtcase(tokcxdef *ctx, char *sym, int len, int num);
+
+/* undefine a #define symbol */
+void tok_del_define(tokcxdef *ctx, char *sym, int len);
+
+/* read/write preprocessor symbols from/to a file */
+void tok_read_defines(tokcxdef *ctx, osfildef *fp, errcxdef *ec);
+
+/* write preprocessor state to a file */
+void tok_write_defines(tokcxdef *ctx, osfildef *fp, errcxdef *ec);
+
+
+/* determine if a char is a valid non-initial character in a symbol name */
+#define TOKISSYM(c) \
+ (isalpha((uchar)(c)) || isdigit((uchar)(c)) || (c)=='_' || (c)=='$')
+
+/* numeric conversion and checking macros */
+#define TOKISHEX(c) \
+ (isdigit((uchar)(c))||((c)>='a'&&(c)<='f')||((c)>='A'&&(c)<='F'))
+#define TOKISOCT(c) \
+ (isdigit((uchar)(c))&&!((c)=='8'||(c)=='9'))
+
+#define TOKHEX2INT(c) \
+ (isdigit((uchar)c)?(c)-'0':((c)>='a'?(c)-'a'+10:(c)-'A'+10))
+#define TOKOCT2INT(c) ((c)-'0')
+#define TOKDEC2INT(c) ((c)-'0')
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/types.h b/engines/glk/tads/tads2/types.h
deleted file mode 100644
index 5da759fda7..0000000000
--- a/engines/glk/tads/tads2/types.h
+++ /dev/null
@@ -1,190 +0,0 @@
-/* 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.
- *
- */
-
-#ifndef GLK_TADS_TADS2_TYPES
-#define GLK_TADS_TADS2_TYPES
-
-#include "common/stream.h"
-
-namespace Glk {
-namespace TADS {
-namespace TADS2 {
-
-typedef unsigned char uchar;
-typedef Common::SeekableReadStream osfildef;
-
-/**
- * Allocate a memory block
- */
-#define mchalo(CTX, SIZE, COMMENT) ((byte *)new byte[SIZE])
-
-/**
- * Free a memory block
- */
-#define mchfre(PTR) delete[] (byte *)PTR
-
-/**
- * Close a file
- */
-#define osfcls(FP) delete FP
-
-/**
- * Read from a file
- */
-#define osfrb(FP, BUF, SIZE) FP->read(BUF, SIZE)
-
-/**
- * Read a 16-bit integer from memory
- */
-#define osrp2(MEM) READ_LE_UINT16(MEM)
-
-/**
- * The character (or characters) which mark the beginning of a special fileref string.
- * The important thing is that the string be one that is either not allowed in
- * filenames on your platform or is unlikely to be the first part of a filename.
- */
-#define OSS_FILEREF_STRING_PREFIX ":"
-
-/**
- * The character (or characters) which mark the end of a special fileref string.
- * Using this and OSS_FILEREF_STRING_PREFIX, you should be able to come up with
- * something which forms an invalid filename
- */
-#define OSS_FILEREF_STRING_SUFFIX ""
-
-/**
- * Maximum length of status line text
- */
-#define OSS_STATUS_STRING_LEN 80
-
-/**
- * Maximum size for filenames
- */
-#define OSFNMAX 1024
-
-/**
- * Important note: do not change these values when porting TADS. These
- * values can be used by games, so they must be the same on all platforms.
- */
-enum {
- OS_AFP_OPEN = 1, ///< choose an existing file to open for reading
- OS_AFP_SAVE = 2 ///< choose a filename for saving to a file
-};
-
-/**
- * File types.These type codes are used when opening or creating a file,
- * so that the OS routine can set appropriate file system metadata
- * to describe or find the file type.
- *
- * The type os_filetype_t is defined for documentary purposes; it's
- * always just an int.
- */
-enum os_filetype_t {
- OSFTGAME = 0, ///< a game data file (.gam)
- OSFTSAVE = 1, ///< a saved game (.sav)
- OSFTLOG = 2, ///< a transcript (log) file
- OSFTSWAP = 3, ///< swap file
- OSFTDATA = 4, ///< user data file (used with the TADS fopen() call)
- OSFTCMD = 5, ///< QA command/log file
- OSFTERRS = 6, ///< error message file
- OSFTTEXT = 7, ///< text file - used for source files
- OSFTBIN = 8, ///< binary file of unknown type - resources, etc
- OSFTCMAP = 9, ///< character mapping file
- OSFTPREF = 10, ///< preferences file
- OSFTUNK = 11, ///< unknown - as a filter, matches any file type
- OSFTT3IMG = 12, ///< T3 image file (.t3 - formerly .t3x)
- OSFTT3OBJ = 13, ///< T3 object file (.t3o)
- OSFTT3SYM = 14, ///< T3 symbol export file (.t3s)
- OSFTT3SAV = 15 ///< T3 saved state file (.t3v)
-};
-
-/**
- * Constants for os_getc() when returning commands. When used for command line
- * editing, special keys (arrows, END, etc.) should cause os_getc() to return 0,
- * and return the appropriate CMD_ value on the NEXT call. Hence, os_getc() must
- * keep the appropriate information around statically for the next call when a
- * command key is issued.
- *
- * The comments indicate which CMD_xxx codes are "translated" codes and which are
- * "raw"; the difference is that, when a particular keystroke could be interpreted
- * as two different CMD_xxx codes, one translated and the other raw, os_getc()
- * should always return the translated version of the key, and os_getc_raw()
- * should return the raw version.
- */
-enum KeyCmd {
- CMD_UP = 1, ///< move up/up arrow (translated)
- CMD_DOWN = 2, ///< move down/down arrow (translated)
- CMD_RIGHT = 3, ///< move right/right arrow (translated)
- CMD_LEFT = 4, ///< move left/left arrow (translated)
- CMD_END = 5, ///< move cursor to end of line (translated)
- CMD_HOME = 6, ///< move cursor to start of line (translated)
- CMD_DEOL = 7, ///< delete to end of line (translated)
- CMD_KILL = 8, ///< delete entire line (translated)
- CMD_DEL = 9, ///< delete current character (translated)
- CMD_SCR = 10, ///< toggle scrollback mode (translated)
- CMD_PGUP = 11, ///< page up (translated)
- CMD_PGDN = 12, ///< page down (translated)
- CMD_TOP = 13, ///< top of file (translated)
- CMD_BOT = 14, ///< bottom of file (translated)
- CMD_F1 = 15, ///< function key F1 (raw)
- CMD_F2 = 16, ///< function key F2 (raw)
- CMD_F3 = 17, ///< function key F3 (raw)
- CMD_F4 = 18, ///< function key F4 (raw)
- CMD_F5 = 19, ///< function key F5 (raw)
- CMD_F6 = 20, ///< function key F6 (raw)
- CMD_F7 = 21, ///< function key F7 (raw)
- CMD_F8 = 22, ///< function key F8 (raw)
- CMD_F9 = 23, ///< function key F9 (raw)
- CMD_F10 = 24, ///< function key F10 (raw)
- CMD_CHOME = 25, ///< control-home (raw)
- CMD_TAB = 26, ///< tab (translated)
- CMD_SF2 = 27, ///< shift-F2 (raw)
- ///< not used (obsolete) - 28
- CMD_WORD_LEFT = 29, ///< word left (ctrl-left on dos) (translated)
- CMD_WORD_RIGHT = 30,///< word right (ctrl-right on dos) (translated)
- CMD_WORDKILL = 31, ///< delete word right (translated)
- CMD_EOF = 32, ///< end-of-file (raw)
- CMD_BREAK = 33, ///< break (Ctrl-C or local equivalent) (translated)
- CMD_INS = 34, ///< insert key (raw)
-
- /**
- * ALT-keys - add alphabetical code to CMD_ALT: ALT-A == CMD_ALT + 0,
- * ALT-B == CMD_ALT + 1, ALT-C == CMD_ALT + 2, etc
- *
- * These keys are all raw (untranslated).
- */
- CMD_ALT = 128 ///< start of ALT keys
-};
-
-/**
- * Status mode codes
- */
-enum StatusMode {
- OSS_STATUS_MODE_STORY = 0,
- OSS_STATUS_MODE_STATUS = 1
-};
-
-} // End of namespace TADS2
-} // End of namespace TADS
-} // End of namespace Glk
-
-#endif
diff --git a/engines/glk/tads/tads2/vocabulary.cpp b/engines/glk/tads/tads2/vocabulary.cpp
index 4647b1bd30..e20bbe3de8 100644
--- a/engines/glk/tads/tads2/vocabulary.cpp
+++ b/engines/glk/tads/tads2/vocabulary.cpp
@@ -20,6 +20,7 @@
*
*/
+#include "glk/tads/tads2/run.h"
#include "glk/tads/tads2/vocabulary.h"
namespace Glk {
diff --git a/engines/glk/tads/tads2/vocabulary.h b/engines/glk/tads/tads2/vocabulary.h
index b4079932a5..5574675aa2 100644
--- a/engines/glk/tads/tads2/vocabulary.h
+++ b/engines/glk/tads/tads2/vocabulary.h
@@ -24,19 +24,774 @@
* Defines TADS vocabulary (player command parser) functionality
*/
-#ifndef GLK_TADS_TADS2_OS
-#define GLK_TADS_TADS2_OS
+#ifndef GLK_TADS_TADS2_VOCABULARY
+#define GLK_TADS_TADS2_VOCABULARY
-#include "glk/tads/tads2/types.h"
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/object.h"
+#include "glk/tads/tads2/property.h"
+#include "glk/tads/tads2/run.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
-// TODO: Rest of vocabulary stuff
+/*
+ * Cover macro for parser errors. Any parser error should be covered
+ * with this macro for documentation and search purposes. (The macro
+ * doesn't do anything - this is just something to search for when we're
+ * trying to enumerate parser error codes.)
+ */
+#define VOCERR(errcode) errcode
+
+/* maximum number of objects matching an ambiguous word */
+#define VOCMAXAMBIG 200
+
+/* size of input buffer */
+#define VOCBUFSIZ 128
+
+/*
+ * Vocabulary relation structure - this structure relates a vocabulary
+ * word to an object and part of speech. A list of these structures is
+ * attached to each vocabulary word structure to provide the word's
+ * meanings.
+ */
+struct vocwdef {
+ uint vocwnxt; /* index of next vocwdef attached to the same word */
+ objnum vocwobj; /* object associated with the word */
+ uchar vocwtyp; /* property associated with the word (part of speech) */
+ uchar vocwflg; /* flags for the word */
+#define VOCFCLASS 1 /* word is for a class object */
+#define VOCFINH 2 /* word is inherited from a superclass */
+#define VOCFNEW 4 /* word was added at run-time */
+#define VOCFDEL 8 /* word has been deleted */
+};
+
+/* vocabulary word structure */
+struct vocdef {
+ vocdef *vocnxt; /* next word at same hash value */
+ uchar voclen; /* length of the word */
+ uchar vocln2; /* length of second word (0 if no second word) */
+ uint vocwlst; /* head of list of vocwdef's attached to the word */
+ uchar voctxt[1]; /* text of the word */
+};
+
+/* vocabulary inheritance cell */
+struct vocidef {
+ uchar vocinsc; /* # of superclasses (gives size of record) */
+ union {
+ struct {
+ uchar vociusflg; /* flags for entry */
+#define VOCIFCLASS 1 /* entry refers to a class object (loc records only) */
+#define VOCIFVOC 2 /* entry has vocabulary words defined */
+#define VOCIFXLAT 4 /* superclasses must be translated from portable fmt */
+#define VOCIFLOCNIL 8 /* location is explicitly set to nil */
+#define VOCIFNEW 16 /* object was allocated at run-time with "new" */
+ objnum vociusloc; /* location of the object */
+ objnum vociusilc; /* inherited location */
+ objnum vociussc[1]; /* array of superclasses */
+ } vocius;
+ vocidef *vociunxt;
+ } vociu;
+#define vociflg vociu.vocius.vociusflg
+#define vociloc vociu.vocius.vociusloc
+#define vociilc vociu.vocius.vociusilc
+#define vocisc vociu.vocius.vociussc
+#define vocinxt vociu.vociunxt
+};
+
+/* size of a page in a vocabulary pool */
+#define VOCPGSIZ 8192
+
+/* number of bytes in an inheritance cell page */
+#define VOCISIZ 8192
+
+/* maximum number of inheritance pages */
+#define VOCIPGMAX 32
+
+/* maximum number of inheritance pages (256 objects per page) */
+#define VOCINHMAX 128
+
+/* size of vocabulary hash table */
+#define VOCHASHSIZ 256
+
+/* size of a template structure */
#define VOCTPLSIZ 10
+
+/* new-style template structure */
#define VOCTPL2SIZ 16
+
+/*
+ * vocwdef's are fixed in size. They're allocated in a set of arrays
+ * (the voccxwp member of the voc context has the list of arrays). Each
+ * array is of a fixed number of vocwdef entries; a maximum number of
+ * vocwdef arrays is possible.
+ */
+#define VOCWPGSIZ 2000 /* number of vocwdef's per array */
+#define VOCWPGMAX 16 /* maximum number of vocwdef arrays */
+
+/*
+ * To find a vocwdef entry given its index, divide the index by the
+ * number of entries per array to find the array number, and use the
+ * remainder to find the index within that array.
+ */
+/*#define VOCW_IN_CACHE*/
+#ifdef VOCW_IN_CACHE
+vocwdef *vocwget(struct voccxdef *ctx, uint idx);
+#else
+#define vocwget(ctx, idx) \
+ ((idx) == VOCCXW_NONE ? (vocwdef *)0 : \
+ ((ctx)->voccxwp[(idx)/VOCWPGSIZ] + ((idx) % VOCWPGSIZ)))
+#endif
+
+/*
+ * Special values for vocdtim - these values indicate that the daemon
+ * does not have a normal turn-based expiration time.
+ */
+#define VOCDTIM_EACH_TURN 0xffff /* the daemon fires every turn */
+
+/* daemon/fuse/alarm slot */
+struct vocddef {
+ objnum vocdfn; /* object number of function to be invoked */
+ runsdef vocdarg; /* argument for daemon/fuse function */
+ prpnum vocdprp; /* property number (used only for alarms) */
+ uint vocdtim; /* time for fuses/alarms (0xffff -> each-turn alarm) */
+};
+
+/* vocabulary object list entry */
+struct vocoldef {
+ objnum vocolobj; /* object matching the word */
+ char *vocolfst; /* first word in cmd[] that identified object */
+ char *vocollst; /* last word in cmd[] that identified object */
+ char *vocolhlst; /* hypothetical last word, if we trimmed a prep */
+ int vocolflg; /* special flags (ALL, etc) */
+};
+
+/* vocabulary context */
+struct voccxdef {
+ errcxdef *voccxerr; /* error handling context */
+ tiocxdef *voccxtio; /* text i/o context */
+ runcxdef *voccxrun; /* execution context */
+ mcmcxdef *voccxmem; /* memory manager context */
+ objucxdef *voccxundo; /* undo context */
+ uchar *voccxpool; /* next free byte in vocdef pool */
+ vocdef *voccxfre; /* head of vocdef free list */
+ char *voccxcpp; /* pointer to compound word area */
+ int voccxcpl; /* length of compound word area */
+ char *voccxspp; /* pointer to special word area */
+ int voccxspl; /* length of special word area */
+ uint voccxrem; /* number of bytes remaining in vocdef pool */
+ vocidef **voccxinh[VOCINHMAX]; /* vocidef page table: 256 per page */
+ uchar *voccxip[VOCIPGMAX]; /* inheritance cell pool */
+ vocidef *voccxifr; /* head of inheritance cell free list */
+ uint voccxiplst; /* last inheritance cell page allocated */
+ uint voccxilst; /* next unused byte in last inheritance page */
+ int voccxredo; /* flag: redo command in buffer */
+
+ /*
+ * redo buffer - if voccxredo is set, and this buffer is not empty,
+ * we'll redo the command in this buffer rather than the one in our
+ * internal stack buffer
+ */
+ char voccxredobuf[VOCBUFSIZ];
+
+ /*
+ * "again" buffer - when we save the last command for repeating via
+ * the "again" command, we'll save the direct and indirect object
+ * words here, so that they can be recovered if "again" is used
+ */
+ char voccxagainbuf[VOCBUFSIZ];
+
+ vocdef *voccxhsh[VOCHASHSIZ]; /* hash table */
+
+#ifdef VOCW_IN_CACHE
+ mcmon voccxwp[VOCWPGMAX]; /* list of pages of vocab records */
+ mcmon voccxwplck; /* locked page of vocab records */
+ vocwdef *voccxwpgptr; /* pointer to currently locked page */
+#else
+ vocwdef *voccxwp[VOCWPGMAX]; /* vocabulary word pool */
+#endif
+
+ uint voccxwalocnt; /* number of vocwdef's used so far */
+ uint voccxwfre; /* index of first vocwdef in free list */
+#define VOCCXW_NONE ((uint)(-1)) /* index value indicating end of list */
+
+ vocddef *voccxdmn; /* array of daemon slots */
+ uint voccxdmc; /* number of slots in daemon array */
+ vocddef *voccxfus; /* array of fuse slots */
+ uint voccxfuc; /* number of slots in fuse array */
+ vocddef *voccxalm; /* array of alarm slots */
+ uint voccxalc; /* number of slots in alarm array */
+ char voccxtim[26]; /* game's timestamp (asctime value) */
+
+ objnum voccxvtk; /* object number of "take" deepverb */
+ objnum voccxme; /* object number of "Me" actor */
+ objnum voccxme_init; /* initial setting of "Me" */
+ objnum voccxstr; /* object number of "strObj" */
+ objnum voccxnum; /* object number of "numObj" */
+ objnum voccxit; /* last "it" value */
+ objnum voccxhim; /* last "him" value */
+ objnum voccxher; /* last "her" value */
+ objnum voccxthc; /* count of items in "them" list */
+ objnum voccxthm[VOCMAXAMBIG]; /* list of items in "them" */
+ objnum voccxprd; /* "pardon" function object number */
+ objnum voccxpre; /* "preparse" function object number */
+ objnum voccxppc; /* "preparseCmd" function object number */
+ objnum voccxpre2; /* "preparseExt" function object number */
+ objnum voccxvag; /* "again" verb object */
+ objnum voccxini; /* "init" function */
+ objnum voccxper; /* "parseError" function object number */
+ objnum voccxprom; /* "cmdPrompt" function object number */
+ objnum voccxpostprom; /* "cmdPostPrompt" function object number */
+ objnum voccxpdis; /* parseDisambig function */
+ objnum voccxper2; /* parseError2 function */
+ objnum voccxperp; /* parseErrorParam function */
+ objnum voccxpdef; /* parseDefault function */
+ objnum voccxpdef2; /* parseDefaultExt function */
+ objnum voccxpask; /* parseAskobj function */
+ objnum voccxpask2; /* parseAskobjActor function */
+ objnum voccxpask3; /* parseAskobjIndirect function */
+ objnum voccxinitrestore; /* "initRestore" function object number */
+ objnum voccxpuv; /* parseUnknownVerb function object number */
+ objnum voccxpnp; /* parseNounPhrase function object number */
+ objnum voccxpostact; /* postAction function object number */
+ objnum voccxprecmd; /* preCommand function object number */
+ objnum voccxendcmd; /* endCommand function object number */
+
+ /* current command word list values */
+ vocoldef *voccxdobj; /* current direct object word list */
+ vocoldef *voccxiobj; /* current indirect object word list */
+
+ /* current command objects */
+ objnum voccxactor; /* current actor */
+ objnum voccxverb; /* current command deepverb */
+ objnum voccxprep; /* current command preposition */
+
+ /* previous command values - used by "again" */
+ objnum voccxlsa; /* previous actor */
+ objnum voccxlsv; /* previous verb */
+ vocoldef voccxlsd; /* previous direct object */
+ vocoldef voccxlsi; /* previous indirect object */
+ objnum voccxlsp; /* preposition */
+ int voccxlssty; /* style (new/old) of last template */
+ uchar voccxlst[VOCTPL2SIZ]; /* template */
+
+ objnum voccxpreinit; /* preinit function */
+
+ /* special flags */
+ uchar voccxflg;
+#define VOCCXFCLEAR 1 /* ignore remainder of command line (restore) */
+#define VOCCXFVWARN 2 /* generate redundant verb warnings */
+#define VOCCXFDBG 4 /* debug mode: show parsing information */
+#define VOCCXAGAINDEL 8 /* "again" lost due to object deletion */
+
+ /* number of remaining unresolved unknown words in the command */
+ int voccxunknown;
+
+ /* total number of unresolved words in the last command */
+ int voccxlastunk;
+
+ /* parser stack area */
+ uchar *voc_stk_ptr;
+ uchar *voc_stk_cur;
+ uchar *voc_stk_end;
+};
+
+/* allocate and push a list, returning a pointer to the list's memory */
+uchar *voc_push_list_siz(voccxdef *ctx, uint lstsiz);
+
+/* push a list of objects from a vocoldef array */
+void voc_push_vocoldef_list(voccxdef *ctx, vocoldef *objlist, int cnt);
+
+/* push a list of objects from an objnum array */
+void voc_push_objlist(voccxdef *ctx, objnum objlist[], int cnt);
+
+/* change the player character ("Me") object */
+void voc_set_me(voccxdef *ctx, objnum new_me);
+
+/* add a vocabulary word */
+void vocadd(voccxdef *ctx, prpnum p, objnum objn,
+ int classflag, char *wrdval);
+
+/* internal addword - must already be split into two words and lengths */
+void vocadd2(voccxdef *ctx, prpnum p, objnum objn, int classflg,
+ uchar *wrd1, int len1, uchar *wrd2, int len2);
+
+/* delete vocabulary for a given object */
+void vocdel(voccxdef *ctx, objnum objn);
+
+/* lower-level vocabulary deletion routine */
+void vocdel1(voccxdef *ctx, objnum objn, char *wrd, prpnum prp,
+ int really_delete, int revert, int keep_undo);
+
+/* delete all inherited vocabulary */
+void vocdelinh(voccxdef *ctx);
+
+/* allocate space for an inheritance record if needed */
+void vocialo(voccxdef *ctx, objnum obj);
+
+/* add an inheritance/location record */
+void vociadd(voccxdef *ctx, objnum obj, objnum loc,
+ int numsc, objnum *sc, int flags);
+
+/* delete inheritance records for an object */
+void vocidel(voccxdef *ctx, objnum chi);
+
+/* renumber an object's inheritance records - used for 'modify' */
+void vociren(voccxdef *ctx, objnum oldnum, objnum newnum);
+
+/* caller-provided context structure for vocffw/vocfnw searches */
+struct vocseadef {
+ vocdef *v;
+ vocwdef *vw;
+ uchar *wrd1;
+ int len1;
+ uchar *wrd2;
+ int len2;
+};
+
+/* find first word matching a given word */
+vocwdef *vocffw(voccxdef *ctx, char *wrd, int len, char *wrd2, int len2,
+ int p, vocseadef *search_ctx);
+
+/* find next word */
+vocwdef *vocfnw(voccxdef *voccx, vocseadef *search_ctx);
+
+/* read a line of input text */
+int vocread(voccxdef *ctx, objnum actor, objnum verb,
+ char *buf, int bufl, int type);
+#define VOCREAD_OK 0
+#define VOCREAD_REDO 1
+
+/* compute size of a vocoldef list */
+int voclistlen(vocoldef *lst);
+
+/* tokenize an input buffer */
+int voctok(voccxdef *ctx, char *cmd, char *outbuf,
+ char **wrd, int lower, int cvt_ones, int show_errors);
+
+/* get types for a word list */
+int vocgtyp(voccxdef *ctx, char **cmd, int *types, char *orgbuf);
+
+/* execute a player command */
+int voccmd(voccxdef *ctx, char *cmd, uint cmdlen);
+
+/* disambiguator */
+int vocdisambig(voccxdef *ctx, vocoldef *outlist, vocoldef *inlist,
+ prpnum defprop, prpnum accprop, prpnum verprop,
+ char *cmd[], objnum otherobj, objnum cmdActor,
+ objnum cmdVerb, objnum cmdPrep, char *cmdbuf,
+ int silent);
+
+/* display a multiple-object prefix */
+void voc_multi_prefix(voccxdef *ctx, objnum objn,
+ int show_prefix, int multi_flags,
+ int cur_index, int count);
+
+/* low-level executor */
+int execmd(voccxdef *ctx, objnum actor, objnum prep,
+ char *vverb, char *vprep, vocoldef *dolist, vocoldef *iolist,
+ char **cmd, int *typelist,
+ char *cmdbuf, int wrdcnt, uchar **preparse_list, int *next_start);
+
+/* recursive command execution */
+int execmd_recurs(voccxdef *ctx, objnum actor, objnum verb,
+ objnum dobj, objnum prep, objnum iobj,
+ int validate_dobj, int validate_iobj);
+
+/* try running preparseCmd user function */
+int try_preparse_cmd(voccxdef *ctx, char **cmd, int wrdcnt,
+ uchar **preparse_list);
+
+/*
+ * Handle an unknown verb or sentence structure. We'll call this when
+ * we encounter a sentence where we don't know the verb word, or we
+ * don't know the combination of verb and verb preposition, or we don't
+ * recognize the sentence structure (for example, an indirect object is
+ * present, but we don't have a template defined using an indirect
+ * object for the verb).
+ *
+ * 'wrdcnt' is the number of words in the cmd[] array. If wrdcnt is
+ * zero, we'll automatically count the array entries, with the end of
+ * the array indicated by a null pointer entry.
+ *
+ * If do_fuses is true, we'll execute the fuses and daemons if the
+ * function exists and doesn't throw an ABORT error, or any other
+ * run-time error other than EXIT.
+ *
+ * This function calls the game-defined function parseUnknownVerb, if it
+ * exists. If the function doesn't exist, we'll simply display the
+ * given error message, using the normal parseError mechanism. The
+ * function should use "abort" or "exit" if it wants to cancel further
+ * processing of the command.
+ *
+ * We'll return true if the function exists and executes successfully,
+ * in which case normal processing should continue with any remaining
+ * command on the command line. We'll return false if the function
+ * doesn't exist or throws an error other than EXIT, in which case the
+ * remainder of the command should be aborted.
+ */
+int try_unknown_verb(voccxdef *ctx, objnum actor,
+ char **cmd, int *typelist, int wrdcnt, int *next_start,
+ int do_fuses, int err, char *msg, ...);
+
+/* find a template */
+int voctplfnd(voccxdef *ctx, objnum verb_in, objnum prep,
+ uchar *tplout, int *newstyle);
+
+/* build a printable name for an object from the words in a command list */
+void voc_make_obj_name(voccxdef *ctx, char *namebuf, char *cmd[],
+ int firstwrd, int lastwrd);
+void voc_make_obj_name_from_list(voccxdef *ctx, char *namebuf,
+ char *cmd[], char *firstwrd, char *lastwrd);
+
+/*
+ * check noun - determines whether the next set of words is a valid noun
+ * phrase. No complaint is issued if not; this check is generally made
+ * to figure out what type of sentence we're dealing with. This is
+ * simple; we just call vocgobj() with the complaint flag turned off.
+ */
+/* int vocchknoun(voccxdef *ctx, char **cmd, int *typelist, int cur,
+ int *next, vocoldef *nounlist, int chkact); */
+#define vocchknoun(ctx, cmd, typelist, cur, next, nounlist, chkact) \
+ vocgobj(ctx, cmd, typelist, cur, next, FALSE, nounlist, TRUE, chkact, 0)
+#define vocchknoun2(ctx, cmd, typlst, cur, next, nounlist, chkact, nomatch) \
+ vocgobj(ctx, cmd, typlst, cur, next, FALSE, nounlist, TRUE, chkact, nomatch)
+
+/*
+ * get noun - reads an object list. We simply call vocgobj() with the
+ * complaint and multiple-noun flags turned on.
+ */
+/* int vocgetnoun(voccxdef *ctx, char **cmd, int *typelist, int cur,
+ int *next, vocoldef *nounlist); */
+#define vocgetnoun(ctx, cmd, typelist, cur, next, nounlist) \
+ vocgobj(ctx, cmd, typelist, cur, next, TRUE, nounlist, TRUE, FALSE, 0)
+
+/* get object */
+int vocgobj(voccxdef *ctx, char **cmd, int *typelist, int cur,
+ int *next, int complain, vocoldef *nounlist,
+ int multi, int chkact, int *nomatch);
+
+/* tokenize a string - TADS program code interface */
+void voc_parse_tok(voccxdef *ctx);
+
+/* get token types - TADS program code interface */
+void voc_parse_types(voccxdef *ctx);
+
+/* get objects matching all of the given words - TADS program code interface */
+void voc_parse_dict_lookup(voccxdef *ctx);
+
+/* parse a noun list - TADS program code interface */
+void voc_parse_np(voccxdef *ctx);
+
+/* disambiguate a noun list - TADS program code interface */
+void voc_parse_disambig(voccxdef *ctx);
+
+/* replace the current command - TADS program code interface */
+void voc_parse_replace_cmd(voccxdef *ctx);
+
+/* check access to an object */
+int vocchkaccess(voccxdef *ctx, objnum obj, prpnum verprop,
+ int seqno, objnum actor, objnum verb);
+
+/* check to see if an object is visible */
+int vocchkvis(voccxdef *ctx, objnum obj, objnum cmdActor);
+
+/* display an appropriate message for an unreachable object */
+void vocnoreach(voccxdef *ctx, objnum *list1, int cnt,
+ objnum actor, objnum verb, objnum prep, prpnum defprop,
+ int show_multi_prefix, int multi_flags,
+ int multi_base_index, int multi_total_count);
+
+/* set {numObj | strObj}.value, as appropriate */
+void vocsetobj(voccxdef *ctx, objnum obj, dattyp typ, void *val,
+ vocoldef *inobj, vocoldef *outobj);
+
+/* macros to read values out of templates */
+#define voctplpr(tpl) ((objnum)osrp2(((uchar *)tpl))) /* preposition */
+#define voctplvi(tpl) ((prpnum)osrp2(((uchar *)tpl) + 2)) /* verIoVerb */
+#define voctplio(tpl) ((prpnum)osrp2(((uchar *)tpl) + 4)) /* ioVerb */
+#define voctplvd(tpl) ((prpnum)osrp2(((uchar *)tpl) + 6)) /* verDoVerb */
+#define voctpldo(tpl) ((prpnum)osrp2(((uchar *)tpl) + 8)) /* doVerb */
+#define voctplflg(tpl) (*(((uchar *)tpl) + 10)) /* flags */
+
+/* flag values for the voctplflg */
+#define VOCTPLFLG_DOBJ_FIRST 0x01 /* disambiguate direct object first */
+
+
+/* word type flags */
+#define VOCT_ARTICLE 1
+#define VOCT_ADJ 2
+#define VOCT_NOUN 4
+#define VOCT_PREP 8
+#define VOCT_VERB 16
+#define VOCT_SPEC 32 /* special words - "of", ",", ".", etc. */
+#define VOCT_PLURAL 64
+#define VOCT_UNKNOWN 128 /* word is unknown */
+
+/* special type flags */
+#define VOCS_ALL 1 /* "all" */
+#define VOCS_EXCEPT 2 /* "except" */
+#define VOCS_IT 4 /* "it" */
+#define VOCS_THEM 8 /* "them" */
+#define VOCS_NUM 16 /* a number */
+#define VOCS_COUNT 32 /* a number being used as a count */
+#define VOCS_PLURAL 64 /* plural */
+#define VOCS_ANY 128 /* "any" */
+#define VOCS_HIM 256 /* "him" */
+#define VOCS_HER 512 /* "her" */
+#define VOCS_STR 1024 /* a quoted string */
+#define VOCS_UNKNOWN 2048 /* noun phrase contains an unknown word */
+#define VOCS_ENDADJ 4096 /* word matched adjective at end of phrase */
+#define VOCS_TRUNC 8192 /* truncated match - word is leading substring */
+#define VOCS_TRIMPREP 16384 /* trimmed prep phrase: assumed it was for verb */
+
+/* special internally-defined one-character word flags */
+#define VOCW_AND ','
+#define VOCW_THEN '.'
+#define VOCW_OF 'O'
+#define VOCW_ALL 'A'
+#define VOCW_BOTH 'B'
+#define VOCW_IT 'I'
+#define VOCW_HIM 'M'
+#define VOCW_ONE 'N'
+#define VOCW_ONES 'P'
+#define VOCW_HER 'R'
+#define VOCW_THEM 'T'
+#define VOCW_BUT 'X'
+#define VOCW_ANY 'Y'
+
+/* structure for special internal word table */
+struct vocspdef {
+ char *vocspin;
+ char vocspout;
+};
+
+/* check if a word is a special word - true if word is given special word */
+/* int vocspec(char *wordptr, int speccode); */
+#define vocspec(w, s) (*(w) == (s))
+
+/*
+ * Set a fuse/daemon/notifier.
+ */
+void vocsetfd(voccxdef *ctx, vocddef *what, objnum func, prpnum prop,
+ uint tm, runsdef *val, int err);
+
+/* remove a fuse/daemon/notifier */
+void vocremfd(voccxdef *ctx, vocddef *what, objnum func, prpnum prop,
+ runsdef *val, int err);
+
+/* count a turn (down all fuse/notifier timers) */
+void vocturn(voccxdef *ctx, int turncnt, int do_fuses);
+
+/* initialize voc context */
+void vocini(voccxdef *vocctx, errcxdef *errctx, mcmcxdef *memctx,
+ runcxdef *runctx, objucxdef *undoctx, int fuses,
+ int daemons, int notifiers);
+
+/* clean up the voc context - frees memory allocated by vocini() */
+void vocterm(voccxdef *vocctx);
+
+/* allocate/free fuse/daemon/notifier array for voc ctx initialization */
+void vocinialo(voccxdef *ctx, vocddef **what, int cnt);
+void voctermfree(vocddef *what);
+
+/* get a vocidef given an object number */
+/* vocidef *vocinh(voccxdef *ctx, objnum obj); */
+#define vocinh(ctx, obj) ((ctx)->voccxinh[(obj) >> 8][(obj) & 255])
+
+/* revert all objects back to original state, using inheritance records */
+void vocrevert(voccxdef *ctx);
+
+/* clear all fuses/daemons/notifiers (useful for restarting) */
+void vocdmnclr(voccxdef *ctx);
+
+/* display a parser error message */
+void vocerr(voccxdef *ctx, int err, char *f, ...);
+
+/*
+ * display a parser informational error message - this will display the
+ * message whether or not we're suppressing messages due to unknown
+ * words, and should be used when providing information, such as objects
+ * we're assuming by default
+ */
+void vocerr_info(voccxdef *ctx, int err, char *f, ...);
+
+/* client undo callback - undoes a daemon/fuse/notifier */
+void vocdundo(void *ctx, uchar *data);
+
+/* client undo size figuring callback - return size of client undo record */
+ushort OS_LOADDS vocdusz(void *ctx, uchar *data);
+
+/* save undo for object creation */
+void vocdusave_newobj(voccxdef *ctx, objnum objn);
+
+/* save undo for adding a word */
+void vocdusave_addwrd(voccxdef *ctx, objnum objn, prpnum typ, int flags,
+ char *wrd);
+
+/* save undo for deleting a word */
+void vocdusave_delwrd(voccxdef *ctx, objnum objn, prpnum typ, int flags,
+ char *wrd);
+
+/* save undo for object deletion */
+void vocdusave_delobj(voccxdef *ctx, objnum objn);
+
+/* save undo for changing the "Me" object */
+void vocdusave_me(voccxdef *ctx, objnum old_me);
+
+/* compute vocabulary word hash value */
+uint vochsh(uchar *t, int len);
+
+/* TADS versions of isalpha, isspace, isdigit, etc */
+#define vocisupper(c) ((uchar)(c) <= 127 && isupper((uchar)(c)))
+#define vocislower(c) ((uchar)(c) <= 127 && islower((uchar)(c)))
+#define vocisalpha(c) ((uchar)(c) > 127 || isalpha((uchar)(c)))
+#define vocisspace(c) ((uchar)(c) <= 127 && isspace((uchar)(c)))
+#define vocisdigit(c) ((uchar)(c) <= 127 && isdigit((uchar)(c)))
+
+
+/*
+ * Undo types for voc subsystem
+ */
+#define VOC_UNDO_DAEMON 1 /* fuse/daemon status change */
+#define VOC_UNDO_NEWOBJ 2 /* object creation */
+#define VOC_UNDO_DELOBJ 3 /* object deletion */
+#define VOC_UNDO_ADDVOC 4 /* add vocabulary to an object */
+#define VOC_UNDO_DELVOC 5 /* delete vocabulary from an object */
+#define VOC_UNDO_SETME 6 /* set the "Me" object */
+
+
+/*
+ * Our own stack. We need to allocate some fairly large structures
+ * (for the disambiguation lists, mostly) in a stack-like fashion, and
+ * we don't want to consume vast quantities of the real stack, because
+ * some machines have relatively restrictive limitations on stack usage.
+ * To provide some elbow room, we'll use a stack-like structure of our
+ * own: we'll allocate out of this structure as needed, and whenever we
+ * leave a C stack frame, we'll also leave our own stack frame.
+ */
+
+/* re-initialize the stack, allocating space for it if needed */
+void voc_stk_ini(voccxdef *ctx, uint siz);
+
+/* enter a stack frame, marking our current position */
+#define voc_enter(ctx, marker) (*(marker) = (ctx)->voc_stk_cur)
+
+/* leave a stack frame, restoring the entry position */
+#define voc_leave(ctx, marker) ((ctx)->voc_stk_cur = marker)
+
+/* return a value */
+#define VOC_RETVAL(ctx, marker, retval) \
+ voc_leave(ctx, marker); return retval
+
+/* allocate space from the stack */
+void *voc_stk_alo(voccxdef *ctx, uint siz);
+
+/* allocation cover macros */
+#define VOC_STK_ARRAY(ctx, typ, var, cnt) \
+ (var = (typ *)voc_stk_alo(ctx, (uint)((cnt) * sizeof(typ))))
+
+#define VOC_MAX_ARRAY(ctx, typ, var) \
+ VOC_STK_ARRAY(ctx, typ, var, VOCMAXAMBIG)
+
+/*
+ * Stack size for the vocab stack. We'll scale our stack needs based
+ * on the size of the vocoldef structure, since this is the most common
+ * item to be allocated on the vocab stack. We'll also scale based on
+ * the defined VOCMAXAMBIG parameter, since it is the number of elements
+ * usually allocated. The actual amount of space needed depends on how
+ * the functions in vocab.c and execmd.c work, so this parameter may
+ * need to be adjusted for changes to the player command parser.
+ */
+#define VOC_STACK_SIZE (16 * VOCMAXAMBIG * sizeof(vocoldef))
+
+/*
+ * Execute all fuses and daemons, then execute the endCommand user
+ * function. Returns zero on success, or ERR_ABORT if 'abort' was
+ * thrown during execution. This is a convenient cover single function
+ * to do all end-of-turn processing; this calls exefuse() and exedaem()
+ * as needed, trapping any 'abort' or 'exit' errors that occur.
+ *
+ * If 'do_fuses' is true, we'll run fuses and daemons. Otherwise,
+ */
+int exe_fuses_and_daemons(voccxdef *ctx, int err, int do_fuses,
+ objnum actor, objnum verb,
+ vocoldef *dobj_list, int do_cnt,
+ objnum prep, objnum iobj);
+
+/*
+ * Execute any pending fuses. Return TRUE if any fuses were executed,
+ * FALSE otherwise.
+ */
+int exefuse(voccxdef *ctx, int do_run);
+
+/*
+ * Execute daemons
+ */
+void exedaem(voccxdef *ctx);
+
+/*
+ * Get the number and size of words defined for an object. The size
+ * returns the total byte count from all the words involved. Do not
+ * include deleted words in the count.
+ */
+void voc_count(voccxdef *ctx, objnum objn, prpnum prp, int *cnt, int *siz);
+
+/*
+ * Iterate through all words for a particular object, calling a
+ * function with each vocwdef found. If objn == MCMONINV, we'll call
+ * the callback for every word.
+ */
+void voc_iterate(voccxdef *ctx, objnum objn,
+ void (*fn)(void *, vocdef *, vocwdef *), void *fnctx);
+
+/* ------------------------------------------------------------------------ */
+/*
+ * disambiguation status codes - used for disambigDobj and disambigIobj
+ * methods in the deepverb
+ */
+
+/* continue with disambiguation process (using possibly updated list) */
+#define VOC_DISAMBIG_CONT 1
+
+/* done - the list is fully resolved; return with (possibly updated) list */
+#define VOC_DISAMBIG_DONE 2
+
+/* error - abort the command */
+#define VOC_DISAMBIG_ERROR 3
+
+/* parse string returned in second element of list as interactive response */
+#define VOC_DISAMBIG_PARSE_RESP 4
+
+/* already asked for an interactive response, but didn't read it yet */
+#define VOC_DISAMBIG_PROMPTED 5
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * parseNounPhrase status codes
+ */
+
+/* parse error occurred */
+#define VOC_PNP_ERROR 1
+
+/* use built-in default parser */
+#define VOC_PNP_DEFAULT 2
+
+/* successful parse */
+#define VOC_PNP_SUCCESS 3
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * parserResolveObjects usage codes
+ */
+#define VOC_PRO_RESOLVE_DOBJ 1 /* direct object */
+#define VOC_PRO_RESOLVE_IOBJ 2 /* indirect object */
+#define VOC_PRO_RESOLVE_ACTOR 3 /* actor */
+
} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk