[Scummvm-git-logs] scummvm master -> 608e9af69d203d4dd4fc65d16ef808d945341ed3
dreammaster
paulfgilbert at gmail.com
Sat May 25 03:29:41 CEST 2019
This automated email contains information about 20 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
b1f8e2ce83 GLK: HUGO: Move Hugo engine out of RELEASE_BUILD blocks
54d240d81f GLK: TADS: Improved detection to detect TADS version
0279143a62 GLK: TADS2: Adding headers
e83972f502 GLK: TADS2: Added code for tokenizer & vocabulary
105a1b94bd GLK: TADS2: Added code for output, run, various miscellaneous
f607792fa4 GLK: TADS2: More code files implemented
fcb2592ec2 GLK: TADS2: Added built in functions
3d9e03af55 GLK: TADS2: Soooo much more implementation
bc87d740f5 GLK: TADS2: Yet more volumous code additions
5aacb45645 GLK: TADS2: Added plygo
e3454b5d96 GLK: TADS: Don't support RTL
0d172c8ca6 GLK: Fix freeing closed windows
0c02346b48 GLK: TADS2: Fix os initialization
aa5fd603b6 GLK: TADS2: Compilation fixes
1a974b9749 GLK: TADS2: Further compilation fixes
5381930e8b GLK: TADS2: Fix uninitialized variable warnings
8f9fcec16c GLK: TADS2: Further compilation fixes
0d0dbf601e GLK: TADS2: Fix uninitialized variable warnings
139673984e GLK: TADS2: Revert char * statics back to string literals
608e9af69d GLK: TADS2: Fix more const loss warnings
Commit: b1f8e2ce837608ee18ebabe1097d64faae9c2243
https://github.com/scummvm/scummvm/commit/b1f8e2ce837608ee18ebabe1097d64faae9c2243
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00
Commit Message:
GLK: HUGO: Move Hugo engine out of RELEASE_BUILD blocks
Changed paths:
engines/glk/detection.cpp
diff --git a/engines/glk/detection.cpp b/engines/glk/detection.cpp
index fe2ea0b..a13bed3 100644
--- a/engines/glk/detection.cpp
+++ b/engines/glk/detection.cpp
@@ -113,10 +113,10 @@ Common::Error GlkMetaEngine::createInstance(OSystem *syst, Engine **engine) cons
*engine = nullptr;
if ((*engine = create<Glk::Frotz::FrotzMetaEngine, Glk::Frotz::Frotz>(syst, gameDesc)) != nullptr) {}
else if ((*engine = create<Glk::Glulxe::GlulxeMetaEngine, Glk::Glulxe::Glulxe>(syst, gameDesc)) != nullptr) {}
+ else if ((*engine = create<Glk::Hugo::HugoMetaEngine, Glk::Hugo::Hugo>(syst, gameDesc)) != nullptr) {}
else if ((*engine = create<Glk::Scott::ScottMetaEngine, Glk::Scott::Scott>(syst, gameDesc)) != nullptr) {}
#ifndef RELEASE_BUILD
else if ((*engine = create<Glk::Alan2::Alan2MetaEngine, Glk::Alan2::Alan2>(syst, gameDesc)) != nullptr) {}
- else if ((*engine = create<Glk::Hugo::HugoMetaEngine, Glk::Hugo::Hugo>(syst, gameDesc)) != nullptr) {}
else if ((*engine = create<Glk::Magnetic::MagneticMetaEngine, Glk::Magnetic::Magnetic>(syst, gameDesc)) != nullptr) {}
else if ((td = Glk::TADS::TADSMetaEngine::findGame(gameDesc._gameId.c_str()))._description) {
if (td._options & Glk::TADS::OPTION_TADS3)
@@ -158,10 +158,10 @@ PlainGameList GlkMetaEngine::getSupportedGames() const {
PlainGameList list;
Glk::Frotz::FrotzMetaEngine::getSupportedGames(list);
Glk::Glulxe::GlulxeMetaEngine::getSupportedGames(list);
+ Glk::Hugo::HugoMetaEngine::getSupportedGames(list);
Glk::Scott::ScottMetaEngine::getSupportedGames(list);
#ifndef RELEASE_BUILD
Glk::Alan2::Alan2MetaEngine::getSupportedGames(list);
- Glk::Hugo::HugoMetaEngine::getSupportedGames(list);
Glk::Magnetic::MagneticMetaEngine::getSupportedGames(list);
Glk::TADS::TADSMetaEngine::getSupportedGames(list);
#endif
@@ -176,6 +176,9 @@ PlainGameDescriptor GlkMetaEngine::findGame(const char *gameId) const {
gd = Glk::Glulxe::GlulxeMetaEngine::findGame(gameId);
if (gd._description) return gd;
+ gd = Glk::Hugo::HugoMetaEngine::findGame(gameId);
+ if (gd._description) return gd;
+
gd = Glk::Scott::ScottMetaEngine::findGame(gameId);
if (gd._description) return gd;
@@ -183,9 +186,6 @@ PlainGameDescriptor GlkMetaEngine::findGame(const char *gameId) const {
gd = Glk::Alan2::Alan2MetaEngine::findGame(gameId);
if (gd._description) return gd;
- gd = Glk::Hugo::HugoMetaEngine::findGame(gameId);
- if (gd._description) return gd;
-
gd = Glk::Magnetic::MagneticMetaEngine::findGame(gameId);
if (gd._description) return gd;
@@ -202,11 +202,11 @@ DetectedGames GlkMetaEngine::detectGames(const Common::FSList &fslist) const {
DetectedGames detectedGames;
Glk::Frotz::FrotzMetaEngine::detectGames(fslist, detectedGames);
Glk::Glulxe::GlulxeMetaEngine::detectGames(fslist, detectedGames);
+ Glk::Hugo::HugoMetaEngine::detectGames(fslist, detectedGames);
Glk::Scott::ScottMetaEngine::detectGames(fslist, detectedGames);
#ifndef RELEASE_BUILD
Glk::Alan2::Alan2MetaEngine::detectGames(fslist, detectedGames);
- Glk::Hugo::HugoMetaEngine::detectGames(fslist, detectedGames);
Glk::Magnetic::MagneticMetaEngine::detectGames(fslist, detectedGames);
Glk::TADS::TADSMetaEngine::detectGames(fslist, detectedGames);
#endif
@@ -218,11 +218,11 @@ void GlkMetaEngine::detectClashes() const {
Common::StringMap map;
Glk::Frotz::FrotzMetaEngine::detectClashes(map);
Glk::Glulxe::GlulxeMetaEngine::detectClashes(map);
+ Glk::Hugo::HugoMetaEngine::detectClashes(map);
Glk::Scott::ScottMetaEngine::detectClashes(map);
#ifndef RELEASE_BUILD
Glk::Alan2::Alan2MetaEngine::detectClashes(map);
- Glk::Hugo::HugoMetaEngine::detectClashes(map);
Glk::Magnetic::MagneticMetaEngine::detectClashes(map);
Glk::TADS::TADSMetaEngine::detectClashes(map);
#endif
Commit: 54d240d81f8858f7ad694c690fcf738b3ec8b89d
https://github.com/scummvm/scummvm/commit/54d240d81f8858f7ad694c690fcf738b3ec8b89d
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00
Commit Message:
GLK: TADS: Improved detection to detect TADS version
Changed paths:
engines/glk/tads/detection.cpp
engines/glk/tads/detection.h
diff --git a/engines/glk/tads/detection.cpp b/engines/glk/tads/detection.cpp
index 4df6d25..51a29d1 100644
--- a/engines/glk/tads/detection.cpp
+++ b/engines/glk/tads/detection.cpp
@@ -59,7 +59,7 @@ GameDescriptor TADSMetaEngine::findGame(const char *gameId) {
}
bool TADSMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) {
- const char *const EXTENSIONS[] = { ".gam", nullptr };
+ const char *const EXTENSIONS[] = { ".gam", ".t3", nullptr };
// Loop through the files of the folder
for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
@@ -68,7 +68,8 @@ bool TADSMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &ga
continue;
Common::String filename = file->getName();
- bool hasExt = Blorb::hasBlorbExt(filename), isBlorb = false;
+ bool hasExt = Blorb::hasBlorbExt(filename), isBlorb = true;
+ int tadsVersion = -1;
for (const char *const *ext = &EXTENSIONS[0]; *ext && !hasExt; ++ext)
hasExt = filename.hasSuffixIgnoreCase(*ext);
if (!hasExt)
@@ -81,10 +82,21 @@ bool TADSMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &ga
Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000);
size_t filesize = gameFile.size();
gameFile.seek(0);
- isBlorb = Blorb::isBlorb(gameFile, ID_TAD2) || Blorb::isBlorb(gameFile, ID_TAD3);
+ if (Blorb::isBlorb(gameFile, ID_TAD2))
+ tadsVersion = 2;
+ else if (Blorb::isBlorb(gameFile, ID_TAD3))
+ tadsVersion = 3;
+ else
+ isBlorb = false;
+
+ if (!isBlorb)
+ // Figure out the TADS version
+ tadsVersion = getTADSVersion(gameFile);
+
gameFile.close();
- if (!isBlorb && Blorb::hasBlorbExt(filename))
+ if (tadsVersion == -1)
+ // Not a TADS game, or Blorb containing TADS game, so can be ignored
continue;
// Check for known games
@@ -95,15 +107,15 @@ bool TADSMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &ga
DetectedGame gd;
if (!p->_gameId) {
if (gDebugLevel > 0) {
- // Print an entry suitable for putting into the detection_tables.h, using the
+ // Print an entry suitable for putting into the detection_tables.h
Common::String fname = filename;
const char *dot = strchr(fname.c_str(), '.');
if (dot)
fname = Common::String(fname.c_str(), dot);
- debug("ENTRY0(\"%s\", \"%s\", %u),", fname.c_str(), md5.c_str(), (uint)filesize);
+ debug("TADS%d ENTRY0(\"%s\", \"%s\", %u),", tadsVersion, fname.c_str(), md5.c_str(), (uint)filesize);
}
- const GameDescriptor &desc = TADS2_GAME_LIST[0];
+ const GameDescriptor &desc = tadsVersion == 2 ? TADS2_GAME_LIST[0] : TADS3_GAME_LIST[0];
gd = DetectedGame(desc._gameId, desc._description, Common::UNK_LANG, Common::kPlatformUnknown);
} else {
PlainGameDescriptor gameDesc = findGame(p->_gameId);
@@ -130,5 +142,20 @@ void TADSMetaEngine::detectClashes(Common::StringMap &map) {
}
}
+int TADSMetaEngine::getTADSVersion(Common::SeekableReadStream &game) {
+ // Read in the start of the file
+ char buffer[16];
+ game.seek(0);
+ game.read(buffer, 16);
+
+ // Check for valid game headers
+ if (memcmp(buffer, "TADS2 bin\n\r\032", 12) == 0)
+ return 2;
+ else if (memcmp(buffer, "T3-image\r\n\032", 11) == 0)
+ return 3;
+ else
+ return -1;
+}
+
} // End of namespace TADS
} // End of namespace Glk
diff --git a/engines/glk/tads/detection.h b/engines/glk/tads/detection.h
index 0ed1e94..5fadd57 100644
--- a/engines/glk/tads/detection.h
+++ b/engines/glk/tads/detection.h
@@ -56,6 +56,13 @@ public:
* Check for game Id clashes with other sub-engines
*/
static void detectClashes(Common::StringMap &map);
+
+ /**
+ * Determines whether the given game is TADS 2 or 3
+ * @param game Open stream pointing to game file
+ * @returns 2 for TADS 2, 3 for TADS 3, or -1 for error
+ */
+ static int getTADSVersion(Common::SeekableReadStream &game);
};
} // End of namespace TADS
Commit: 0279143a62c2cdab635894103da03b43eba7cf9c
https://github.com/scummvm/scummvm/commit/0279143a62c2cdab635894103da03b43eba7cf9c
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00
Commit Message:
GLK: TADS2: Adding headers
Changed paths:
A engines/glk/tads/os_filetype.h
A engines/glk/tads/osfrobtads.cpp
A engines/glk/tads/osfrobtads.h
A engines/glk/tads/tads2/appctx.h
A engines/glk/tads/tads2/built_in.h
A engines/glk/tads/tads2/character_map.cpp
A engines/glk/tads/tads2/character_map.h
A engines/glk/tads/tads2/debug.h
A engines/glk/tads/tads2/error.cpp
A engines/glk/tads/tads2/error.h
A engines/glk/tads/tads2/error_handling.cpp
A engines/glk/tads/tads2/error_handling.h
A engines/glk/tads/tads2/file_io.cpp
A engines/glk/tads/tads2/file_io.h
A engines/glk/tads/tads2/lib.h
A engines/glk/tads/tads2/line_source.h
A engines/glk/tads/tads2/memory_cache.cpp
A engines/glk/tads/tads2/memory_cache.h
A engines/glk/tads/tads2/memory_cache_heap.cpp
A engines/glk/tads/tads2/memory_cache_heap.h
A engines/glk/tads/tads2/memory_cache_loader.cpp
A engines/glk/tads/tads2/memory_cache_loader.h
A engines/glk/tads/tads2/memory_cache_swap.cpp
A engines/glk/tads/tads2/memory_cache_swap.h
A engines/glk/tads/tads2/object.cpp
A engines/glk/tads/tads2/object.h
A engines/glk/tads/tads2/opcode_defs.h
A engines/glk/tads/tads2/post_compilation.h
A engines/glk/tads/tads2/property.h
A engines/glk/tads/tads2/run.cpp
A engines/glk/tads/tads2/run.h
A engines/glk/tads/tads2/text_io.h
A engines/glk/tads/tads2/tokenizer.cpp
A engines/glk/tads/tads2/tokenizer.h
R engines/glk/tads/tads2/ler.cpp
R engines/glk/tads/tads2/ler.h
R engines/glk/tads/tads2/tads2_cmap.cpp
R engines/glk/tads/tads2/types.h
engines/glk/module.mk
engines/glk/tads/tads2/data.cpp
engines/glk/tads/tads2/data.h
engines/glk/tads/tads2/os.cpp
engines/glk/tads/tads2/os.h
engines/glk/tads/tads2/regex.cpp
engines/glk/tads/tads2/regex.h
engines/glk/tads/tads2/tads2.cpp
engines/glk/tads/tads2/tads2.h
engines/glk/tads/tads2/vocabulary.cpp
engines/glk/tads/tads2/vocabulary.h
diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index d84d542..43c2523 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 0000000..93b1016
--- /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 0000000..fbcf1c1
--- /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 0000000..2dce84c
--- /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 0000000..3b3f499
--- /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 0000000..f5807e6
--- /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 0000000..91daa86
--- /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 0000000..c61406f
--- /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 c60fcb2..b19f45f 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 8db2153..8d122c4 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 0000000..52695cf
--- /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 0000000..793ddcc
--- /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 0000000..9683106
--- /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 0000000..a2300ee
--- /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 0000000..a225bbe
--- /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 0000000..581601f
--- /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 0000000..8599c49
--- /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 d996977..0000000
--- 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 57e3e63..0000000
--- 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 0000000..605f072
--- /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 0000000..a7e5454
--- /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 0000000..3bab3b0
--- /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 0000000..ec0383b
--- /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 0000000..fae3c5e
--- /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 0000000..269cdec
--- /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 0000000..f403299
--- /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 0000000..c7ec22d
--- /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 0000000..114a725
--- /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 0000000..f1b9849
--- /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 0000000..2ba1161
--- /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 0000000..5e2499c
--- /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 0000000..32519ae
--- /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 15a291a..e51a4d9 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 45ab804..5dcc83c 100644
--- a/engines/glk/tads/tads2/os.h
+++ b/engines/glk/tads/tads2/os.h
@@ -20,219 +20,5137 @@
*
*/
+/* 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.
+ *
Commit: e83972f502142b4e6191b962a7be89829bd8d708
https://github.com/scummvm/scummvm/commit/e83972f502142b4e6191b962a7be89829bd8d708
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00
Commit Message:
GLK: TADS2: Added code for tokenizer & vocabulary
Changed paths:
A engines/glk/tads/tads2/line_source_file.cpp
A engines/glk/tads/tads2/line_source_file.h
engines/glk/module.mk
engines/glk/tads/osfrobtads.cpp
engines/glk/tads/osfrobtads.h
engines/glk/tads/tads2/file_io.h
engines/glk/tads/tads2/lib.h
engines/glk/tads/tads2/tads2.cpp
engines/glk/tads/tads2/tokenizer.cpp
engines/glk/tads/tads2/tokenizer.h
engines/glk/tads/tads2/vocabulary.cpp
engines/glk/tads/tads2/vocabulary.h
diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index 43c2523..9f95fc0 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -99,6 +99,7 @@ MODULE_OBJS := \
tads/tads2/error.o \
tads/tads2/error_handling.o \
tads/tads2/file_io.o \
+ tads/tads2/line_source_file.o \
tads/tads2/memory_cache.o \
tads/tads2/memory_cache_heap.o \
tads/tads2/memory_cache_loader.o \
diff --git a/engines/glk/tads/osfrobtads.cpp b/engines/glk/tads/osfrobtads.cpp
index fbcf1c1..13229e5 100644
--- a/engines/glk/tads/osfrobtads.cpp
+++ b/engines/glk/tads/osfrobtads.cpp
@@ -34,8 +34,20 @@ osfildef *osfoprb(const char *fname, os_filetype_t typ) {
return nullptr;
}
+osfildef *osfoprwtb(const char *fname, os_filetype_t typ) {
+ Common::DumpFile *df = new Common::DumpFile();
+ if (df->open(fname))
+ return df;
+ delete df;
+ return nullptr;
+}
+
int osfrb(osfildef *fp, void *buf, size_t count) {
- return fp->read(buf, count);
+ return dynamic_cast<Common::ReadStream *>(fp)->read(buf, count);
+}
+
+bool osfwb(osfildef *fp, void *buf, size_t count) {
+ return dynamic_cast<Common::WriteStream *>(fp)->write(buf, count) != count;
}
} // End of namespace TADS
diff --git a/engines/glk/tads/osfrobtads.h b/engines/glk/tads/osfrobtads.h
index 2dce84c..b22dbae 100644
--- a/engines/glk/tads/osfrobtads.h
+++ b/engines/glk/tads/osfrobtads.h
@@ -38,6 +38,12 @@
namespace Glk {
namespace TADS {
+#define OSPATHCHAR '/'
+#define OSPATHALT ""
+#define OSPATHURL "/"
+#define OSPATHSEP ':'
+#define OS_NEWLINE_SEQ "\n"
+
/* Defined for Gargoyle. */
#define HAVE_STDINT_
@@ -92,8 +98,12 @@ namespace TADS {
#define OSFNMAX 255
-/* File handle structure for osfxxx functions. */
-typedef Common::SeekableReadStream osfildef;
+/**
+ * File handle structure for osfxxx functions
+ * Note that we need to define it as a Common::Stream since the type is used by
+ * TADS for both reading and writing files
+ */
+typedef Common::Stream osfildef;
/* Directory handle for searches via os_open_dir() et al. */
typedef Common::FSNode *osdirhdl_t;
@@ -188,18 +198,18 @@ osfildef *osfoprwt(const char *fname, os_filetype_t typ);
#define osfoprs osfoprt
/* Open binary file for reading. */
-osfildef *osfoprb(const char *fname, os_filetype_t typ);
+inline 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 );
+osfoprwb(const char *fname, os_filetype_t typ);
-/* Open binary file for reading/writing. If the file already exists,
+/* Open binary file for 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"))
+inline osfildef *osfoprwtb(const char *fname, os_filetype_t typ);
/* Get a line of text from a text file. */
#define osfgets fgets
@@ -208,7 +218,7 @@ osfoprwb( const char* fname, os_filetype_t typ );
#define osfputs fputs
/* Write bytes to file. */
-#define osfwb(fp,buf,bufl) (fwrite((buf),(bufl),1,(fp))!=1)
+inline bool osfwb(osfildef *fp, void *buf, size_t count);
/* Flush buffered writes to a file. */
#define osfflush fflush
diff --git a/engines/glk/tads/tads2/file_io.h b/engines/glk/tads/tads2/file_io.h
index 8599c49..0edfbae 100644
--- a/engines/glk/tads/tads2/file_io.h
+++ b/engines/glk/tads/tads2/file_io.h
@@ -28,6 +28,8 @@
#define GLK_TADS_TADS2_FILE_IO
#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/memory_cache_loader.h"
+#include "glk/tads/tads2/object.h"
namespace Glk {
namespace TADS {
@@ -35,6 +37,9 @@ namespace TADS2 {
/* forward declarations */
struct voccxdef;
+struct tokpdef;
+struct tokthdef;
+struct tokcxdef;
/* load-on-demand context (passed in by mcm in load callback) */
typedef struct fiolcxdef fiolcxdef;
@@ -65,20 +70,16 @@ void fiowrt(struct mcmcxdef *mctx, voccxdef *vctx,
#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);
+void fiord(mcmcxdef *mctx, voccxdef *vctx, tokcxdef *tctx, char *fname,
+ char *exename, fiolcxdef *setupctx, objnum *preinit, uint *flagp,
+ tokpdef *path, uchar **fmtsp, uint *fmtlp, uint *pcntptr, int flags,
+ 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);
+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
diff --git a/engines/glk/tads/tads2/lib.h b/engines/glk/tads/tads2/lib.h
index 605f072..095abfc 100644
--- a/engines/glk/tads/tads2/lib.h
+++ b/engines/glk/tads/tads2/lib.h
@@ -134,7 +134,7 @@ void varused();
* anything outside of the normal ASCII set as spaces.
*/
#define t_isspace(c) \
- (((unsigned char)(c)) <= 127 && isspace((unsigned char)(c)))
+ (((unsigned char)(c)) <= 127 && Common::isSpace((unsigned char)(c)))
/* round a size to worst-case alignment boundary */
diff --git a/engines/glk/tads/tads2/line_source_file.cpp b/engines/glk/tads/tads2/line_source_file.cpp
new file mode 100644
index 0000000..26536c0
--- /dev/null
+++ b/engines/glk/tads/tads2/line_source_file.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/line_source_file.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/line_source_file.h b/engines/glk/tads/tads2/line_source_file.h
new file mode 100644
index 0000000..a8b90d8
--- /dev/null
+++ b/engines/glk/tads/tads2/line_source_file.h
@@ -0,0 +1,170 @@
+/* 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_LINE_SOURCE_FILE
+#define GLK_TADS_TADS2_LINE_SOURCE_FILE
+
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/debug.h"
+#include "glk/tads/tads2/line_source.h"
+#include "glk/tads/tads2/object.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+struct tokpdef;
+
+/* maximum number of pages of debugging records we can keep */
+#define LINFPGMAX 128
+
+/*
+ * executable line information structure: this record relates one
+ * executable line to the object containing the p-code, and the offset
+ * in the object of the p-code for the start of the line
+ */
+struct linfinfo {
+ /*
+ * OPCLINE data (file seek position or line number, depending on how
+ * the game was compiled: -ds -> file seek offset, -ds2 -> line
+ * number)
+ */
+ ulong fpos;
+
+ /* object number */
+ objnum objn;
+
+ /* offset from start of code */
+ uint ofs;
+};
+
+/*
+ * file line source
+ */
+struct linfdef {
+ lindef linflin; /* superclass data */
+ osfildef *linffp; /* file pointer for this line source */
+ char linfbuf[100]; /* buffer for the line contents */
+ int linfbufnxt; /* offset in buffer of start of next line */
+ int linfnxtlen; /* length of data after linfbufnxt */
+ ulong linfnum; /* current line number */
+ ulong linfseek; /* seek position of current line */
+ mcmcxdef *linfmem; /* memory manager context */
+ mcmon linfpg[LINFPGMAX]; /* pages for debugging records */
+ ulong linfcrec; /* number of debugger records written so far */
+ char linfnam[1]; /* name of file being read */
+};
+
+/* initialize a file line source, opening the file for the line source */
+linfdef *linfini(mcmcxdef *mctx, errcxdef *errctx, char *filename,
+ int flen, tokpdef *path, int must_find_file,
+ int new_line_records);
+
+/* initialize a pre-allocated linfdef, skipping debugger page setup */
+void linfini2(mcmcxdef *mctx, linfdef *linf,
+ char *filename, int flen, osfildef *fp, int new_line_records);
+
+/* get next line from line source */
+int linfget(lindef *lin);
+
+/* generate printable rep of current position in source (for errors) */
+void linfppos(lindef *lin, char *buf, uint bufl);
+
+/* close line source */
+void linfcls(lindef *lin);
+
+/* generate source-line debug instruction operand */
+void linfglop(lindef *lin, uchar *buf);
+
+/* generate new-style source-line debug instructino operand */
+void linfglop2(lindef *lin, uchar *buf);
+
+/* save line source to binary (.gam) file */
+int linfwrt(lindef *lin, osfildef *fp);
+
+/* load a file-line-source from binary (.gam) file */
+int linfload(osfildef *fp, dbgcxdef *dbgctx, errcxdef *ec,
+ tokpdef *path);
+
+/* add a debugger line record for the current line */
+void linfcmp(lindef *lin, uchar *buf);
+
+/* find nearest line record to a file seek location */
+void linffind(lindef *lin, char *buf, objnum *objp, uint *ofsp);
+
+/* activate line source for debugging */
+void linfact(lindef *lin);
+
+/* disactivate line source */
+void linfdis(lindef *lin);
+
+/* get current seek position */
+void linftell(lindef *lin, uchar *pos);
+
+/* seek */
+void linfseek(lindef *lin, uchar *pos);
+
+/* read */
+int linfread(lindef *lin, uchar *buf, uint siz);
+
+/* add a signed delta to a seek positon */
+void linfpadd(lindef *lin, uchar *pos, long delta);
+
+/* query whether at top of file */
+int linfqtop(lindef *lin, uchar *pos);
+
+/* read one line at current seek position */
+int linfgets(lindef *lin, uchar *buf, uint bufsiz);
+
+/* get name of line source */
+void linfnam(lindef *lin, char *buf);
+
+/* get the current line number */
+ulong linflnum(lindef *lin);
+
+/* go to top or bottom */
+void linfgoto(lindef *lin, int where);
+
+/* return the current offset in the line source */
+long linfofs(lindef *lin);
+
+/* renumber an object */
+void linfren(lindef *lin, objnum oldnum, objnum newnum);
+
+/* delete an object */
+void linfdelnum(lindef *lin, objnum objn);
+
+/* copy line records to an array of linfinfo structures */
+void linf_copy_linerecs(linfdef *linf, linfinfo *info);
+
+/* debugging echo */
+#ifdef DEBUG
+# define LINFDEBUG(x) x
+#else /* DEBUG */
+# define LINFDEBUG(x)
+#endif /* DEBUG */
+
+} // 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 b391857..e1f5921 100644
--- a/engines/glk/tads/tads2/tads2.cpp
+++ b/engines/glk/tads/tads2/tads2.cpp
@@ -56,6 +56,7 @@ void TADS2::runGame() {
}
void TADS2::trdmain1(errcxdef *errctx) {
+#ifdef TODO
osfildef *swapfp = (osfildef *)0;
runcxdef runctx;
bifcxdef bifctx;
@@ -99,7 +100,7 @@ void TADS2::trdmain1(errcxdef *errctx) {
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 */
diff --git a/engines/glk/tads/tads2/tokenizer.cpp b/engines/glk/tads/tads2/tokenizer.cpp
index 59dceeb..96bd2f1 100644
--- a/engines/glk/tads/tads2/tokenizer.cpp
+++ b/engines/glk/tads/tads2/tokenizer.cpp
@@ -21,12 +21,1536 @@
*/
#include "glk/tads/tads2/tokenizer.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/memory_cache_heap.h"
+#include "glk/tads/tads2/os.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
-// TODO: Rest of tokenizer stuff
+
+/* special temporary buffers for <<expr>> macro expansion */
+static char tokmac1[] = ",say((";
+static char tokmac1s[] = "(say((";
+static char tokmac2[] = "),nil),\"";
+static char tokmac3[] = "),nil))";
+static char tokmac4[] = ")";
+
+/* forward definition of static functions */
+static int tokdfhsh(char *sym, int len);
+
+
+/* find a #define symbol */
+static tokdfdef *tok_find_define(tokcxdef *ctx, char *sym, int len)
+{
+ int hsh;
+ tokdfdef *df;
+
+ /* find the appropriate chain the hash table */
+ hsh = tokdfhsh(sym, len);
+
+ /* search the chain for this symbol */
+ for (df = ctx->tokcxdf[hsh] ; df ; df = df->nxt)
+ {
+ /* if this one matches, return it */
+ if (df->len == len && !memcmp(df->nm, sym, (size_t)len))
+ {
+ /* fix it up if it's the special __FILE__ or __LINE__ symbol */
+ if (len == 8)
+ {
+ if (!memcmp(sym, "__FILE__", (size_t)8))
+ {
+ size_t elen;
+
+ /*
+ * put in the opening single quote, since we want
+ * the expanded result to be a string
+ */
+ df->expan[0] = '\'';
+
+ /* get the name */
+ linnam(ctx->tokcxlin, df->expan+1);
+
+ /* get the length, and add the closing quote */
+ elen = strlen(df->expan);
+ df->expan[elen] = '\'';
+
+ /*
+ * set the length of the expansion, including the
+ * quotes (the first quote was measured in the
+ * length originally, but the second quote hasn't
+ * been counted yet, so add one to our original
+ * length)
+ */
+ df->explen = (int)elen + 1;
+
+ /* if the expansion is too long, it's an error */
+ if (df->explen >= OSFNMAX)
+ errsig(ctx->tokcxerr, ERR_LONG_FILE_MACRO);
+ }
+ else if (!memcmp(sym, "__LINE__", (size_t)8))
+ {
+ ulong l;
+
+ /* get the line number */
+ l = linlnum(ctx->tokcxlin);
+
+ /* convert it to a textual format for the expansion */
+ sprintf(df->expan, "%lu", l);
+
+ /* set the expanded value's length */
+ df->explen = strlen(df->expan);
+
+ /* make sure the expansion isn't too long */
+ if (df->explen >= 40)
+ errsig(ctx->tokcxerr, ERR_LONG_LINE_MACRO);
+ }
+ }
+
+ /* return it */
+ return df;
+ }
+ }
+
+ /* didn't find anything */
+ return 0;
+}
+
+/*
+ * Write preprocessor state to a file
+ */
+void tok_write_defines(tokcxdef *ctx, osfildef *fp, errcxdef *ec)
+{
+ int i;
+ tokdfdef **dfp;
+ tokdfdef *df;
+ char buf[4];
+
+ /* write each element of the hash chains */
+ for (i = TOKDFHSHSIZ, dfp = ctx->tokcxdf ; i ; ++dfp, --i)
+ {
+ /* write each entry in this hash chain */
+ for (df = *dfp ; df ; df = df->nxt)
+ {
+ /* write this entry */
+ oswp2(buf, df->len);
+ oswp2(buf + 2, df->explen);
+ if (osfwb(fp, buf, 4)
+ || osfwb(fp, df->nm, df->len)
+ || (df->explen != 0 && osfwb(fp, df->expan, df->explen)))
+ errsig(ec, ERR_WRTGAM);
+ }
+
+ /* write a zero-length entry to indicate the end of this chain */
+ oswp2(buf, 0);
+ if (osfwb(fp, buf, 4)) errsig(ec, ERR_WRTGAM);
+ }
+}
+
+/*
+ * Read preprocessor state from a file
+ */
+void tok_read_defines(tokcxdef *ctx, osfildef *fp, errcxdef *ec)
+{
+ int i;
+ tokdfdef **dfp;
+ tokdfdef *df;
+ char buf[4];
+
+ /* write each element of the hash chains */
+ for (i = TOKDFHSHSIZ, dfp = ctx->tokcxdf ; i ; ++dfp, --i)
+ {
+ /* read this hash chain */
+ for (;;)
+ {
+ /* read the next entry's header, and stop if this is the end */
+ if (osfrb(fp, buf, 4)) errsig(ec, ERR_RDGAM);
+ if (osrp2(buf) == 0) break;
+
+ /* set up a new symbol of the appropriate size */
+ df = (tokdfdef *)mchalo(ec,
+ (sizeof(tokdfdef) + osrp2(buf)
+ + osrp2(buf+2) - 1),
+ "tok_read_defines");
+ df->explen = osrp2(buf+2);
+ df->nm = df->expan + df->explen;
+ df->len = osrp2(buf);
+
+ /* read the rest of the symbol */
+ if (osfrb(fp, df->nm, df->len)
+ || (df->explen != 0 && osfrb(fp, df->expan, df->explen)))
+ errsig(ec, ERR_RDGAM);
+
+ /*
+ * If a symbol with this name already exists in the table,
+ * discard the new one -- the symbols defined by -D and the
+ * current set of built-in symbols takes precedence over the
+ * set loaded from the file.
+ */
+ if (tok_find_define(ctx, df->nm, df->len))
+ {
+ /* simply discard this symbol */
+ mchfre(df);
+ }
+ else
+ {
+ /* link it into this hash chain */
+ df->nxt = *dfp;
+ *dfp = df;
+ }
+ }
+ }
+}
+
+
+
+/* compute a #define symbol's hash value */
+static int tokdfhsh(char *sym, int len)
+{
+ uint hsh;
+
+ for (hsh = 0 ; len ; ++sym, --len)
+ hsh = (hsh + *sym) & TOKDFHSHMASK;
+ return hsh;
+}
+
+/* convert a #define symbol to lower case if folding case */
+static char *tok_casefold_defsym(tokcxdef *ctx, char *outbuf,
+ char *src, int len)
+{
+ if (ctx->tokcxflg & TOKCXCASEFOLD)
+ {
+ char *dst;
+ int rem;
+
+ /* make a lower-case copy of the symbol */
+ rem = (len > TOKNAMMAX ? TOKNAMMAX : len);
+ for (dst = outbuf ; rem > 0 ; ++dst, ++src, --rem)
+ *dst = (Common::isUpper((uchar)*src) ? Common::isLower((uchar)*src) : *src);
+
+ /* use the lower-case copy instead of the original */
+ return outbuf;
+ }
+ else
+ {
+ /* return the original unchanged */
+ return src;
+ }
+}
+
+/*
+ * convert a token to lower-case if we're folding case
+ */
+void tok_case_fold(tokcxdef *ctx, tokdef *tok)
+{
+ /* if we're in case-insensitive mode, convert the token to lower-case */
+ if (ctx->tokcxflg & TOKCXCASEFOLD)
+ {
+ char *p;
+ int len;
+
+ /* convert each character in the token to lower-case */
+ for (p = tok->toknam, len = tok->toklen ; len != 0 ; ++p, --len)
+ {
+ /* if this character is upper-case, convert it to lower-case */
+ if (Common::isUpper((uchar)*p))
+ *p = Common::isLower((uchar)*p);
+ }
+ }
+}
+
+/* add a symbol to the #define symbol table, folding case if necessary */
+void tok_add_define_cvtcase(tokcxdef *ctx, char *sym, int len,
+ char *expan, int explen)
+{
+ char mysym[TOKNAMMAX];
+
+ /* convert to lower-case if necessary */
+ sym = tok_casefold_defsym(ctx, mysym, sym, len);
+
+ /* add the symbol */
+ tok_add_define(ctx, sym, len, expan, explen);
+}
+
+/* add a symbol to the #define symbol table */
+void tok_add_define(tokcxdef *ctx, char *sym, int len,
+ char *expan, int explen)
+{
+ int hsh;
+ tokdfdef *df;
+
+ /* if it's already defined, ignore it */
+ if (tok_find_define(ctx, sym, len))
+ return;
+
+ /* find the appropriate entry in the hash table */
+ hsh = tokdfhsh(sym, len);
+
+ /* allocate space for the symbol */
+ df = (tokdfdef *)mchalo(ctx->tokcxerr,
+ (sizeof(tokdfdef) + len + explen - 1),
+ "tok_add_define");
+
+ /* set up the new symbol */
+ df->nm = df->expan + explen;
+ df->len = len;
+ df->explen = explen;
+ memcpy(df->expan, expan, explen);
+ memcpy(df->nm, sym, len);
+
+ /* link it into the hash chain */
+ df->nxt = ctx->tokcxdf[hsh];
+ ctx->tokcxdf[hsh] = df;
+}
+
+/* add a #define symbol with a numeric value */
+void tok_add_define_num_cvtcase(tokcxdef *ctx, char *sym, int len, int num)
+{
+ char buf[20];
+
+ /* convert the value to a string */
+ sprintf(buf, "%d", num);
+
+ /* add the text value */
+ tok_add_define_cvtcase(ctx, sym, len, buf, strlen(buf));
+}
+
+/* undefine a #define symbol */
+void tok_del_define(tokcxdef *ctx, char *sym, int len)
+{
+ int hsh;
+ tokdfdef *df;
+ tokdfdef *prv;
+
+ /* find the appropriate chain the hash table */
+ hsh = tokdfhsh(sym, len);
+
+ /* search the chain for this symbol */
+ for (prv = 0, df = ctx->tokcxdf[hsh] ; df ; prv = df, df = df->nxt)
+ {
+ /* if this one matches, delete it */
+ if (df->len == len && !memcmp(df->nm, sym, (size_t)len))
+ {
+ /* unlink it from the chain */
+ if (prv)
+ prv->nxt = df->nxt;
+ else
+ ctx->tokcxdf[hsh] = df->nxt;
+
+ /* delete this symbol, and we're done */
+ mchfre(df);
+ break;
+ }
+ }
+}
+
+/* scan a #define symbol to see how long it is */
+static int tok_scan_defsym(tokcxdef *ctx, char *p, int len)
+{
+ int symlen;
+
+ /* make sure it's a valid symbol */
+ if (!(Common::isAlpha((uchar)*p) || *p == '_' || *p == '$'))
+ {
+ errlog(ctx->tokcxerr, ERR_REQSYM);
+ return 0;
+ }
+
+ /* count characters as long as we have valid symbol characters */
+ for (symlen = 0 ; len && TOKISSYM(*p) ; ++p, --len, ++symlen) ;
+ return symlen;
+}
+
+/* process a #define */
+static void tokdefine(tokcxdef *ctx, char *p, int len)
+{
+ char *sym;
+ int symlen;
+ char *expan;
+ char mysym[TOKNAMMAX];
+
+ /* get the symbol */
+ sym = p;
+ if (!(symlen = tok_scan_defsym(ctx, p, len)))
+ return;
+
+ /* if it's already in the table, log an error */
+ if (tok_find_define(ctx, sym, symlen))
+ {
+ errlog(ctx->tokcxerr, ERR_DEFREDEF);
+ return;
+ }
+
+ /* skip whitespace following the symbol */
+ expan = sym + symlen;
+ len -= symlen;
+ while (len && t_isspace(*expan)) --len, ++expan;
+
+ /* if we're folding case, convert the symbol to lower case */
+ sym = tok_casefold_defsym(ctx, mysym, sym, symlen);
+
+ /* define the symbol */
+ tok_add_define(ctx, sym, symlen, expan, len);
+}
+
+/*
+ * Update the #if status for the current nesting. Any enclosing
+ * negative #if will override everything inside, so we need to look
+ * through the nesting from the outside in until we either determine
+ * that everything is affirmative or we find a negative anywhere in the
+ * nesting.
+ */
+static void tok_update_if_stat(tokcxdef *ctx)
+{
+ int i;
+
+ /* look through nesting from the outermost level */
+ for (i = 0 ; i < ctx->tokcxifcnt ; ++i)
+ {
+ /* assume this level will apply to everything inside */
+ ctx->tokcxifcur = ctx->tokcxif[i];
+
+ /* if this level is off, everything inside is off */
+ switch (ctx->tokcxif[i])
+ {
+ case TOKIF_IF_NO:
+ case TOKIF_ELSE_NO:
+ /*
+ * this level is off, hence everything inside is off -- stop
+ * here with the current (negative) determination
+ */
+ return;
+
+ default:
+ /* so far we're in the "on" section, so keep looking */
+ break;
+ }
+ }
+}
+
+/* process an #ifdef or a #ifndef */
+static void tok_ifdef_ifndef(tokcxdef *ctx, char *p, int len, int is_ifdef)
+{
+ int symlen;
+ char *sym;
+ int stat;
+ int found;
+ char mysym[TOKNAMMAX];
+
+ /* get the symbol */
+ sym = p;
+ if (!(symlen = tok_scan_defsym(ctx, p, len)))
+ return;
+
+ /* if we're folding case, convert the symbol to lower case */
+ sym = tok_casefold_defsym(ctx, mysym, sym, symlen);
+
+ /* see if we can find it in the table, and set the status accordingly */
+ found = (tok_find_define(ctx, sym, symlen) != 0);
+
+ /* invert the test if this is an ifndef */
+ if (!is_ifdef) found = !found;
+
+ /* set the #if status accordingly */
+ if (found)
+ stat = TOKIF_IF_YES;
+ else
+ stat = TOKIF_IF_NO;
+ ctx->tokcxif[ctx->tokcxifcnt] = stat;
+
+ /* allocate a new #if level (making sure we have room) */
+ if (ctx->tokcxifcnt >= TOKIFNEST)
+ {
+ errlog(ctx->tokcxerr, ERR_MANYPIF);
+ return;
+ }
+ ctx->tokcxifcnt++;
+
+ /* update the current status */
+ tok_update_if_stat(ctx);
+}
+
+/* process a #error */
+static void tok_p_error(tokcxdef *ctx, char *p, int len)
+{
+ errlog1(ctx->tokcxerr, ERR_P_ERROR,
+ ERRTSTR, errstr(ctx->tokcxerr, p, len));
+}
+
+/* process a #ifdef */
+static void tokifdef(tokcxdef *ctx, char *p, int len)
+{
+ tok_ifdef_ifndef(ctx, p, len, TRUE);
+}
+
+/* process a #ifndef */
+static void tokifndef(tokcxdef *ctx, char *p, int len)
+{
+ tok_ifdef_ifndef(ctx, p, len, FALSE);
+}
+
+/* process a #if */
+static void tokif(tokcxdef *ctx, char *p, int len)
+{
+ errsig(ctx->tokcxerr, ERR_PIF_NA);
+}
+
+/* process a #elif */
+static void tokelif(tokcxdef *ctx, char *p, int len)
+{
+ errsig(ctx->tokcxerr, ERR_PELIF_NA);
+}
+
+/* process a #else */
+static void tokelse(tokcxdef *ctx, char *p, int len)
+{
+ int cnt;
+
+ /* if we're not expecting #else, it's an error */
+ cnt = ctx->tokcxifcnt;
+ if (cnt == 0 || ctx->tokcxif[cnt-1] == TOKIF_ELSE_YES
+ || ctx->tokcxif[cnt-1] == TOKIF_ELSE_NO)
+ {
+ errlog(ctx->tokcxerr, ERR_BADPELSE);
+ return;
+ }
+
+ /* switch to the appropriate #else state (opposite the #if state) */
+ if (ctx->tokcxif[cnt-1] == TOKIF_IF_YES)
+ ctx->tokcxif[cnt-1] = TOKIF_ELSE_NO;
+ else
+ ctx->tokcxif[cnt-1] = TOKIF_ELSE_YES;
+
+ /* update the current status */
+ tok_update_if_stat(ctx);
+}
+
+/* process a #endif */
+static void tokendif(tokcxdef *ctx, char *p, int len)
+{
+ /* if we're not expecting #endif, it's an error */
+ if (ctx->tokcxifcnt == 0)
+ {
+ errlog(ctx->tokcxerr, ERR_BADENDIF);
+ return;
+ }
+
+ /* remove the #if level */
+ ctx->tokcxifcnt--;
+
+ /* update the current status */
+ tok_update_if_stat(ctx);
+}
+
+/* process a #undef */
+static void tokundef(tokcxdef *ctx, char *p, int len)
+{
+ char *sym;
+ int symlen;
+ char mysym[TOKNAMMAX];
+
+ /* get the symbol */
+ sym = p;
+ if (!(symlen = tok_scan_defsym(ctx, p, len)))
+ return;
+
+ /* if we're folding case, convert the symbol to lower case */
+ sym = tok_casefold_defsym(ctx, mysym, sym, symlen);
+
+ /* if it's not defined, log a warning */
+ if (!tok_find_define(ctx, sym, symlen))
+ {
+ errlog(ctx->tokcxerr, ERR_PUNDEF);
+ return;
+ }
+
+ /* undefine the symbol */
+ tok_del_define(ctx, sym, symlen);
+}
+
+/* process a #pragma directive */
+static void tokpragma(tokcxdef *ctx, char *p, int len)
+{
+ /* ignore empty pragmas */
+ if (len == 0)
+ {
+ errlog(ctx->tokcxerr, ERR_PRAGMA);
+ return;
+ }
+
+ /* see what we have */
+ if (len > 1
+ && (*p == 'c' || *p == 'C')
+ && (*(p+1) == '+' || *(p+1) == '-' || t_isspace(*(p+1))))
+ {
+ /* skip spaces after the 'C', if any */
+ for (++p, --len ; len && t_isspace(*p) ; ++p, --len) ;
+
+ /* look for the + or - flag */
+ if (len && *p == '+')
+ ctx->tokcxflg |= TOKCXFCMODE;
+ else if (len && *p == '-')
+ ctx->tokcxflg &= ~TOKCXFCMODE;
+ else
+ {
+ errlog(ctx->tokcxerr, ERR_PRAGMA);
+ return;
+ }
+ }
+ else
+ {
+ errlog(ctx->tokcxerr, ERR_PRAGMA);
+ }
+}
+
+/* process a #include directive */
+static void tokinclude(tokcxdef *ctx, char *p, int len)
+{
+ linfdef *child;
+ tokpdef *path;
+ char *fname;
+ int match;
+ int flen;
+ linfdef *lin;
+ char *q;
+ size_t flen2;
+
+ /* find the filename portion */
+ fname = p + 1; /* remember start of filename */
+ path = ctx->tokcxinc; /* start with first path entry */
+
+ if (!len)
+ {
+ errlog(ctx->tokcxerr, ERR_INCNOFN);
+ return;
+ }
+
+ switch(*p)
+ {
+ case '<':
+ match = '>';
+ if (path && path->tokpnxt) path = path->tokpnxt; /* skip 1st path */
+ goto find_matching_delim;
+
+ case '"':
+ match = '"';
+
+ find_matching_delim:
+ for (++p, --len ; len && *p != match ; --len, ++p) ;
+ if (len == 0 || *p != match) errlog(ctx->tokcxerr, ERR_INCMTCH);
+ break;
+
+ default:
+ errlog(ctx->tokcxerr, ERR_INCSYN);
+ return;
+ }
+
+ flen = p - fname; /* compute length of filename */
+ for (q = p, flen2 = 0 ;
+ q > fname && *(q-1) != OSPATHCHAR && !strchr(OSPATHALT, *(q-1)) ;
+ --q, ++flen2) ;
+
+ /* check to see if this file has already been included */
+ for (lin = ctx->tokcxhdr ; lin ; lin = (linfdef *)lin->linflin.linnxt)
+ {
+ char *p2 = lin->linfnam;
+
+ p2 += strlen(p2);
+
+ while (p2 > lin->linfnam && *(p2-1) != OSPATHCHAR
+ && !strchr(OSPATHALT, *(p2-1)))
+ --p2;
+ if (strlen(p2) == flen2
+ && !memicmp(p2, q, flen2))
+ {
+ errlog1(ctx->tokcxerr, ERR_INCRPT, ERRTSTR,
+ errstr(ctx->tokcxerr, fname, flen));
+ return;
+ }
+ }
+
+ /* initialize the line source */
+ child = linfini(ctx->tokcxmem, ctx->tokcxerr, fname, flen, path, TRUE,
+ (ctx->tokcxflg & TOKCXFLIN2) != 0);
+
+ /* if not found, signal an error */
+ if (!child) errsig1(ctx->tokcxerr, ERR_INCSEAR,
+ ERRTSTR, errstr(ctx->tokcxerr, fname, flen));
+
+ /* link into tokenizer list of line records */
+ child->linflin.linnxt = (lindef *)ctx->tokcxhdr;
+ ctx->tokcxhdr = child;
+
+ /* if we're tracking sources for debugging, add into the chain */
+ if (ctx->tokcxdbg)
+ {
+ ctx->tokcxdbg->dbgcxlin = &child->linflin;
+ child->linflin.linid = ctx->tokcxdbg->dbgcxfid++;
+ }
+
+ /* remember my C-mode setting */
+ if (ctx->tokcxflg & TOKCXFCMODE)
+ ctx->tokcxlin->linflg |= LINFCMODE;
+ else
+ ctx->tokcxlin->linflg &= ~LINFCMODE;
+
+ child->linflin.linpar = ctx->tokcxlin; /* remember parent line source */
+ ctx->tokcxlin = &child->linflin; /* make the child the current source */
+}
+
+/* get a new line from line source, processing '#' directives */
+static int tokgetlin(tokcxdef *ctx, int dopound)
+{
+ for (;;)
+ {
+ if (linget(ctx->tokcxlin))
+ {
+ /* at eof in current source; resume parent if there is one */
+ if (ctx->tokcxlin->linpar)
+ {
+ lindef *parent;
+
+ parent = ctx->tokcxlin->linpar; /* remember parent */
+ lincls(ctx->tokcxlin); /* close included file */
+ if (!ctx->tokcxdbg) /* if no debug context... */
+ mchfre(ctx->tokcxlin); /* free line source */
+ ctx->tokcxlin = parent; /* reset to parent line source */
+ if (parent->linflg & LINFCMODE)
+ ctx->tokcxflg |= TOKCXFCMODE;
+ else
+ ctx->tokcxflg &= ~TOKCXFCMODE;
+ continue; /* back for another attempt */
+ }
+ else
+ {
+ /* check for outstanding #if/#ifdef */
+ if (ctx->tokcxifcnt)
+ errlog(ctx->tokcxerr, ERR_NOENDIF);
+
+ /* return end-of-file indication */
+ return TRUE;
+ }
+ }
+
+ /* if this is a multi-segment line, copy it into our own buffer */
+ if (ctx->tokcxlin->linflg & LINFMORE)
+ {
+ char *p;
+ uint rem;
+ int done;
+
+ if (!ctx->tokcxbuf)
+ {
+ /* allocate 1k as a default buffer */
+ ctx->tokcxbuf = (char *)mchalo(ctx->tokcxerr, 1024,
+ "tok");
+ ctx->tokcxbsz = 1024;
+ }
+ ctx->tokcxlen = 0;
+
+ for (done = FALSE, p = ctx->tokcxbuf, rem = ctx->tokcxbsz ;
+ !done ; )
+ {
+ size_t len = ctx->tokcxlin->linlen;
+
+ /* add the current segment's length into line length */
+ ctx->tokcxlen += len;
+
+ /* we're done after this piece if the last fetch was all */
+ done = !(ctx->tokcxlin->linflg & LINFMORE);
+ if (len + 1 > rem)
+ {
+ char *newp;
+
+ /* increase the size of the buffer */
+ if (ctx->tokcxbsz > (unsigned)0x8000)
+ errsig(ctx->tokcxerr, ERR_LONGLIN);
+ rem += 4096;
+ ctx->tokcxbsz += 4096;
+
+ /* allocate a new buffer and copy line into it */
+ newp = (char *)mchalo(ctx->tokcxerr, ctx->tokcxbsz, "tok");
+ memcpy(newp, ctx->tokcxbuf, (size_t)(p - ctx->tokcxbuf));
+
+ /* free the original buffer, and use the new one */
+ p = (p - ctx->tokcxbuf) + newp;
+ mchfre(ctx->tokcxbuf);
+ ctx->tokcxbuf = newp;
+ }
+
+ /* add the line to the buffer */
+ memcpy(p, ctx->tokcxlin->linbuf, len);
+ p += len;
+ rem -= len;
+
+ /* get the next piece of the line if there is one */
+ if (!done)
+ {
+ if (linget(ctx->tokcxlin)) break;
+ }
+ }
+
+ /* null-terminate the buffer, and use it for input */
+ *p = '\0';
+ ctx->tokcxptr = ctx->tokcxbuf;
+ }
+ else
+ {
+ ctx->tokcxptr = ctx->tokcxlin->linbuf;
+ ctx->tokcxlen = ctx->tokcxlin->linlen;
+ }
+
+ /* check for preprocessor directives */
+ if (dopound && ctx->tokcxlen != 0 && ctx->tokcxptr[0] == '#'
+ && !(ctx->tokcxlin->linflg & LINFNOINC))
+ {
+ char *p;
+ int len;
+ static struct
+ {
+ char *nm;
+ int len;
+ int ok_in_if;
+ void (*fn)(tokcxdef *, char *, int);
+ }
+ *dirp, dir[] =
+ {
+ { "include", 7, FALSE, tokinclude },
+ { "pragma", 6, FALSE, tokpragma },
+ { "define", 6, FALSE, tokdefine },
+ { "ifdef", 5, TRUE, tokifdef },
+ { "ifndef", 6, TRUE, tokifndef },
+ { "if", 2, TRUE, tokif },
+ { "else", 4, TRUE, tokelse },
+ { "elif", 4, TRUE, tokelif },
+ { "endif", 5, TRUE, tokendif },
+ { "undef", 5, FALSE, tokundef },
+ { "error", 5, FALSE, tok_p_error }
+ };
+ int i;
+
+ /* scan off spaces between '#' and directive */
+ for (len = ctx->tokcxlen - 1, p = &ctx->tokcxptr[1] ;
+ len && t_isspace(*p) ; --len, ++p) ;
+
+ /* find and process the directive */
+ for (dirp = dir, i = sizeof(dir)/sizeof(dir[0]) ; i ; --i, ++dirp)
+ {
+ /* compare this directive; if it wins, call its function */
+ if (len >= dirp->len && !memcmp(p, dirp->nm, (size_t)dirp->len)
+ && (len == dirp->len || t_isspace(*(p + dirp->len))))
+ {
+ int cnt;
+ int stat;
+
+ /*
+ * if we're not in a #if's false part, or if the
+ * directive is processed even in #if false parts,
+ * process the line, otherwise skip it
+ */
+ cnt = ctx->tokcxifcnt;
+ if (dirp->ok_in_if || cnt == 0
+ || ((stat = ctx->tokcxifcur) == TOKIF_IF_YES
+ || stat == TOKIF_ELSE_YES))
+ {
+ /* skip whitespace following the directive */
+ for (p += dirp->len, len -= dirp->len ;
+ len && t_isspace(*p) ;
+ --len, ++p) ;
+
+ /* invoke the function to process this directive */
+ (*dirp->fn)(ctx, p, len);
+ }
+
+ /* there's no need to look at more directives */
+ break;
+ }
+ }
+
+ /* if we didn't find anything, flag the error */
+ if (i == 0)
+ errlog(ctx->tokcxerr, ERR_PRPDIR);
+
+ /* ignore this line */
+ continue;
+ }
+ else
+ {
+ /*
+ * Check the #if level. If we're in an #if, and we're to
+ * ignore lines (because of a false condition or an #else
+ * part for a true condition), skip this line.
+ */
+ if (ctx->tokcxifcnt != 0)
+ {
+ switch(ctx->tokcxifcur)
+ {
+ case TOKIF_IF_NO:
+ case TOKIF_ELSE_NO:
+ /* ignore this line */
+ continue;
+
+ default:
+ /* we're in a true part - keep the line */
+ break;
+ }
+ }
+
+ ctx->tokcxlin->linflg &= ~LINFDBG; /* no debug record yet */
+ return(FALSE); /* return the line we found */
+ }
+ }
+}
+
+/* get the next token, removing it from the input stream */
+int toknext(tokcxdef *ctx)
+{
+ char *p;
+ tokdef *tok = &ctx->tokcxcur;
+ int len;
+
+ /*
+ * Check for the special case that we pushed an open paren prior to
+ * a string containing an embedded expression. If this is the case,
+ * immediately return the string we previously parsed.
+ */
+ if ((ctx->tokcxflg & TOKCXF_EMBED_PAREN_PRE) != 0)
+ {
+ /*
+ * convert the token to a string - note that the offset
+ * information for the string is already in the current token
+ * structure, since we set everything up for it on the previous
+ * call where we actually parsed the beginning of the string
+ */
+ tok->toktyp = TOKTDSTRING;
+
+ /* clear the special flag - we've now consumed the pushed string */
+ ctx->tokcxflg &= ~TOKCXF_EMBED_PAREN_PRE;
+
+ /* immediately return the string */
+ return tok->toktyp;
+ }
+
+ /* set up at the current scanning position */
+ p = ctx->tokcxptr;
+ len = ctx->tokcxlen;
+
+ /* scan off whitespace and comments until we find something */
+ do
+ {
+ skipblanks:
+ /* if there's nothing on this line, get the next one */
+ if (len == 0)
+ {
+ /* if we're in a macro expansion, continue after it */
+ if (ctx->tokcxmlvl)
+ {
+ ctx->tokcxmlvl--;
+ p = ctx->tokcxmsav[ctx->tokcxmlvl];
+ len = ctx->tokcxmsvl[ctx->tokcxmlvl];
+ }
+ else
+ {
+ if (tokgetlin(ctx, TRUE))
+ {
+ tok->toktyp = TOKTEOF;
+ goto done;
+ }
+ p = ctx->tokcxptr;
+ len = ctx->tokcxlen;
+ }
+ }
+ while (len && t_isspace(*p)) ++p, --len; /* scan off whitespace */
+
+ /* check for comments, and remove if present */
+ if (len >= 2 && *p == '/' && *(p+1) == '/')
+ len = 0;
+ else if (len >= 2 && *p == '/' && *(p+1) == '*')
+ {
+ while (len < 2 || *p != '*' || *(p+1) != '/')
+ {
+ if (len != 0)
+ ++p, --len;
+
+ if (len == 0)
+ {
+ if (ctx->tokcxmlvl != 0)
+ {
+ ctx->tokcxmlvl--;
+ p = ctx->tokcxmsav[ctx->tokcxmlvl];
+ len = ctx->tokcxmsvl[ctx->tokcxmlvl];
+ }
+ else
+ {
+ if (tokgetlin(ctx, FALSE))
+ {
+ ctx->tokcxptr = p;
+ tok->toktyp = TOKTEOF;
+ goto done;
+ }
+ p = ctx->tokcxptr;
+ len = ctx->tokcxlen;
+ }
+ }
+ }
+ p += 2;
+ len -= 2;
+ goto skipblanks;
+ }
+ } while (len == 0);
+
+nexttoken:
+ if (Common::isAlpha((uchar)*p) || *p == '_' || *p == '$')
+ {
+ int l;
+ int hash;
+ char *q;
+ toktdef *tab;
+ int found = FALSE;
+ uchar thischar;
+ tokdfdef *df;
+
+ for (hash = 0, l = 0, q = tok->toknam ;
+ len != 0 && TOKISSYM(*p) && l < TOKNAMMAX ;
+ (thischar = ((Common::isUpper((uchar)*p)
+ && (ctx->tokcxflg & TOKCXCASEFOLD))
+ ? Common::isLower((uchar)*p) : *p)),
+ (hash = ((hash + thischar) & (TOKHASHSIZE - 1))),
+ (*q++ = thischar), ++p, --len, ++l) ;
+ *q = '\0';
+ if (len != 0 && TOKISSYM(*p))
+ {
+ while (len != 0 && TOKISSYM(*p)) ++p, --len;
+ errlog1(ctx->tokcxerr, ERR_TRUNC, ERRTSTR,
+ errstr(ctx->tokcxerr, tok->toknam, tok->toklen));
+ }
+ tok->toklen = l;
+ tok->tokhash = hash;
+
+ /*
+ * check for the special defined() preprocessor operator
+ */
+ if (l == 9 && !memcmp(tok->toknam,
+ ((ctx->tokcxflg & TOKCXCASEFOLD)
+ ? "__defined" : "__DEFINED"),
+ (size_t)9)
+ && len > 2 && *p == '(' && TOKISSYM(*(p+1))
+ && !Common::isDigit((uchar)*(p+1)))
+ {
+ int symlen;
+ char mysym[TOKNAMMAX];
+
+ /* find the matching ')', allowing only symbolic characters */
+ ++p, --len;
+ for (symlen = 0, q = p ; len && *p != ')' && TOKISSYM(*p) ;
+ ++p, --len, ++symlen) ;
+
+ /* make sure we found the closing paren */
+ if (!len || *p != ')')
+ errsig(ctx->tokcxerr, ERR_BADISDEF);
+ ++p, --len;
+
+ /* if we're folding case, convert the symbol to lower case */
+ q = tok_casefold_defsym(ctx, mysym, q, symlen);
+
+ /* check to see if it's defined */
+ tok->toktyp = TOKTNUMBER;
+ tok->tokval = (tok_find_define(ctx, q, symlen) != 0);
+ goto done;
+ }
+
+ /* substitute the preprocessor #define, if any */
+ if ((df = tok_find_define(ctx, tok->toknam, l)) != 0)
+ {
+ /* save the current parsing position */
+ if (ctx->tokcxmlvl >= TOKMACNEST)
+ errsig(ctx->tokcxerr, ERR_MACNEST);
+ ctx->tokcxmsav[ctx->tokcxmlvl] = p;
+ ctx->tokcxmsvl[ctx->tokcxmlvl] = len;
+ ctx->tokcxmlvl++;
+
+ /* point to the token's expansion and keep going */
+ p = df->expan;
+ len = df->explen;
+ goto nexttoken;
+ }
+
+ /* look up in symbol table(s), if any */
+ for (tab = ctx->tokcxstab ; tab ; tab = tab->toktnxt)
+ {
+ if ((found = (*tab->toktfsea)(tab, tok->toknam, l, hash,
+ &tok->toksym)) != 0)
+ break;
+ }
+
+ if (found && tok->toksym.tokstyp == TOKSTKW)
+ tok->toktyp = tok->toksym.toksval;
+ else
+ {
+ tok->toktyp = TOKTSYMBOL;
+ if (!found) tok->toksym.tokstyp = TOKSTUNK;
+ }
+ goto done;
+ }
+ else if (Common::isDigit((uchar)*p))
+ {
+ long acc = 0;
+
+ /* check for octal/hex */
+ if (*p == '0')
+ {
+ ++p, --len;
+ if (len && (*p == 'x' || *p == 'X'))
+ {
+ /* hex */
+ ++p, --len;
+ while (len && TOKISHEX(*p))
+ {
+ acc = (acc << 4) + TOKHEX2INT(*p);
+ ++p, --len;
+ }
+ }
+ else
+ {
+ /* octal */
+ while (len && TOKISOCT(*p))
+ {
+ acc = (acc << 3) + TOKOCT2INT(*p);
+ ++p, --len;
+ }
+ }
+ }
+ else
+ {
+ /* decimal */
+ while (len && Common::isDigit((uchar)*p))
+ {
+ acc = (acc << 1) + (acc << 3) + TOKDEC2INT(*p);
+ ++p, --len;
+ }
+ }
+ tok->tokval = acc;
+ tok->toktyp = TOKTNUMBER;
+ goto done;
+ }
+ else if (*p == '"' || *p == '\'')
+ {
+ char delim; /* closing delimiter we're looking for */
+ char *strstart; /* pointer to start of string */
+ int warned;
+
+ delim = *p;
+ --len;
+ strstart = ++p;
+
+ if (delim == '"' && len >= 2 && *p == '<' && *(p+1) == '<')
+ {
+ /* save the current parsing position */
+ if (ctx->tokcxmlvl >= TOKMACNEST)
+ errsig(ctx->tokcxerr, ERR_MACNEST);
+ ctx->tokcxmsav[ctx->tokcxmlvl] = p + 2;
+ ctx->tokcxmsvl[ctx->tokcxmlvl] = len - 2;
+ ctx->tokcxmlvl++;
+
+ /*
+ * read from the special "<<" expansion string - use the
+ * version for a "<<" at the very beginning of the string
+ */
+ p = tokmac1s;
+ len = strlen(p);
+ ctx->tokcxflg |= TOKCXFINMAC;
+ goto nexttoken;
+ }
+ tok->toktyp = (delim == '"' ? TOKTDSTRING : TOKTSSTRING);
+
+ tok->tokofs = (*ctx->tokcxsst)(ctx->tokcxscx); /* start the string */
+ for (warned = FALSE ;; )
+ {
+ if (len >= 2 && *p == '\\')
+ {
+ if (*(p+1) == '"' || *(p+1) == '\'')
+ {
+ (*ctx->tokcxsad)(ctx->tokcxscx, strstart,
+ (ushort)(p - strstart));
+ strstart = p + 1;
+ }
+ p += 2;
+ len -= 2;
+ }
+ else if (len == 0 || *p == delim ||
+ (delim == '"' && len >= 2 && *p == '<' && *(p+1) == '<'
+ && !(ctx->tokcxflg & TOKCXFINMAC)))
+ {
+ (*ctx->tokcxsad)(ctx->tokcxscx, strstart,
+ (ushort)(p - strstart));
+ if (len == 0)
+ {
+ if (ctx->tokcxmlvl)
+ {
+ ctx->tokcxmlvl--;
+ p = ctx->tokcxmsav[ctx->tokcxmlvl];
+ len = ctx->tokcxmsvl[ctx->tokcxmlvl];
+ }
+ else
+ (*ctx->tokcxsad)(ctx->tokcxscx, " ", (ushort)1);
+
+ while (len == 0)
+ {
+ if (tokgetlin(ctx, FALSE))
+ errsig(ctx->tokcxerr, ERR_STREOF);
+ p = ctx->tokcxptr;
+ len = ctx->tokcxlen;
+
+ /* warn if it looks like the end of an object */
+ if (!warned && len && (*p == ';' || *p == '}'))
+ {
+ errlog(ctx->tokcxerr, ERR_STREND);
+ warned = TRUE; /* warn only once per string */
+ }
+
+ /* scan past whitespace at start of line */
+ while (len && t_isspace(*p)) ++p, --len;
+ }
+ strstart = p;
+ }
+ else break;
+ }
+ else
+ ++p, --len;
+ }
+
+ /* end the string */
+ (*ctx->tokcxsend)(ctx->tokcxscx);
+
+ /* check to see how it ended */
+ if (len != 0 && *p == delim)
+ {
+ /*
+ * We ended with the matching delimiter. Move past the
+ * closing delimiter.
+ */
+ ++p;
+ --len;
+
+ /*
+ * If we have a pending close paren we need to put in
+ * because of an embedded expression that occurred earlier
+ * in the string, parse the macro to provide the paren.
+ */
+ if ((ctx->tokcxflg & TOKCXF_EMBED_PAREN_AFT) != 0
+ && !(ctx->tokcxflg & TOKCXFINMAC))
+ {
+ /* clear the flag */
+ ctx->tokcxflg &= ~TOKCXF_EMBED_PAREN_AFT;
+
+ /* push the current parsing position */
+ if (ctx->tokcxmlvl >= TOKMACNEST)
+ errsig(ctx->tokcxerr, ERR_MACNEST);
+ ctx->tokcxmsav[ctx->tokcxmlvl] = p;
+ ctx->tokcxmsvl[ctx->tokcxmlvl] = len;
+ ctx->tokcxmlvl++;
+
+ /* parse the macro */
+ p = tokmac4;
+ len = strlen(p);
+ }
+ }
+ else if (len != 0 && *p == '<')
+ {
+ /* save the current parsing position */
+ if (ctx->tokcxmlvl >= TOKMACNEST)
+ errsig(ctx->tokcxerr, ERR_MACNEST);
+ ctx->tokcxmsav[ctx->tokcxmlvl] = p + 2;
+ ctx->tokcxmsvl[ctx->tokcxmlvl] = len - 2;
+ ctx->tokcxmlvl++;
+
+ /* read from the "<<" expansion */
+ p = tokmac1;
+ len = strlen(p);
+ ctx->tokcxflg |= TOKCXFINMAC;
+
+ /*
+ * Set the special push-a-paren flag: we'll return an open
+ * paren now, so that we have an open paren before the
+ * string, and then on the next call to toknext() we'll
+ * immediately return the string we've already parsed here.
+ * This will ensure that everything in the string is
+ * properly grouped together as a single indivisible
+ * expression.
+ *
+ * Note that we only need to do this for the first embedded
+ * expression in a string. Once we have a close paren
+ * pending, we don't need more open parens.
+ */
+ if (!(ctx->tokcxflg & TOKCXF_EMBED_PAREN_AFT))
+ {
+ ctx->tokcxflg |= TOKCXF_EMBED_PAREN_PRE;
+ tok->toktyp = TOKTLPAR;
+ }
+ }
+ goto done;
+ }
+ else if (len >= 2 && *p == '>' && *(p+1) == '>'
+ && (ctx->tokcxflg & TOKCXFINMAC) != 0)
+ {
+ /* skip the ">>" */
+ ctx->tokcxflg &= ~TOKCXFINMAC;
+ p += 2;
+ len -= 2;
+
+ /* save the current parsing position */
+ if (ctx->tokcxmlvl >= TOKMACNEST)
+ errsig(ctx->tokcxerr, ERR_MACNEST);
+ ctx->tokcxmsav[ctx->tokcxmlvl] = p;
+ ctx->tokcxmsvl[ctx->tokcxmlvl] = len;
+ ctx->tokcxmlvl++;
+
+ if (*p == '"')
+ {
+ ++(ctx->tokcxmsav[ctx->tokcxmlvl - 1]);
+ --(ctx->tokcxmsvl[ctx->tokcxmlvl - 1]);
+ p = tokmac3;
+
+ /*
+ * we won't need an extra closing paren now, since tokmac3
+ * provides it
+ */
+ ctx->tokcxflg &= ~TOKCXF_EMBED_PAREN_AFT;
+ }
+ else
+ {
+ /*
+ * The string is continuing. Set a flag to note that we
+ * need to provide a close paren after the end of the
+ * string, and parse the glue (tokmac2) that goes between
+ * the expression and the resumption of the string.
+ */
+ ctx->tokcxflg |= TOKCXF_EMBED_PAREN_AFT;
+ p = tokmac2;
+ }
+
+ len = strlen(p);
+ goto nexttoken;
+ }
+ else
+ {
+ tokscdef *sc;
+
+ for (sc = ctx->tokcxsc[ctx->tokcxinx[(uchar)*p]] ; sc ;
+ sc = sc->tokscnxt)
+ {
+ if (toksceq(sc->tokscstr, p, sc->toksclen, len))
+ {
+ tok->toktyp = sc->toksctyp;
+ p += sc->toksclen;
+ len -= sc->toksclen;
+ goto done;
+ }
+ }
+ errsig(ctx->tokcxerr, ERR_INVTOK);
+ }
+
+done:
+ ctx->tokcxptr = p;
+ ctx->tokcxlen = len;
+ return(tok->toktyp);
+}
+
+/* initialize a linear symbol table */
+void toktlini(errcxdef *errctx, toktldef *toktab, uchar *mem, uint siz)
+{
+ CLRSTRUCT(*toktab);
+
+ /* initialize superclass data */
+ toktab->toktlsc.toktfadd = toktladd; /* set add-symbol method */
+ toktab->toktlsc.toktfsea = toktlsea; /* set search-table method */
+ toktab->toktlsc.toktfeach = toktleach; /* set 'each' method */
+ toktab->toktlsc.toktfset = toktlset; /* set 'update' method */
+ toktab->toktlsc.tokterr = errctx; /* set error handling context */
+
+ /* initialize class data */
+ toktab->toktlptr = mem;
+ toktab->toktlnxt = mem;
+ toktab->toktlsiz = siz;
+}
+
+/* add a symbol to a linear symbol table */
+void toktladd(toktdef *toktab1, char *name, int namel,
+ int typ, int val, int hash)
+{
+ uint siz = sizeof(toks1def) + namel;
+ toksdef *sym;
+ toktldef *toktab = (toktldef *)toktab1;
+
+ VARUSED(hash);
+
+ if (toktab->toktlsiz < siz)
+ errsig(toktab->toktlsc.tokterr, ERR_NOLCLSY);
+
+ sym = (toksdef *)toktab->toktlnxt;
+ siz = osrndsz(siz);
+ toktab->toktlnxt += siz;
+ if (siz > toktab->toktlsiz) toktab->toktlsiz = 0;
+ else toktab->toktlsiz -= siz;
+
+ /* set up symbol */
+ sym->toksval = val;
+ sym->tokslen = namel;
+ sym->tokstyp = typ;
+ sym->toksfr = 0;
+ memcpy(sym->toksnam, name, (size_t)(namel + 1));
+
+ /* indicate there's one more symbol in the table */
+ ++(toktab->toktlcnt);
+}
+
+/* delete all symbols from a linear symbol table */
+void toktldel(toktldef *tab)
+{
+ tab->toktlcnt = 0;
+ tab->toktlsiz += tab->toktlnxt - tab->toktlptr;
+ tab->toktlnxt = tab->toktlptr;
+}
+
+/* call a function for every symbol in a linear symbol table */
+void toktleach(toktdef *tab1,
+ void (*cb)(void *ctx, toksdef *sym), void *ctx)
+{
+ toksdef *p;
+ uint cnt;
+ toktldef *tab = (toktldef *)tab1;
+
+ for (p = (toksdef *)tab->toktlptr, cnt = tab->toktlcnt ; cnt ; --cnt )
+ {
+ (*cb)(ctx, p);
+ p = (toksdef *)(((uchar *)p)
+ + osrndsz(p->tokslen + sizeof(toks1def)));
+ }
+}
+
+/* search a linear symbol table */
+int toktlsea(toktdef *tab1, char *name, int namel, int hash, toksdef *ret)
+{
+ toksdef *p;
+ uint cnt;
+ toktldef *tab = (toktldef *)tab1;
+
+ VARUSED(hash);
+
+ for (p = (toksdef *)tab->toktlptr, cnt = tab->toktlcnt ; cnt ; --cnt )
+ {
+ if (p->tokslen == namel && !memcmp(p->toksnam, name, (size_t)namel))
+ {
+ memcpy(ret, p, (size_t)(sizeof(toks1def) + namel));
+ return(TRUE);
+ }
+
+ p = (toksdef *)(((uchar *)p)
+ + osrndsz(p->tokslen + sizeof(toks1def)));
+ }
+
+ /* nothing found - indicate by returning FALSE */
+ return(FALSE);
+}
+
+/* update a symbol in a linear symbol table */
+void toktlset(toktdef *tab1, toksdef *newsym)
+{
+ toksdef *p;
+ uint cnt;
+ toktldef *tab = (toktldef *)tab1;
+
+ for (p = (toksdef *)tab->toktlptr, cnt = tab->toktlcnt ; cnt ; --cnt )
+ {
+ if (p->tokslen == newsym->tokslen
+ && !memcmp(p->toksnam, newsym->toksnam, (size_t)newsym->tokslen))
+ {
+ p->toksval = newsym->toksval;
+ p->tokstyp = newsym->tokstyp;
+ return;
+ }
+
+ p = (toksdef *)(((uchar *)p)
+ + osrndsz(p->tokslen + sizeof(toks1def)));
+ }
+}
+
+tokcxdef *tokcxini(errcxdef *errctx, mcmcxdef *mcmctx, tokldef *sctab)
+{
+ int i;
+ int cnt;
+ tokldef *p;
+ uchar c;
+ uchar index[256];
+ tokcxdef *ret;
+ tokscdef *sc;
+ ushort siz;
+
+ /* set up index table: finds tokcxsc entry from character value */
+ memset(index, 0, (size_t)sizeof(index));
+ for (i = cnt = 0, p = sctab ; (c = p->toklstr[0]) != 0 ; ++cnt, ++p)
+ if (!index[c]) index[c] = ++i;
+
+ /* allocate memory for table plus the tokscdef's */
+ siz = sizeof(tokcxdef) + (i * sizeof(tokscdef *))
+ + ((cnt + 1) * sizeof(tokscdef));
+ ret = (tokcxdef *)mchalo(errctx, siz, "tokcxini");
+ memset(ret, 0, (size_t)siz);
+
+ /* copy the index, set up fixed part */
+ memcpy(ret->tokcxinx, index, sizeof(ret->tokcxinx));
+ ret->tokcxerr = errctx;
+ ret->tokcxmem = mcmctx;
+
+ /* start out without an #if */
+ ret->tokcxifcur = TOKIF_IF_YES;
+
+ /* force the first toknext() to read a line */
+ ret->tokcxptr = "\000";
+
+ /* figure where the tokscdef's go (right after sc pointer array) */
+ sc = (tokscdef *)&ret->tokcxsc[i+1];
+
+ /* set up the individual tokscdef entries, and link into lists */
+ for (p = sctab ; (c = p->toklstr[0]) != 0 ; ++p, ++sc)
+ {
+ size_t len;
+
+ sc->toksctyp = p->tokltyp;
+ len = sc->toksclen = strlen(p->toklstr);
+ memcpy(sc->tokscstr, p->toklstr, len);
+ sc->tokscnxt = ret->tokcxsc[index[c]];
+ ret->tokcxsc[index[c]] = sc;
+ }
+
+ return(ret);
+}
+
+/* add an include path to a tokdef */
+void tokaddinc(tokcxdef *ctx, char *path, int pathlen)
+{
+ tokpdef *newpath;
+ tokpdef *last;
+
+ /* find the tail of the include path list, if any */
+ for (last = ctx->tokcxinc ; last && last->tokpnxt ;
+ last = last->tokpnxt) ;
+
+ /* allocate storage for and set up a new path structure */
+ newpath = (tokpdef *)mchalo(ctx->tokcxerr,
+ (sizeof(tokpdef) + pathlen - 1),
+ "tokaddinc");
+ newpath->tokplen = pathlen;
+ newpath->tokpnxt = (tokpdef *)0;
+ memcpy(newpath->tokpdir, path, (size_t)pathlen);
+
+ /* link in at end of list (if no list yet, newpath becomes first entry) */
+ if (last)
+ last->tokpnxt = newpath;
+ else
+ ctx->tokcxinc = newpath;
+}
} // End of namespace TADS2
} // End of namespace TADS
diff --git a/engines/glk/tads/tads2/tokenizer.h b/engines/glk/tads/tads2/tokenizer.h
index 50a8492..11d76c6 100644
--- a/engines/glk/tads/tads2/tokenizer.h
+++ b/engines/glk/tads/tads2/tokenizer.h
@@ -26,6 +26,7 @@
#include "glk/tads/tads2/lib.h"
#include "glk/tads/tads2/error_handling.h"
#include "glk/tads/tads2/line_source.h"
+#include "glk/tads/tads2/line_source_file.h"
#include "glk/tads/tads2/memory_cache.h"
namespace Glk {
@@ -356,7 +357,7 @@ struct tokcxdef {
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 */
+ linfdef *tokcxhdr; /* list of previously included headers */
tokscdef *tokcxsc[1]; /* special character table */
};
@@ -453,16 +454,16 @@ 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)=='$')
+ (Common::isAlpha((uchar)(c)) || Common::isDigit((uchar)(c)) || (c)=='_' || (c)=='$')
/* numeric conversion and checking macros */
#define TOKISHEX(c) \
- (isdigit((uchar)(c))||((c)>='a'&&(c)<='f')||((c)>='A'&&(c)<='F'))
+ (Common::isDigit((uchar)(c))||((c)>='a'&&(c)<='f')||((c)>='A'&&(c)<='F'))
#define TOKISOCT(c) \
- (isdigit((uchar)(c))&&!((c)=='8'||(c)=='9'))
+ (Common::isDigit((uchar)(c))&&!((c)=='8'||(c)=='9'))
#define TOKHEX2INT(c) \
- (isdigit((uchar)c)?(c)-'0':((c)>='a'?(c)-'a'+10:(c)-'A'+10))
+ (Common::isDigit((uchar)c)?(c)-'0':((c)>='a'?(c)-'a'+10:(c)-'A'+10))
#define TOKOCT2INT(c) ((c)-'0')
#define TOKDEC2INT(c) ((c)-'0')
diff --git a/engines/glk/tads/tads2/vocabulary.cpp b/engines/glk/tads/tads2/vocabulary.cpp
index e20bbe3..516e9df 100644
--- a/engines/glk/tads/tads2/vocabulary.cpp
+++ b/engines/glk/tads/tads2/vocabulary.cpp
@@ -22,12 +22,912 @@
#include "glk/tads/tads2/run.h"
#include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/memory_cache_heap.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
-// TODO: Rest of vocabulary stuff
+
+/*
+ * Main vocabulary context. This can be saved globally if desired, so
+ * that routines that don't have any other access to it (such as
+ * Unix-style signal handlers) can reach it.
+ */
+voccxdef *main_voc_ctx = 0;
+
+#ifdef VOCW_IN_CACHE
+vocwdef *vocwget(voccxdef *ctx, uint idx)
+{
+ uint pg;
+
+ if (idx == VOCCXW_NONE)
+ return 0;
+
+ /* get the page we need */
+ pg = idx/VOCWPGSIZ;
+
+ /* if it's not locked, lock it */
+ if (pg != ctx->voccxwplck)
+ {
+ /* unlock the old page */
+ if (ctx->voccxwplck != MCMONINV)
+ mcmunlck(ctx->voccxmem, ctx->voccxwp[ctx->voccxwplck]);
+
+ /* lock the new page */
+ ctx->voccxwpgptr = (vocwdef *)mcmlck(ctx->voccxmem,
+ ctx->voccxwp[pg]);
+ ctx->voccxwplck = pg;
+ }
+
+ /* return the entry on that page */
+ return ctx->voccxwpgptr + (idx % VOCWPGSIZ);
+}
+#endif /*VOCW_IN_CACHE */
+
+/* hash value is based on first 6 characters only to allow match-in-6 */
+uint vochsh(uchar *t, int len)
+{
+ uint ret = 0;
+
+ if (len > 6) len = 6;
+ for ( ; len ; --len, ++t)
+ ret = (ret + (uint)(vocisupper(*t) ? tolower(*t) : *t))
+ & (VOCHASHSIZ - 1);
+ return(ret);
+}
+
+/* copy vocabulary word, and convert to lower case */
+static void voccpy(uchar *dst, uchar *src, int len)
+{
+ for ( ; len ; --len, ++dst, ++src)
+ *dst = vocisupper(*src) ? tolower(*src) : *src;
+}
+
+/* allocate and set up a new vocwdef record, linking into a vocdef's list */
+static void vocwset(voccxdef *ctx, vocdef *v, prpnum p, objnum objn,
+ int classflg)
+{
+ vocwdef *vw;
+ uint inx;
+ vocwdef *vw2;
+
+ /*
+ * look through the vocdef list to see if there's an existing entry
+ * with the DELETED marker -- if so, simply undelete it
+ */
+ for (inx = v->vocwlst, vw = vocwget(ctx, inx) ; vw ;
+ inx = vw->vocwnxt, vw = vocwget(ctx, inx))
+ {
+ /* if this entry was deleted, and otherwise matches, undelete it */
+ if ((vw->vocwflg & VOCFDEL)
+ && vw->vocwobj == objn && vw->vocwtyp == p)
+ {
+ /*
+ * Remove the deleted flag. We will otherwise leave the
+ * flags unchanged, since the VOCFDEL flag applies only to
+ * statically allocated objects, and hence the original
+ * flags should take precedence over any run-time flags.
+ */
+ vw->vocwflg &= ~VOCFDEL;
+
+ /* we're done */
+ return;
+ }
+ }
+
+ /* make sure the word+object+type record isn't already defined */
+ for (inx = v->vocwlst, vw = vocwget(ctx, inx) ; vw ;
+ inx = vw->vocwnxt, vw = vocwget(ctx, inx))
+ {
+ if (vw->vocwobj == objn && vw->vocwtyp == p
+ && (vw->vocwflg & VOCFCLASS) == (classflg & VOCFCLASS))
+ {
+ /* it matches - don't add a redundant record */
+ return;
+ }
+ }
+
+ /* look in the free list for an available vocwdef */
+ if (ctx->voccxwfre != VOCCXW_NONE)
+ {
+ inx = ctx->voccxwfre;
+ vw = vocwget(ctx, inx); /* get the free vocwdef */
+ ctx->voccxwfre = vw->vocwnxt; /* unlink from free list */
+ }
+ else
+ {
+ /* allocate another page of vocwdef's if necssary */
+ if ((ctx->voccxwalocnt % VOCWPGSIZ) == 0)
+ {
+ int pg = ctx->voccxwalocnt / VOCWPGSIZ;
+
+ /* make sure we haven't exceeded the available page count */
+ if (pg >= VOCWPGMAX) errsig(ctx->voccxerr, ERR_VOCMNPG);
+
+ /* allocate on the new page */
+#ifdef VOCW_IN_CACHE
+ mcmalo(ctx->voccxmem, (ushort)(VOCWPGSIZ * sizeof(vocwdef)),
+ &ctx->voccxwp[pg]);
+ mcmunlck(ctx->voccxmem, ctx->voccxwp[pg]);
+#else
+ ctx->voccxwp[pg] =
+ (vocwdef *)mchalo(ctx->voccxerr,
+ (VOCWPGSIZ * sizeof(vocwdef)),
+ "vocwset");
+#endif
+ }
+
+ /* get the next entry, and increment count of used entries */
+ inx = ctx->voccxwalocnt++;
+ vw = vocwget(ctx, inx);
+ }
+
+ /* link the new vocwdef into the vocdef's relation list */
+ vw->vocwnxt = v->vocwlst;
+ v->vocwlst = inx;
+
+ /* set up the new vocwdef */
+ vw->vocwtyp = (uchar)p;
+ vw->vocwobj = objn;
+ vw->vocwflg = classflg;
+
+ /*
+ * Scan the list and make sure we're not adding a redundant verb.
+ * Don't bother with the warning if this is a class.
+ */
+ if (p == PRP_VERB && (ctx->voccxflg & VOCCXFVWARN)
+ && (vw->vocwflg & VOCFCLASS) == 0)
+ {
+ for (vw2 = vocwget(ctx, v->vocwlst) ; vw2 ;
+ vw2 = vocwget(ctx, vw2->vocwnxt))
+ {
+ /*
+ * if this is a different object, and it's not a class, and
+ * it's defined as a verb, warn about it
+ */
+ if (vw2 != vw
+ && (vw2->vocwflg & VOCFCLASS) == 0
+ && vw2->vocwtyp == PRP_VERB)
+ {
+ if (v->vocln2 != 0)
+ errlog2(ctx->voccxerr, ERR_VOCREVB,
+ ERRTSTR,
+ errstr(ctx->voccxerr,
+ (char *)v->voctxt, v->voclen),
+ ERRTSTR,
+ errstr(ctx->voccxerr,
+ (char *)v->voctxt + v->voclen, v->vocln2));
+ else
+ errlog1(ctx->voccxerr, ERR_VOCREVB,
+ ERRTSTR,
+ errstr(ctx->voccxerr,
+ (char *)v->voctxt, v->voclen));
+ break;
+ }
+ }
+ }
+}
+
+/* set up a vocdef record, and link into hash table */
+static void vocset(voccxdef *ctx, vocdef *v, prpnum p, objnum objn,
+ int classflg, uchar *wrdtxt, int len,
+ uchar *wrd2, int len2)
+{
+ uint hshval = vochsh(wrdtxt, len);
+
+ v->vocnxt = ctx->voccxhsh[hshval];
+ ctx->voccxhsh[hshval] = v;
+
+ v->voclen = len;
+ v->vocln2 = len2;
+ voccpy(v->voctxt, wrdtxt, len);
+ if (wrd2) voccpy(v->voctxt + len, wrd2, len2);
+
+ /* allocate and initialize a vocwdef for the object */
+ vocwset(ctx, v, p, objn, classflg);
+}
+
+/* internal addword - already parsed into two words and have lengths */
+void vocadd2(voccxdef *ctx, prpnum p, objnum objn, int classflg,
+ uchar *wrdtxt, int len, uchar *wrd2, int len2)
+{
+ vocdef *v;
+ vocdef *prv;
+ uint need;
+ uint hshval;
+
+ /* if the word is null, ignore it entirely */
+ if (len == 0 && len2 == 0)
+ return;
+
+ /* look for a vocdef entry with the same word text */
+ hshval = vochsh(wrdtxt, len);
+ for (v = ctx->voccxhsh[hshval] ; v ; v = v->vocnxt)
+ {
+ /* if it matches on both words, use this entry */
+ if (v->voclen == len && !memcmp(wrdtxt, v->voctxt, (size_t)len)
+ && ((!wrd2 && v->vocln2 == 0)
+ || (v->vocln2 == len2 &&
+ !memcmp(wrd2, v->voctxt + len, (size_t)len2))))
+ {
+ vocwset(ctx, v, p, objn, classflg);
+ return;
+ }
+ }
+
+ /* look for a free vocdef entry of the same size */
+ for (prv = (vocdef *)0, v = ctx->voccxfre ; v ; prv = v, v = v->vocnxt)
+ if (v->voclen == len + len2) break;
+
+ if (v)
+ {
+ /* we found something - unlink from free list */
+ if (prv) prv->vocnxt = v->vocnxt;
+ else ctx->voccxfre = v->vocnxt;
+
+ /* reuse the entry */
+ v->vocwlst = VOCCXW_NONE;
+ vocset(ctx, v, p, objn, classflg, wrdtxt, len, wrd2, len2);
+ return;
+ }
+
+ /* didn't find an existing vocdef; allocate a new one */
+ need = sizeof(vocdef) + len + len2 - 1;
+ if (ctx->voccxrem < need)
+ {
+ /* not enough space in current page; allocate a new one */
+ ctx->voccxpool = mchalo(ctx->voccxerr, VOCPGSIZ, "vocadd2");
+ ctx->voccxrem = VOCPGSIZ;
+ }
+
+ /* use top of current pool, and update pool pointer and size */
+ v = (vocdef *)ctx->voccxpool;
+ need = osrndsz(need);
+ ctx->voccxpool += need;
+ if (ctx->voccxrem > need) ctx->voccxrem -= need;
+ else ctx->voccxrem = 0;
+
+ /* set up new vocdef */
+ v->vocwlst = VOCCXW_NONE;
+ vocset(ctx, v, p, objn, classflg, wrdtxt, len, wrd2, len2);
+}
+
+static void voc_parse_words(char **wrdtxt, int *len, char **wrd2, int *len2)
+{
+ /* get length and pointer to actual text */
+ *len = osrp2(*wrdtxt) - 2;
+ *wrdtxt += 2;
+
+ /* see if there's a second word - look for a space */
+ for (*wrd2 = *wrdtxt, *len2 = *len ; *len2 && !vocisspace(**wrd2) ;
+ ++*wrd2, --*len2) ;
+ if (*len2)
+ {
+ *len -= *len2;
+ while (*len2 && vocisspace(**wrd2)) ++*wrd2, --*len2;
+ }
+ else
+ {
+ /* no space ==> no second word */
+ *wrd2 = (char *)0;
+ }
+}
+
+void vocadd(voccxdef *ctx, prpnum p, objnum objn, int classflg, char *wrdtxt)
+{
+ int len;
+ char *wrd2;
+ int len2;
+
+ voc_parse_words(&wrdtxt, &len, &wrd2, &len2);
+ vocadd2(ctx, p, objn, classflg, (uchar *)wrdtxt, len, (uchar *)wrd2, len2);
+}
+
+/* make sure we have a page table entry for an object, allocating one if not */
+void vocialo(voccxdef *ctx, objnum obj)
+{
+ if (!ctx->voccxinh[obj >> 8])
+ {
+ ctx->voccxinh[obj >> 8] =
+ (vocidef **)mchalo(ctx->voccxerr,
+ (256 * sizeof(vocidef *)), "vocialo");
+ memset(ctx->voccxinh[obj >> 8], 0, (size_t)(256 * sizeof(vocidef *)));
+ }
+}
+
+/* add an inheritance/location record */
+void vociadd(voccxdef *ctx, objnum obj, objnum loc,
+ int numsc, objnum *sc, int flags)
+{
+ vocidef *v;
+ vocidef *min;
+ vocidef *prv;
+ vocidef *minprv = nullptr;
+
+ /* make sure we have a page table entry for this object */
+ vocialo(ctx, obj);
+
+ /* look in free list for an entry that's big enough */
+ for (prv = (vocidef *)0, min = (vocidef *)0, v = ctx->voccxifr ; v ;
+ prv = v, v = v->vocinxt)
+ {
+ if (v->vocinsc == numsc)
+ {
+ min = v;
+ minprv = prv;
+ break;
+ }
+ else if (v->vocinsc > numsc)
+ {
+ if (!min || v->vocinsc < min->vocinsc)
+ {
+ min = v;
+ minprv = prv;
+ }
+ }
+ }
+
+ if (!min)
+ {
+ uint need;
+
+ /* nothing in free list; allocate a new entry */
+ need = osrndsz(sizeof(vocidef) + (numsc - 1)*sizeof(objnum));
+ if (ctx->voccxilst + need >= VOCISIZ)
+ {
+ /* nothing left on current page; allocate a new page */
+ ctx->voccxip[++(ctx->voccxiplst)] =
+ mchalo(ctx->voccxerr, VOCISIZ, "vociadd");
+ ctx->voccxilst = 0;
+ }
+
+ /* allocate space out of current page */
+ v = (vocidef *)(ctx->voccxip[ctx->voccxiplst] + ctx->voccxilst);
+ ctx->voccxilst += need;
+ }
+ else
+ {
+ /* unlink from chain and use */
+ v = min;
+ if (minprv)
+ minprv->vocinxt = v->vocinxt;
+ else
+ ctx->voccxifr = v->vocinxt;
+ }
+
+ /* set up the entry */
+ if (vocinh(ctx, obj) != (vocidef *)0) errsig(ctx->voccxerr, ERR_VOCINUS);
+ v->vociloc = loc;
+ v->vociilc = MCMONINV;
+ v->vociflg = (flags & ~VOCIFXLAT);
+ v->vocinsc = numsc;
+ if (numsc)
+ {
+ if (flags & VOCIFXLAT)
+ {
+ int i;
+
+ for (i = 0 ; i < numsc ; ++i)
+ v->vocisc[i] = osrp2(&sc[i]);
+ }
+ else
+ memcpy(v->vocisc, sc, (size_t)(numsc * sizeof(objnum)));
+ }
+ vocinh(ctx, obj) = v; /* set page table entry */
+}
+
+/* revert all objects to original state, using inheritance records */
+void vocrevert(voccxdef *vctx)
+{
+ vocidef ***vpg;
+ vocidef **v;
+ int i;
+ int j;
+ objnum obj;
+
+ /*
+ * Go through the inheritance records. Delete each dynamically
+ * allocated object, and revert each static object to its original
+ * load state.
+ */
+ for (vpg = vctx->voccxinh, i = 0 ; i < VOCINHMAX ; ++vpg, ++i)
+ {
+ if (!*vpg) continue;
+ for (v = *vpg, obj = (i << 8), j = 0 ; j < 256 ; ++v, ++obj, ++j)
+ {
+ if (*v)
+ {
+ /* if the object was dynamically allocated, delete it */
+ if ((*v)->vociflg & VOCIFNEW)
+ {
+ /* delete vocabulary and inheritance data for the object */
+ vocidel(vctx, obj);
+ vocdel(vctx, obj);
+
+ /* delete the object */
+ mcmfre(vctx->voccxmem, (mcmon)obj);
+ }
+ else
+ {
+ /* revert the object */
+ mcmrevert(vctx->voccxmem, (mcmon)obj);
+ }
+ }
+ }
+ }
+
+ /*
+ * Revert the vocabulary list: delete all newly added words, and
+ * undelete all original words marked as deleted.
+ */
+ vocdel1(vctx, MCMONINV, (char *)0, 0, TRUE, TRUE, FALSE);
+}
+
+/* initialize voc context */
+void vocini(voccxdef *vocctx, errcxdef *errctx, mcmcxdef *memctx,
+ runcxdef *runctx, objucxdef *undoctx,
+ int fuses, int daemons, int notifiers)
+{
+ CLRSTRUCT(*vocctx);
+ vocctx->voccxerr = errctx;
+ vocctx->voccxiplst = (uint)-1;
+ vocctx->voccxilst = VOCISIZ;
+ vocctx->voccxmem = memctx;
+ vocctx->voccxrun = runctx;
+ vocctx->voccxundo = undoctx;
+
+ vocctx->voccxme =
+ vocctx->voccxme_init =
+ vocctx->voccxvtk =
+ vocctx->voccxstr =
+ vocctx->voccxnum =
+ vocctx->voccxit =
+ vocctx->voccxhim =
+ vocctx->voccxprd =
+ vocctx->voccxpre =
+ vocctx->voccxpre2 =
+ vocctx->voccxppc =
+ vocctx->voccxlsv =
+ vocctx->voccxpreinit =
+ vocctx->voccxper =
+ vocctx->voccxprom =
+ vocctx->voccxpostprom =
+ vocctx->voccxpdis =
+ vocctx->voccxper2 =
+ vocctx->voccxperp =
+ vocctx->voccxpdef =
+ vocctx->voccxpdef2 =
+ vocctx->voccxpask =
+ vocctx->voccxpask2 =
+ vocctx->voccxpask3 =
+ vocctx->voccxinitrestore =
+ vocctx->voccxpuv =
+ vocctx->voccxpnp =
+ vocctx->voccxpostact =
+ vocctx->voccxendcmd =
+ vocctx->voccxher = MCMONINV;
+ vocctx->voccxthc = 0;
+#ifdef VOCW_IN_CACHE
+ vocctx->voccxwplck = MCMONINV;
+#endif
+
+ vocctx->voccxactor = MCMONINV;
+ vocctx->voccxverb = MCMONINV;
+ vocctx->voccxprep = MCMONINV;
+ vocctx->voccxdobj = 0;
+ vocctx->voccxiobj = 0;
+
+ vocctx->voccxunknown = 0;
+ vocctx->voccxlastunk = 0;
+
+ vocctx->voc_stk_ptr = 0;
+ vocctx->voc_stk_cur = 0;
+ vocctx->voc_stk_end = 0;
+
+ /* allocate fuses, daemons, notifiers */
+ vocinialo(vocctx, &vocctx->voccxfus, (vocctx->voccxfuc = fuses));
+ vocinialo(vocctx, &vocctx->voccxdmn, (vocctx->voccxdmc = daemons));
+ vocinialo(vocctx, &vocctx->voccxalm, (vocctx->voccxalc = notifiers));
+
+ /* no entries in vocwdef free list yet */
+ vocctx->voccxwfre = VOCCXW_NONE;
+}
+
+/* uninitialize the voc context */
+void vocterm(voccxdef *ctx)
+{
+ /* delete the fuses, daemons, and notifiers */
+ voctermfree(ctx->voccxfus);
+ voctermfree(ctx->voccxdmn);
+ voctermfree(ctx->voccxalm);
+
+ /* delete the private stack */
+ if (ctx->voc_stk_ptr != 0)
+ mchfre(ctx->voc_stk_ptr);
+}
+
+/* clean up the vocab context */
+void voctermfree(vocddef *what)
+{
+ if (what != 0)
+ mchfre(what);
+}
+
+/*
+ * 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)
+{
+ int i;
+ vocdef *v;
+ vocdef **vp;
+ vocwdef *vw;
+ uint idx;
+
+ /* go through each hash value looking for matching words */
+ for (i = VOCHASHSIZ, vp = ctx->voccxhsh ; i ; ++vp, --i)
+ {
+ /* go through all words in this hash chain */
+ for (v = *vp ; v ; v = v->vocnxt)
+ {
+ /* go through each object relation for this word */
+ for (idx = v->vocwlst, vw = vocwget(ctx, idx) ; vw ;
+ idx = vw->vocwnxt, vw = vocwget(ctx, idx))
+ {
+ /*
+ * if this word is for this object, call the callback
+ */
+ if (objn == MCMONINV || vw->vocwobj == objn)
+ (*fn)(fnctx, v, vw);
+ }
+ }
+ }
+}
+
+/* callback context for voc_count */
+struct voc_count_ctx
+{
+ int cnt;
+ int siz;
+ prpnum prp;
+};
+
+/* callback for voc_count */
+static void voc_count_cb(void *ctx0, vocdef *voc, vocwdef *vocw)
+{
+ struct voc_count_ctx *ctx = (struct voc_count_ctx *)ctx0;
+
+ VARUSED(vocw);
+
+ /*
+ * If it matches the property (or we want all properties), count
+ * it. Don't count deleted objects.
+ */
+ if ((ctx->prp == 0 || ctx->prp == vocw->vocwtyp)
+ && !(vocw->vocwflg & VOCFDEL))
+ {
+ /* count the word */
+ ctx->cnt++;
+
+ /* count the size */
+ ctx->siz += voc->voclen + voc->vocln2;
+ }
+}
+
+/*
+ * 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)
+{
+ struct voc_count_ctx fnctx;
+
+ /* set up the context with zero initial counts */
+ fnctx.cnt = 0;
+ fnctx.siz = 0;
+ fnctx.prp = prp;
+
+ /* iterate over all words for the object with our callback */
+ voc_iterate(ctx, objn, voc_count_cb, &fnctx);
+
+ /* return the data */
+ if (cnt) *cnt = fnctx.cnt;
+ if (siz) *siz = fnctx.siz;
+}
+
+
+/*
+ * Delete a particular word associated with an object, or all words if
+ * the word pointer is null. If the word isn't marked as added at
+ * runtime (i.e., the VOCFNEW flag is not set for the word), the word is
+ * simply marked as deleted, rather than being actually deleted.
+ * However, if the 'really_delete' flag is set, the word is actually
+ * deleted. If the 'revert' flag is true, this routine deletes _every_
+ * dynamically created word, and undeletes all dynamically deleted words
+ * that were in the original vocabulary.
+ */
+void vocdel1(voccxdef *ctx, objnum objn, char *wrd1, prpnum prp,
+ int really_delete, int revert, int keep_undo)
+{
+ int i;
+ vocdef *v;
+ vocdef *prv;
+ vocdef *nxt;
+ vocdef **vp;
+ vocwdef *vw;
+ vocwdef *prvw;
+ vocwdef *nxtw;
+ uint nxtidx;
+ uint idx;
+ int deleted_vocdef;
+ char *wrd2 = nullptr;
+ int len1 = 0, len2 = 0;
+ int do_del;
+ char *orgwrd;
+
+ /* parse the word if provided */
+ orgwrd = wrd1;
+ if (wrd1)
+ voc_parse_words(&wrd1, &len1, &wrd2, &len2);
+
+ /* go through each hash value looking for matching words */
+ for (i = VOCHASHSIZ, vp = ctx->voccxhsh ; i ; ++vp, --i)
+ {
+ /* go through all words in this hash chain */
+ for (prv = (vocdef *)0, v = *vp ; v ; v = nxt)
+ {
+ /* remember next word in hash chain */
+ nxt = v->vocnxt;
+
+ /* if this word doesn't match, skip it */
+ if (wrd1)
+ {
+ /* compare the first word */
+ if (v->voclen != len1
+ || memicmp((char *)v->voctxt, wrd1, (size_t)len1))
+ {
+ prv = v;
+ continue;
+ }
+
+ /* if there's a second word, compare it as well */
+ if (wrd2 && (v->vocln2 != len2
+ || memicmp((char *)v->voctxt + len1,
+ wrd2, (size_t)len2)))
+ {
+ prv = v;
+ continue;
+ }
+ }
+
+ /* presume we're not going to delete this vocdef */
+ deleted_vocdef = FALSE;
+
+ /* go through all object relations for this word */
+ for (prvw = 0, idx = v->vocwlst, vw = vocwget(ctx, idx) ; vw ;
+ vw = nxtw, idx = nxtidx)
+ {
+ /* remember next word in relation list */
+ nxtidx = vw->vocwnxt;
+ nxtw = vocwget(ctx, nxtidx);
+
+ /*
+ * figure out whether to delete this word, based on the
+ * caller's specified operating mode
+ */
+ if (revert)
+ {
+ /* if reverting, delete all new words */
+ do_del = (vw->vocwflg & VOCFNEW);
+
+ /* also, remove the DELETED flag if present */
+ vw->vocwflg &= ~VOCFDEL;
+ }
+ else
+ {
+ /*
+ * delete the word if the object number matches,
+ * AND either we're not searching for a specific
+ * vocabulary word (in which case wrd1 will be null)
+ * or the word matches the vocabulary word we're
+ * seeking (in which case the part of speech must
+ * match)
+ */
+ do_del = (vw->vocwobj == objn
+ && (wrd1 == 0 || vw->vocwtyp == prp));
+
+ /*
+ * if we're not in really_delete mode, and the word
+ * matches and it wasn't dynamically added at
+ * run-time, simply mark it as deleted -- this will
+ * allow it to be undeleted if the game is reverted
+ * to RESTART conditions
+ */
+ if (do_del && !really_delete && !(vw->vocwflg & VOCFNEW))
+ {
+ /* geneate undo for the operation */
+ if (keep_undo && orgwrd)
+ vocdusave_delwrd(ctx, objn, prp,
+ vw->vocwflg, orgwrd);
+
+ /* just mark the word for deletion, but keep vw */
+ vw->vocwflg |= VOCFDEL;
+ do_del = FALSE;
+ }
+ }
+
+ /* now delete the structure if we decided we should */
+ if (do_del)
+ {
+ /* geneate undo for the operation */
+ if (keep_undo && orgwrd)
+ vocdusave_delwrd(ctx, objn, prp, vw->vocwflg, orgwrd);
+
+ /* unlink this vocwdef from the vocdef's list */
+ if (prvw)
+ prvw->vocwnxt = vw->vocwnxt;
+ else
+ v->vocwlst = vw->vocwnxt;
+
+ /* link the vocwdef into the vocwdef free list */
+ vw->vocwnxt = ctx->voccxwfre;
+ ctx->voccxwfre = idx;
+
+ /*
+ * if there's nothing left in the vocdef's list,
+ * delete the entire vocdef as well
+ */
+ if (v->vocwlst == VOCCXW_NONE)
+ {
+ if (prv) prv->vocnxt = v->vocnxt;
+ else *vp = v->vocnxt;
+
+ /* link into free chain */
+ v->vocnxt = ctx->voccxfre;
+ ctx->voccxfre = v;
+
+ /* note that it's been deleted */
+ deleted_vocdef = TRUE;
+ }
+ }
+ else
+ {
+ /* we're not deleting the word, so move prvw forward */
+ prvw = vw;
+ }
+ }
+
+ /* if we didn't delete this vocdef, move prv forward onto it */
+ if (!deleted_vocdef)
+ prv = v;
+ }
+ }
+}
+
+/* delete all vocabulary for an object */
+void vocdel(voccxdef *ctx, objnum objn)
+{
+ vocdel1(ctx, objn, (char *)0, (prpnum)0, TRUE, FALSE, FALSE);
+}
+
+/* delete object inheritance records for a particular object */
+void vocidel(voccxdef *ctx, objnum obj)
+{
+ vocidef *v;
+
+ /* get entry out of page table, and clear page table slot */
+ v = vocinh(ctx, obj);
+ vocinh(ctx, obj) = (vocidef *)0;
+
+ /* link into free list */
+ if (v)
+ {
+ v->vocinxt = ctx->voccxifr;
+ ctx->voccxifr = v;
+ }
+}
+
+/*
+ * Find template matching a verb+object+prep combination; return TRUE
+ * if a suitable template is found, FALSE otherwise.
+ */
+int voctplfnd(voccxdef *ctx, objnum verb_in, objnum prep,
+ uchar *tplout, int *newstyle)
+{
+ uchar *tplptr;
+ uchar *thistpl;
+ int found;
+ int tplcnt;
+ uint tplofs;
+ objnum verb;
+
+ /* look for a new-style template first */
+ tplofs = objgetap(ctx->voccxmem, verb_in, PRP_TPL2, &verb, FALSE);
+ if (tplofs)
+ {
+ /* flag the presence of the new-style template */
+ *newstyle = TRUE;
+ }
+ else
+ {
+ /* no new-style template - look for old version */
+ tplofs = objgetap(ctx->voccxmem, verb_in, PRP_TPL, &verb, FALSE);
+ *newstyle = FALSE;
+ }
+
+ /* inherit templates until we run out of them */
+ for (;;)
+ {
+ /* if we found something already, use it */
+ if (tplofs)
+ {
+ size_t siz;
+
+ /* figure the size of this template style */
+ siz = (*newstyle ? VOCTPL2SIZ : VOCTPLSIZ);
+
+ /* lock the verb object, and get the property value pointer */
+ tplptr = mcmlck(ctx->voccxmem, verb);
+ thistpl = prpvalp(tplptr + tplofs);
+
+ /* first byte is number of templates in array */
+ tplcnt = *thistpl++;
+
+ /* look for a template that matches the preposition object */
+ for (found = FALSE ; tplcnt ; thistpl += siz, --tplcnt)
+ {
+ if (voctplpr(thistpl) == prep)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ /* unlock the object and return the value if we found one */
+ mcmunlck(ctx->voccxmem, verb);
+ if (found)
+ {
+ memcpy(tplout, thistpl, siz);
+ return(TRUE);
+ }
+ }
+
+ /* try inheriting a template (new-style, then old-style) */
+ tplofs = objgetap(ctx->voccxmem, verb_in, PRP_TPL2, &verb, TRUE);
+ if (tplofs)
+ *newstyle = TRUE;
+ else
+ {
+ tplofs = objgetap(ctx->voccxmem, verb_in, PRP_TPL, &verb, TRUE);
+ *newstyle = FALSE;
+ }
+
+ /* return not-found if we couldn't inherit it */
+ if (!tplofs)
+ return FALSE;
+
+ /* use the newly found verb */
+ verb_in = verb;
+ }
+}
+
+/*
+ * Set the "Me" object
+ */
+void voc_set_me(voccxdef *ctx, objnum new_me)
+{
+ /* save undo for the change */
+ vocdusave_me(ctx, ctx->voccxme);
+
+ /* set the new "Me" object */
+ ctx->voccxme = new_me;
+}
} // End of namespace TADS2
} // End of namespace TADS
diff --git a/engines/glk/tads/tads2/vocabulary.h b/engines/glk/tads/tads2/vocabulary.h
index 5574675..f4c058e 100644
--- a/engines/glk/tads/tads2/vocabulary.h
+++ b/engines/glk/tads/tads2/vocabulary.h
@@ -27,6 +27,7 @@
#ifndef GLK_TADS_TADS2_VOCABULARY
#define GLK_TADS_TADS2_VOCABULARY
+#include "common/util.h"
#include "glk/tads/tads2/lib.h"
#include "glk/tads/tads2/object.h"
#include "glk/tads/tads2/property.h"
@@ -645,11 +646,11 @@ void vocdusave_me(voccxdef *ctx, objnum old_me);
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)))
+#define vocisupper(c) ((uchar)(c) <= 127 && Common::isUpper((uchar)(c)))
+#define vocislower(c) ((uchar)(c) <= 127 && Common::isLower((uchar)(c)))
+#define vocisalpha(c) ((uchar)(c) > 127 || Common::isAlpha((uchar)(c)))
+#define vocisspace(c) ((uchar)(c) <= 127 && Common::isSpace((uchar)(c)))
+#define vocisdigit(c) ((uchar)(c) <= 127 && Common::isDigit((uchar)(c)))
/*
Commit: 105a1b94bd9d5a0f10752e135671f4e9a4b0d8da
https://github.com/scummvm/scummvm/commit/105a1b94bd9d5a0f10752e135671f4e9a4b0d8da
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00
Commit Message:
GLK: TADS2: Added code for output, run, various miscellaneous
Changed paths:
A engines/glk/tads/os_glk.h
A engines/glk/tads/tads2/list.cpp
A engines/glk/tads/tads2/list.h
A engines/glk/tads/tads2/output.cpp
common/util.cpp
common/util.h
engines/glk/module.mk
engines/glk/tads/osfrobtads.cpp
engines/glk/tads/osfrobtads.h
engines/glk/tads/tads2/character_map.cpp
engines/glk/tads/tads2/data.cpp
engines/glk/tads/tads2/data.h
engines/glk/tads/tads2/error_handling.h
engines/glk/tads/tads2/lib.h
engines/glk/tads/tads2/os.h
engines/glk/tads/tads2/run.cpp
engines/glk/tads/tads2/run.h
engines/glk/tads/tads2/tokenizer.cpp
engines/glk/tads/tads2/vocabulary.cpp
diff --git a/common/util.cpp b/common/util.cpp
index 62a1baf..a6c7958 100644
--- a/common/util.cpp
+++ b/common/util.cpp
@@ -23,6 +23,7 @@
#define FORBIDDEN_SYMBOL_EXCEPTION_isalnum
#define FORBIDDEN_SYMBOL_EXCEPTION_isalpha
#define FORBIDDEN_SYMBOL_EXCEPTION_isdigit
+#define FORBIDDEN_SYMBOL_EXCEPTION_isxdigit
#define FORBIDDEN_SYMBOL_EXCEPTION_isnumber
#define FORBIDDEN_SYMBOL_EXCEPTION_islower
#define FORBIDDEN_SYMBOL_EXCEPTION_isspace
@@ -132,6 +133,11 @@ bool isDigit(int c) {
return isdigit((byte)c);
}
+bool isXDigit(int c) {
+ ENSURE_ASCII_CHAR(c);
+ return isxdigit((byte)c);
+}
+
bool isLower(int c) {
ENSURE_ASCII_CHAR(c);
return islower((byte)c);
diff --git a/common/util.h b/common/util.h
index 77d7523..5f853da 100644
--- a/common/util.h
+++ b/common/util.h
@@ -142,6 +142,16 @@ bool isAlpha(int c);
bool isDigit(int c);
/**
+ * Test whether the given character is a hwzadecimal-digit (0-9 or A-F).
+ * If the parameter is outside the range of a signed or unsigned char, then
+ * false is returned.
+ *
+ * @param c the character to test
+ * @return true if the character is a hexadecimal-digit, false otherwise.
+ */
+bool isXDigit(int c);
+
+/**
* Test whether the given character is a lower-case letter (a-z).
* If the parameter is outside the range of a signed or unsigned char, then
* false is returned.
diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index 9f95fc0..93eff12 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -100,11 +100,13 @@ MODULE_OBJS := \
tads/tads2/error_handling.o \
tads/tads2/file_io.o \
tads/tads2/line_source_file.o \
+ tads/tads2/list.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/output.o \
tads/tads2/regex.o \
tads/tads2/run.o \
tads/tads2/tads2.o \
diff --git a/engines/glk/tads/os_glk.h b/engines/glk/tads/os_glk.h
new file mode 100644
index 0000000..e58868b
--- /dev/null
+++ b/engines/glk/tads/os_glk.h
@@ -0,0 +1,69 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/* TADS OS interface file type definitions
+ *
+ * Defines certain datatypes used in the TADS operating system interface
+ */
+
+#ifndef GLK_TADS_OS_GLK
+#define GLK_TADS_OS_GLK
+
+namespace Glk {
+namespace TADS {
+
+#define OSPATHCHAR '/'
+#define OSPATHALT ""
+#define OSPATHURL "/"
+#define OSPATHSEP ':'
+#define OS_NEWLINE_SEQ "\n"
+
+/* maximum width (in characters) of a line of text */
+#define OS_MAXWIDTH 255
+
+/* round a size to worst-case alignment boundary */
+#define osrndsz(s) (((s)+3) & ~3)
+
+/* round a pointer to worst-case alignment boundary */
+#define osrndpt(p) ((uchar *)((((unsigned long)(p)) + 3) & ~3))
+
+/* read unaligned portable unsigned 2-byte value, returning int */
+#define osrp2(p) READ_LE_UINT16(p)
+
+/* read unaligned portable signed 2-byte value, returning int */
+#define osrp2s(p) READ_LE_INT16(p)
+
+/* write int to unaligned portable 2-byte value */
+#define oswp2(p, i) WRITE_LE_UINT16(p, i)
+#define oswp2s(p, i) WRITE_LE_INT16(p, i)
+
+/* read unaligned portable 4-byte value, returning unsigned long */
+#define osrp4(p) READ_LE_UINT32(p)
+#define osrp4s(p) READ_LE_INT32(p)
+
+#define oswp4(p, l) WRITE_LE_UINT32(p, l)
+#define oswp4s(p, l) WRITE_LE_INT32(p, l)
+
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/osfrobtads.cpp b/engines/glk/tads/osfrobtads.cpp
index 13229e5..df265be 100644
--- a/engines/glk/tads/osfrobtads.cpp
+++ b/engines/glk/tads/osfrobtads.cpp
@@ -50,5 +50,13 @@ bool osfwb(osfildef *fp, void *buf, size_t count) {
return dynamic_cast<Common::WriteStream *>(fp)->write(buf, count) != count;
}
+void osfflush(osfildef *fp) {
+ dynamic_cast<Common::WriteStream *>(fp)->flush();
+}
+
+osfildef *osfopwt(const char *fname, os_filetype_t typ) {
+ return osfoprwtb(fname, typ);
+}
+
} // End of namespace TADS
} // End of namespace Glk
diff --git a/engines/glk/tads/osfrobtads.h b/engines/glk/tads/osfrobtads.h
index b22dbae..ce1624d 100644
--- a/engines/glk/tads/osfrobtads.h
+++ b/engines/glk/tads/osfrobtads.h
@@ -38,13 +38,6 @@
namespace Glk {
namespace TADS {
-#define OSPATHCHAR '/'
-#define OSPATHALT ""
-#define OSPATHURL "/"
-#define OSPATHSEP ':'
-#define OS_NEWLINE_SEQ "\n"
-
-
/* Defined for Gargoyle. */
#define HAVE_STDINT_
@@ -178,7 +171,7 @@ int osfmode( const char* fname, int follow_links, unsigned long* mode,
#define osfoprt(fname,typ) (fopen((fname),"r"))
/* Open text file for writing. */
-#define osfopwt(fname,typ) (fopen((fname),"w"))
+osfildef *osfopwt(const char *fname, os_filetype_t typ);
/* Open text file for reading and writing, keeping the file's existing
* contents if the file already exists or creating a new file if no
@@ -221,7 +214,7 @@ inline osfildef *osfoprwtb(const char *fname, os_filetype_t typ);
inline bool osfwb(osfildef *fp, void *buf, size_t count);
/* Flush buffered writes to a file. */
-#define osfflush fflush
+inline void osfflush(osfildef *fp);
/* Read bytes from file. */
int osfrb(osfildef *fp, void *buf, size_t count);
diff --git a/engines/glk/tads/tads2/character_map.cpp b/engines/glk/tads/tads2/character_map.cpp
index 91daa86..9191835 100644
--- a/engines/glk/tads/tads2/character_map.cpp
+++ b/engines/glk/tads/tads2/character_map.cpp
@@ -26,6 +26,7 @@
#include "glk/tads/tads2/os.h"
#include "glk/tads/tads2/text_io.h"
#include "glk/tads/osfrobtads.h"
+#include "glk/tads/os_glk.h"
#include "common/algorithm.h"
namespace Glk {
diff --git a/engines/glk/tads/tads2/data.cpp b/engines/glk/tads/tads2/data.cpp
index b19f45f..fe002a3 100644
--- a/engines/glk/tads/tads2/data.cpp
+++ b/engines/glk/tads/tads2/data.cpp
@@ -24,48 +24,49 @@
#include "glk/tads/tads2/lib.h"
#include "glk/tads/tads2/run.h"
#include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/os_glk.h"
#include "common/algorithm.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
-Data::Data(DataType type) : _type(type) {
-}
-
-size_t Data::size() const {
- switch (_type) {
- case DAT_NUMBER:
- return 4; // numbers are in 4-byte lsb-first format
-
- case DAT_OBJECT:
- return 2; // object numbers are in 2-byte lsb-first format
+/* return size of a data value */
+uint datsiz(dattyp typ, void *val)
+{
+ switch(typ)
+ {
+ case DAT_NUMBER:
+ return(4); /* numbers are in 4-byte lsb-first format */
- case DAT_SSTRING:
- case DAT_DSTRING:
- case DAT_LIST:
- return osrp2(_ptr);
+ case DAT_OBJECT:
+ return(2); /* object numbers are in 2-byte lsb-first format */
- case DAT_NIL:
- case DAT_TRUE:
- return 0;
+ case DAT_SSTRING:
+ case DAT_DSTRING:
+ case DAT_LIST:
+ return(osrp2((char *)val));
- case DAT_PROPNUM:
- case DAT_SYN:
- case DAT_FNADDR:
- case DAT_REDIR:
- return 2;
+ case DAT_NIL:
+ case DAT_TRUE:
+ return(0);
- case DAT_TPL:
- // template is counted array of 10-byte entries, plus length byte */
- return 1 + ((*(uchar *)_ptr) * VOCTPLSIZ);
+ case DAT_PROPNUM:
+ case DAT_SYN:
+ case DAT_FNADDR:
+ case DAT_REDIR:
+ return(2);
+
+ case DAT_TPL:
+ /* template is counted array of 10-byte entries, plus length byte */
+ return(1 + ((*(uchar *)val) * VOCTPLSIZ));
- case DAT_TPL2:
- return 1 + ((*(uchar *)_ptr) * VOCTPL2SIZ);
+ case DAT_TPL2:
+ return(1 + ((*(uchar *)val) * VOCTPL2SIZ));
- default:
- return 0;
- }
+ default:
+ return(0);
+ }
}
} // End of namespace TADS2
diff --git a/engines/glk/tads/tads2/data.h b/engines/glk/tads/tads2/data.h
index 8d122c4..e573730 100644
--- a/engines/glk/tads/tads2/data.h
+++ b/engines/glk/tads/tads2/data.h
@@ -47,23 +47,10 @@ enum DataType {
DAT_REDIR = 16, ///< redirection to different object
DAT_TPL2 = 17 ///< new-style template
};
-typedef DataType dattyp;
+typedef int dattyp;
-class Data {
-private:
- DataType _type;
- void *_ptr;
-public:
- /**
- * Constructor
- */
- Data(DataType type);
-
- /**
- * Return the size of the data
- */
- size_t size() const;
-};
+/* determine the size of a piece of data */
+uint datsiz(dattyp typ, void *valptr);
} // End of namespace TADS2
} // End of namespace TADS
diff --git a/engines/glk/tads/tads2/error_handling.h b/engines/glk/tads/tads2/error_handling.h
index a225bbe..ab1f86e 100644
--- a/engines/glk/tads/tads2/error_handling.h
+++ b/engines/glk/tads/tads2/error_handling.h
@@ -67,9 +67,6 @@ namespace Glk {
namespace TADS {
namespace TADS2 {
-// TODO: Clean away use of jmp_buf, since ScummVM doesn't allow it
-struct jmp_buf {};
-
/**
* Maximum length of a facility identifier
*/
@@ -86,7 +83,7 @@ struct errdef {
char errfac[ERRFACMAX+1]; /* facility of current error */
erradef erraav[10]; /* parameters for error */
int erraac; /* count of parameters in argc */
- jmp_buf errbuf; /* jump buffer for current error frame */
+ //jmp_buf errbuf; ScummVM doesn't support using jump buffers
};
#define ERRBUFSIZ 512
@@ -120,7 +117,7 @@ struct errcxdef {
#define ERRBEGIN(ctx) \
{ \
errdef fr_; \
- if ((fr_.errcode = setjmp(fr_.errbuf)) == 0) \
+ if (1 /*(fr_.errcode = setjmp(fr_.errbuf)) == 0 */) \
{ \
fr_.errprv = (ctx)->errcxptr; \
(ctx)->errcxptr = &fr_;
diff --git a/engines/glk/tads/tads2/lib.h b/engines/glk/tads/tads2/lib.h
index 095abfc..a7ed2aa 100644
--- a/engines/glk/tads/tads2/lib.h
+++ b/engines/glk/tads/tads2/lib.h
@@ -76,20 +76,12 @@ typedef uint32 uint32_t;
#define bic(va, bt) ((va) &= ~(bt))
/*
- * noreg/NOREG - use for variables changed in error-protected code that
- * are used in error handling code. Use 'noreg' on the declaration like
- * a storage class qualifier. Use 'NOREG' as an argument call, passing
- * the addresses of all variables declared noreg. For non-ANSI
- * compilers, a routine osnoreg(/o_ void *arg0, ... _o/) must be
- * defined.
+ * noreg/NOREG - was used for variables changed in error-protected code that
+ * are used in error handling code. However, since ScummVM doesn't support
+ * setjmp for portability reasons, the following can be left as blank defines
*/
-#ifdef OSANSI
-# define noreg volatile
-# define NOREG(arglist)
-#else /* OSANSI */
# define noreg
-# define NOREG(arglist) osnoreg arglist ;
-#endif /* OSANSI */
+# define NOREG(arglist)
/*
* Linting directives. You can define these before including this file
@@ -136,27 +128,6 @@ void varused();
#define t_isspace(c) \
(((unsigned char)(c)) <= 127 && Common::isSpace((unsigned char)(c)))
-
-/* round a size to worst-case alignment boundary */
-#define osrndsz(s) (((s)+3) & ~3)
-
-/* round a pointer to worst-case alignment boundary */
-#define osrndpt(p) ((uchar *)((((unsigned long)(p)) + 3) & ~3))
-
-/* read unaligned portable unsigned 2-byte value, returning int */
-#define osrp2(p) READ_LE_UINT16(p)
-
-/* read unaligned portable signed 2-byte value, returning int */
-#define osrp2s(p) READ_LE_INT16(p)
-
-/* write int to unaligned portable 2-byte value */
-#define oswp2(p, i) WRITE_LE_UINT16(p, i)
-#define oswp2s(p, i) WRITE_LE_INT16(p, i)
-
-/* read unaligned portable 4-byte value, returning unsigned long */
-#define osrp4(p) READ_LE_UINT32(p)
-#define osrp4s(p) READ_LE_INT32(p)
-
} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/list.cpp b/engines/glk/tads/tads2/list.cpp
new file mode 100644
index 0000000..88cfad4
--- /dev/null
+++ b/engines/glk/tads/tads2/list.cpp
@@ -0,0 +1,42 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/tads2/list.h"
+#include "glk/tads/tads2/data.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+void lstadv(uchar **lstp, uint *sizp)
+{
+ uint siz;
+
+ siz = datsiz(**lstp, (*lstp) + 1) + 1;
+ assert(siz <= *sizp);
+ *lstp += siz;
+ *sizp -= siz;
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/list.h b/engines/glk/tads/tads2/list.h
new file mode 100644
index 0000000..bb5eb2b
--- /dev/null
+++ b/engines/glk/tads/tads2/list.h
@@ -0,0 +1,47 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/* List definitions
+ *
+ * A TADS run-time list is essentially a packed counted array.
+ * The first thing in a list is a ushort, which specifies the
+ * number of elements in the list. The list elements are then
+ * packed into the list immediately following.
+ */
+
+#ifndef GLK_TADS_TADS2_LIST
+#define GLK_TADS_TADS2_LIST
+
+#include "glk/tads/tads2/lib.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* advance a list pointer/size pair to the next element of a list */
+void lstadv(uchar **lstp, uint *sizp);
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/os.h b/engines/glk/tads/tads2/os.h
index 5dcc83c..666a5cb 100644
--- a/engines/glk/tads/tads2/os.h
+++ b/engines/glk/tads/tads2/os.h
@@ -393,8 +393,7 @@ int os_get_zoneinfo_key(char *buf, size_t buflen);
* timezone's clock settings, name(s), and rules for recurring annual
* changes between standard time and daylight time, if applicable.
*/
-struct os_tzrule_t
-{
+struct os_tzrule_t {
/*
* Day of year, 1-365, NEVER counting Feb 29; set to 0 if not used.
* Corresponds to the "J" format in Unix TZ strings. (Called "Julian
@@ -448,8 +447,7 @@ struct os_tzrule_t
/* time of day, in seconds after midnight (e.g., 2AM is 120 == 2*60*60) */
int time;
};
-struct os_tzinfo_t
-{
+struct os_tzinfo_t {
/*
* The local offset from GMT, in seconds, for standard time and
* daylight time in this zone. These values are positive for zones
diff --git a/engines/glk/tads/tads2/output.cpp b/engines/glk/tads/tads2/output.cpp
new file mode 100644
index 0000000..6aa39b5
--- /dev/null
+++ b/engines/glk/tads/tads2/output.cpp
@@ -0,0 +1,3559 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/tads2/character_map.h"
+#include "glk/tads/tads2/debug.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/memory_cache.h"
+#include "glk/tads/tads2/os.h"
+#include "glk/tads/tads2/run.h"
+#include "glk/tads/tads2/text_io.h"
+#include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/os_glk.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* Forward declarations */
+struct out_stream_info;
+
+/*
+ * use our own isxxx - anything outside the US ASCII range is not reliably
+ * classifiable by the normal C isxxx routines
+ */
+#define outissp(c) (((uchar)(c)) <= 127 && Common::isSpace((uchar)(c)))
+#define outisal(c) (((uchar)(c)) <= 127 && Common::isAlpha((uchar)(c)))
+#define outisdg(c) (((uchar)(c)) <= 127 && Common::isDigit((uchar)(c)))
+#define outisup(c) (((uchar)(c)) <= 127 && Common::isUpper((uchar)(c)))
+#define outislo(c) (((uchar)(c)) <= 127 && Common::isLower((uchar)(c)))
+
+
+/*
+ * Turn on formatter-level MORE mode, EXCEPT under any of the following
+ * conditions:
+ *
+ * - this is a MAC OS port
+ *. - this is an HTML TADS interpreter
+ *. - USE_OS_LINEWRAP is defined
+ *
+ * Formatter-level MORE mode and formatter-level line wrapping go together;
+ * you can't have one without the other. So, if USE_OS_LINEWRAP is
+ * defined, we must also use OS-level MORE mode, which means we don't want
+ * formatter-level MORE mode.
+ *
+ * For historical reasons, we check specifically for MAC_OS. This was the
+ * first platform for which OS-level MORE mode and OS-level line wrapping
+ * were invented; at the time, we foolishly failed to anticipate that more
+ * platforms might eventually come along with the same needs, so we coded a
+ * test for MAC_OS rather than some more abstract marker. For
+ * compatibility, we retain this specific test.
+ *
+ * USE_OS_LINEWRAP is intended as the more abstract marker we should
+ * originally have used. A port should #define USE_OS_LINEWRAP in its
+ * system-specific os_xxx.h header to turn on OS-level line wrapping and
+ * OS-level MORE mode. Ports should avoid adding new #ifndef tests for
+ * specific platforms here; we've only retained the MAC_OS test because we
+ * don't want to break the existing MAC_OS port.
+ */
+#ifndef MAC_OS
+# ifndef USE_HTML
+# ifndef USE_OS_LINEWRAP
+# define USE_MORE /* activate formatter-level more-mode */
+# endif /* USE_OS_LINEWRAP */
+# endif /* USE_HTML */
+#endif /* MAC_OS */
+
+/*
+ * In HTML mode, don't use MORE mode. Note that we explicitly turn MORE
+ * mode OFF, even though we won't have turned it on above, because it might
+ * have been turned on by an os_xxx.h header. This is here for historical
+ * reasons; in particular, some of the HTML interpreter builds include
+ * headers that were originally written for the normal builds for those
+ * same platforms, and those original headers explicitly #define USE_MORE
+ * somewhere. So, to be absolutely sure we get it right here, we have to
+ * explicitly turn off USE_MORE when compiling for HTML mode.
+ */
+#ifdef USE_HTML
+# ifdef USE_MORE
+# undef USE_MORE
+# endif
+#endif
+
+/*
+ * QSPACE is the special character for a quoted space (internally, the
+ * sequence "\ " (backslash-space) is converted to QSPACE). It must not
+ * be any printable character. The value here may need to be changed in
+ * the extremely unlikely event that TADS is ever ported to an EBCDIC
+ * machine.
+ */
+#define QSPACE 26
+
+/*
+ * QTAB is a special hard tab character indicator. We use this when we
+ * need to generate a hard tab to send to the underlying output layer
+ * (in particular, we use this to send hard tabs to the HTML formatter
+ * when we're in HTML mode).
+ */
+#define QTAB 25
+
+
+/* maximum width of the display */
+#define MAXWIDTH OS_MAXWIDTH
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Globals and statics. These should really be moved into a context
+ * structure, so that the output formatter subsystem could be shared
+ * among multiple clients. For now, there's no practical problem using
+ * statics, because we only need a single output subsystem at one time.
+ */
+
+/* current script (command input) file */
+extern osfildef *scrfp;
+
+/*
+ * This should be TRUE if the output should have two spaces after a
+ * period (or other such punctuation. It should generally be TRUE for
+ * fixed-width fonts, and FALSE for proportional fonts.
+ */
+static int doublespace = 1;
+
+/*
+ * Log file handle and name. If we're copying output to a log file,
+ * these will tell us about the file.
+ */
+osfildef *logfp;
+static char logfname[OSFNMAX];
+
+/* flag indicating whether output has occurred since last check */
+static uchar outcnt;
+
+/* flag indicating whether hidden output has occurred */
+static uchar hidout;
+
+/* flag indicating whether to show (TRUE) or hide (FALSE) output */
+static uchar outflag;
+
+/* flag indicating whether output is hidden for debugging purposes */
+int dbghid;
+
+/*
+ * Current recursion level in formatter invocation
+ */
+static int G_recurse = 0;
+
+/* active stream in current recursion level */
+static out_stream_info *G_cur_stream;
+
+/* watchpoint mode flag */
+static uchar outwxflag;
+
+/*
+ * User filter function. When this function is set, we'll invoke this
+ * function for each string that's displayed through the output
+ * formatter.
+ */
+static objnum G_user_filter = MCMONINV;
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Hack to run with TADS 2.0 with minimal reworking. Rather than using
+ * an allocated output layer context, store our subsystem context
+ * information in some statics. This is less clean than using a real
+ * context, but doesn't create any practical problems as we don't need
+ * to share the output formatter subsystem among multiple simultaneous
+ * callers.
+ */
+static runcxdef *runctx; /* execution context */
+static uchar *fmsbase; /* format string area base */
+static uchar *fmstop; /* format string area top */
+static objnum cmdActor; /* current actor */
+
+/* forward declarations of static functions */
+static void outstring_stream(out_stream_info *stream, char *s);
+static void outchar_noxlat_stream(out_stream_info *stream, char c);
+static char out_parse_entity(char *outbuf, size_t outbuf_size,
+ char **sp, size_t *slenp);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * HTML lexical analysis mode
+ */
+#define HTML_MODE_NORMAL 0 /* normal text, not in a tag */
+#define HTML_MODE_TAG 1 /* parsing inside a tag */
+#define HTML_MODE_SQUOTE 2 /* in a single-quoted string in a tag */
+#define HTML_MODE_DQUOTE 3 /* in a double-quoted string in a tag */
+
+/*
+ * HTML parsing mode flag for <BR> tags. We defer these until we've
+ * read the full tag in order to obey an HEIGHT attribute we find. When
+ * we encounter a <BR>, we figure out whether we think we'll need a
+ * flush or a blank line; if we find a HEIGHT attribute, we may change
+ * this opinion.
+ */
+#define HTML_DEFER_BR_NONE 0 /* no pending <BR> */
+#define HTML_DEFER_BR_FLUSH 1 /* only need an outflush() */
+#define HTML_DEFER_BR_BLANK 2 /* need an outblank() */
+
+/*
+ * If we're compiling for an HTML-enabled underlying output subsystem,
+ * we want to call the underlying OS layer when switching in and out of
+ * HTML mode. If the underlying system doesn't process HTML, we don't
+ * need to let it know anything about HTML mode.
+ */
+#ifdef USE_HTML
+# define out_start_html(stream) os_start_html()
+# define out_end_html(stream) os_end_html()
+#else
+# define out_start_html(stream)
+# define out_end_html(stream)
+#endif
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Output formatter stream state structure. This structure encapsulates
+ * the state of an individual output stream.
+ */
+struct out_stream_info {
+ /* low-level display routine (va_list version) */
+ void (*do_print)(out_stream_info *stream, const char *str);
+
+ /* current line position and output column */
+ uchar linepos;
+ uchar linecol;
+
+ /* number of lines on the screen (since last MORE prompt) */
+ int linecnt;
+
+ /* output buffer */
+ char linebuf[MAXWIDTH];
+
+ /*
+ * attribute buffer - we keep one attribute entry for each character in
+ * the line buffer
+ */
+ int attrbuf[MAXWIDTH];
+
+ /* current attribute for text we're buffering into linebuf */
+ int cur_attr;
+
+ /* last attribute we wrote to the osifc layer */
+ int os_attr;
+
+ /* CAPS mode - next character output is converted to upper-case */
+ uchar capsflag;
+
+ /* NOCAPS mode - next character output is converted to lower-case */
+ uchar nocapsflag;
+
+ /* ALLCAPS mode - all characters output are converted to upper-case */
+ uchar allcapsflag;
+
+ /* capture information */
+ mcmcxdef *capture_ctx; /* memory context to use for capturing */
+ mcmon capture_obj; /* object holding captured output */
+ uint capture_ofs; /* write offset in capture object */
+ int capturing; /* true -> we are capturing output */
+
+ /* "preview" state for line flushing */
+ int preview;
+
+ /* flag indicating that we just flushed a new line */
+ int just_did_nl;
+
+ /* this output stream uses "MORE" mode */
+ int use_more_mode;
+
+ /*
+ * This output stream uses OS-level line wrapping - if this is set,
+ * the output formatter will not insert a newline at the end of a
+ * line that it's flushing for word wrapping, but will instead let
+ * the underlying OS display layer handle the wrapping.
+ */
+ int os_line_wrap;
+
+ /*
+ * Flag indicating that the underlying output system wants to
+ * receive its output as HTML.
+ *
+ * If this is true, we'll pass through HTML to the underlying output
+ * system, and in addition generate HTML sequences for certain
+ * TADS-native escapes (for example, we'll convert the "\n" sequence
+ * to a <BR> sequence).
+ *
+ * If this is false, we'll do just the opposite: we'll remove HTML
+ * from the output stream and convert it into normal text sequences.
+ */
+ int html_target;
+
+ /*
+ * Flag indicating that the target uses plain text. If this flag is
+ * set, we won't add the OS escape codes for highlighted characters.
+ */
+ int plain_text_target;
+
+ /*
+ * Flag indicating that the caller is displaying HTML. We always
+ * start off in text mode; the client can switch to HTML mode by
+ * displaying a special escape sequence, and can switch back to text
+ * mode by displaying a separate special escape sequence.
+ */
+ int html_mode;
+
+ /* current lexical analysis mode */
+ unsigned int html_mode_flag;
+
+ /* <BR> defer mode */
+ unsigned int html_defer_br;
+
+ /*
+ * HTML "ignore" mode - we suppress all output when parsing the
+ * contents of a <TITLE> or <ABOUTBOX> tag
+ */
+ int html_in_ignore;
+
+ /*
+ * HTML <TITLE> mode - when we're in this mode, we're gathering the
+ * title (i.e., we're inside a <TITLE> tag's contents). We'll copy
+ * characters to the title buffer rather than the normal output
+ * buffer, and then call os_set_title() when we reach the </TITLE>
+ * tag.
+ */
+ int html_in_title;
+
+ /* buffer for the title */
+ char html_title_buf[256];
+
+ /* pointer to next available character in title buffer */
+ char *html_title_ptr;
+
+ /* quoting level */
+ int html_quote_level;
+
+ /* PRE nesting level */
+ int html_pre_level;
+
+ /*
+ * Parsing mode flag for ALT attributes. If we're parsing a tag
+ * that allows ALT, such as IMG or SOUND, we'll set this flag, then
+ * insert the ALT text if we encounter it during parsing.
+ */
+ int html_allow_alt;
+};
+
+/*
+ * Default output converter. This is the output converter for the
+ * standard display. Functions in the public interface that do not
+ * specify an output converter will use this converter by default.
+ */
+static out_stream_info G_std_disp;
+
+/*
+ * Log file converter. This is the output converter for a log file.
+ * Whenever we open a log file, we'll initialize this converter; as we
+ * display text to the main display, we'll also copy it to the log file.
+ *
+ * We maintain an entire separate conversion context for the log file,
+ * so that we can perform a different set of conversions on it. We may
+ * want, for example, to pass HTML text through to the OS display
+ * subsystem (this is the case for the HTML-enabled interpreter), but
+ * we'll still want to convert log file output to text. By keeping a
+ * separate display context for the log file, we can format output to
+ * the log file using an entirely different style than we do for the
+ * display.
+ */
+static out_stream_info G_log_disp;
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * low-level output handlers for the standard display and log file
+ */
+
+/* standard display printer */
+static void do_std_print(out_stream_info *stream, const char *str)
+{
+ VARUSED(stream);
+
+ /* display the text through the OS layer */
+ os_printz(str);
+}
+
+/* log file printer */
+static void do_log_print(out_stream_info *stream, const char *str)
+{
+ VARUSED(stream);
+
+ /* display to the log file */
+ if (logfp != 0 && G_os_moremode)
+ {
+ os_fprintz(logfp, str);
+ osfflush(logfp);
+ }
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * initialize a generic output formatter state structure
+ */
+static void out_state_init(out_stream_info *stream)
+{
+ /* start out at the first column */
+ stream->linepos = 0;
+ stream->linecol = 0;
+ stream->linebuf[0] = '\0';
+
+ /* set normal text attributes */
+ stream->cur_attr = 0;
+ stream->os_attr = 0;
+
+ /* start out at the first line */
+ stream->linecnt = 0;
+
+ /* we're not in either "caps", "nocaps", or "allcaps" mode yet */
+ stream->capsflag = stream->nocapsflag = stream->allcapsflag = FALSE;
+
+ /* we're not capturing yet */
+ stream->capturing = FALSE;
+ stream->capture_obj = MCMONINV;
+
+ /* we aren't previewing a line yet */
+ stream->preview = 0;
+
+ /* we haven't flushed a new line yet */
+ stream->just_did_nl = FALSE;
+
+ /* presume this stream does not use "MORE" mode */
+ stream->use_more_mode = FALSE;
+
+ /* presume this stream uses formatter-level line wrapping */
+ stream->os_line_wrap = FALSE;
+
+ /* assume that the underlying system is not HTML-enabled */
+ stream->html_target = FALSE;
+
+ /* presume this target accepts OS highlighting sequences */
+ stream->plain_text_target = FALSE;
+
+ /* start out in text mode */
+ stream->html_mode = FALSE;
+
+ /* start out in "normal" lexical state */
+ stream->html_mode_flag = HTML_MODE_NORMAL;
+
+ /* not in an ignored tag yet */
+ stream->html_in_ignore = FALSE;
+
+ /* not in title mode yet */
+ stream->html_in_title = FALSE;
+
+ /* not yet deferring line breaks */
+ stream->html_defer_br = HTML_DEFER_BR_NONE;
+
+ /* not yet in quotes */
+ stream->html_quote_level = 0;
+
+ /* not yet in a PRE block */
+ stream->html_pre_level = 0;
+
+ /* not in an ALT tag yet */
+ stream->html_allow_alt = FALSE;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * initialize a standard display stream
+ */
+static void out_init_std(out_stream_info *stream)
+{
+ /* there's no user output filter function yet */
+ out_set_filter(MCMONINV);
+
+ /* initialize the basic stream state */
+ out_state_init(stream);
+
+ /* set up the low-level output routine */
+ G_std_disp.do_print = do_std_print;
+
+#ifdef USE_MORE
+ /*
+ * We're compiled for MORE mode, and we're not compiling for an
+ * underlying HTML formatting layer, so use MORE mode for the
+ * standard display stream.
+ */
+ stream->use_more_mode = TRUE;
+#else
+ /*
+ * We're compiled for OS-layer (or HTML-layer) MORE handling. For
+ * this case, use OS-layer (or HTML-layer) line wrapping as well.
+ */
+ stream->os_line_wrap = TRUE;
+#endif
+
+#ifdef USE_HTML
+ /*
+ * if we're compiled for HTML mode, set the standard output stream
+ * so that it knows it has an HTML target - this will ensure that
+ * HTML tags are passed through to the underlying stream, and that
+ * we generate HTML equivalents for our own control sequences
+ */
+ stream->html_target = TRUE;
+#endif
+}
+
+/*
+ * initialize a standard log file stream
+ */
+static void out_init_log(out_stream_info *stream)
+{
+ /* initialize the basic stream state */
+ out_state_init(stream);
+
+ /* set up the low-level output routine */
+ stream->do_print = do_log_print;
+
+ /* use plain text in the log file stream */
+ stream->plain_text_target = TRUE;
+}
+
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * table of '&' character name sequences
+ */
+struct amp_tbl_t {
+ /* entity name */
+ const char *cname;
+
+ /* HTML Unicode character value */
+ uint html_cval;
+
+ /* native character set expansion */
+ char *expan;
+};
+
+/*
+ * HTML entity mapping table. When we're in non-HTML mode, we keep our
+ * own expansion table so that we can map HTML entity names into the
+ * local character set.
+ *
+ * The entries in this table must be in sorted order (by HTML entity
+ * name), because we use a binary search to find an entity name in the
+ * table.
+ */
+static struct amp_tbl_t amp_tbl[] = {
+ { "AElig", 198, 0 },
+ { "Aacute", 193, 0 },
+ { "Abreve", 258, 0 },
+ { "Acirc", 194, 0 },
+ { "Agrave", 192, 0 },
+ { "Alpha", 913, 0 },
+ { "Aogon", 260, 0 },
+ { "Aring", 197, 0 },
+ { "Atilde", 195, 0 },
+ { "Auml", 196, 0 },
+ { "Beta", 914, 0 },
+ { "Cacute", 262, 0 },
+ { "Ccaron", 268, 0 },
+ { "Ccedil", 199, 0 },
+ { "Chi", 935, 0 },
+ { "Dagger", 8225, 0 },
+ { "Dcaron", 270, 0 },
+ { "Delta", 916, 0 },
+ { "Dstrok", 272, 0 },
+ { "ETH", 208, 0 },
+ { "Eacute", 201, 0 },
+ { "Ecaron", 282, 0 },
+ { "Ecirc", 202, 0 },
+ { "Egrave", 200, 0 },
+ { "Eogon", 280, 0 },
+ { "Epsilon", 917, 0 },
+ { "Eta", 919, 0 },
+ { "Euml", 203, 0 },
+ { "Gamma", 915, 0 },
+ { "Iacute", 205, 0 },
+ { "Icirc", 206, 0 },
+ { "Igrave", 204, 0 },
+ { "Iota", 921, 0 },
+ { "Iuml", 207, 0 },
+ { "Kappa", 922, 0 },
+ { "Lacute", 313, 0 },
+ { "Lambda", 923, 0 },
+ { "Lcaron", 317, 0 },
+ { "Lstrok", 321, 0 },
+ { "Mu", 924, 0 },
+ { "Nacute", 323, 0 },
+ { "Ncaron", 327, 0 },
+ { "Ntilde", 209, 0 },
+ { "Nu", 925, 0 },
+ { "OElig", 338, 0 },
+ { "Oacute", 211, 0 },
+ { "Ocirc", 212, 0 },
+ { "Odblac", 336, 0 },
+ { "Ograve", 210, 0 },
+ { "Omega", 937, 0 },
+ { "Omicron", 927, 0 },
+ { "Oslash", 216, 0 },
+ { "Otilde", 213, 0 },
+ { "Ouml", 214, 0 },
+ { "Phi", 934, 0 },
+ { "Pi", 928, 0 },
+ { "Prime", 8243, 0 },
+ { "Psi", 936, 0 },
+ { "Racute", 340, 0 },
+ { "Rcaron", 344, 0 },
+ { "Rho", 929, 0 },
+ { "Sacute", 346, 0 },
+ { "Scaron", 352, 0 },
+ { "Scedil", 350, 0 },
+ { "Sigma", 931, 0 },
+ { "THORN", 222, 0 },
+ { "Tau", 932, 0 },
+ { "Tcaron", 356, 0 },
+ { "Tcedil", 354, 0 },
+ { "Theta", 920, 0 },
+ { "Uacute", 218, 0 },
+ { "Ucirc", 219, 0 },
+ { "Udblac", 368, 0 },
+ { "Ugrave", 217, 0 },
+ { "Upsilon", 933, 0 },
+ { "Uring", 366, 0 },
+ { "Uuml", 220, 0 },
+ { "Xi", 926, 0 },
+ { "Yacute", 221, 0 },
+ { "Yuml", 376, 0 },
+ { "Zacute", 377, 0 },
+ { "Zcaron", 381, 0 },
+ { "Zdot", 379, 0 },
+ { "Zeta", 918, 0 },
+ { "aacute", 225, 0 },
+ { "abreve", 259, 0 },
+ { "acirc", 226, 0 },
+ { "acute", 180, 0 },
+ { "aelig", 230, 0 },
+ { "agrave", 224, 0 },
+ { "alefsym", 8501, 0 },
+ { "alpha", 945, 0 },
+ { "amp", '&', 0 },
+ { "and", 8743, 0 },
+ { "ang", 8736, 0 },
+ { "aogon", 261, 0 },
+ { "aring", 229, 0 },
+ { "asymp", 8776, 0 },
+ { "atilde", 227, 0 },
+ { "auml", 228, 0 },
+ { "bdquo", 8222, 0 },
+ { "beta", 946, 0 },
+ { "breve", 728, 0 },
+ { "brvbar", 166, 0 },
+ { "bull", 8226, 0 },
+ { "cacute", 263, 0 },
+ { "cap", 8745, 0 },
+ { "caron", 711, 0 },
+ { "ccaron", 269, 0 },
+ { "ccedil", 231, 0 },
+ { "cedil", 184, 0 },
+ { "cent", 162, 0 },
+ { "chi", 967, 0 },
+ { "circ", 710, 0 },
+ { "clubs", 9827, 0 },
+ { "cong", 8773, 0 },
+ { "copy", 169, 0 },
+ { "crarr", 8629, 0 },
+ { "cup", 8746, 0 },
+ { "curren", 164, 0 },
+ { "dArr", 8659, 0 },
+ { "dagger", 8224, 0 },
+ { "darr", 8595, 0 },
+ { "dblac", 733, 0 },
+ { "dcaron", 271, 0 },
+ { "deg", 176, 0 },
+ { "delta", 948, 0 },
+ { "diams", 9830, 0 },
+ { "divide", 247, 0 },
+ { "dot", 729, 0 },
+ { "dstrok", 273, 0 },
+ { "eacute", 233, 0 },
+ { "ecaron", 283, 0 },
+ { "ecirc", 234, 0 },
+ { "egrave", 232, 0 },
+ { "emdash", 8212, 0 },
+ { "empty", 8709, 0 },
+ { "endash", 8211, 0 },
+ { "eogon", 281, 0 },
+ { "epsilon", 949, 0 },
+ { "equiv", 8801, 0 },
+ { "eta", 951, 0 },
+ { "eth", 240, 0 },
+ { "euml", 235, 0 },
+ { "exist", 8707, 0 },
+ { "fnof", 402, 0 },
+ { "forall", 8704, 0 },
+ { "frac12", 189, 0 },
+ { "frac14", 188, 0 },
+ { "frac34", 190, 0 },
+ { "frasl", 8260, 0 },
+ { "gamma", 947, 0 },
+ { "ge", 8805, 0 },
+ { "gt", '>', 0 },
+ { "hArr", 8660, 0 },
+ { "harr", 8596, 0 },
+ { "hearts", 9829, 0 },
+ { "hellip", 8230, 0 },
+ { "iacute", 237, 0 },
+ { "icirc", 238, 0 },
+ { "iexcl", 161, 0 },
+ { "igrave", 236, 0 },
+ { "image", 8465, 0 },
+ { "infin", 8734, 0 },
+ { "int", 8747, 0 },
+ { "iota", 953, 0 },
+ { "iquest", 191, 0 },
+ { "isin", 8712, 0 },
+ { "iuml", 239, 0 },
+ { "kappa", 954, 0 },
+ { "lArr", 8656, 0 },
+ { "lacute", 314, 0 },
+ { "lambda", 955, 0 },
+ { "lang", 9001, 0 },
+ { "laquo", 171, 0 },
+ { "larr", 8592, 0 },
+ { "lcaron", 318, 0 },
+ { "lceil", 8968, 0 },
+ { "ldq", 8220, 0 },
+ { "ldquo", 8220, 0 },
+ { "le", 8804, 0 },
+ { "lfloor", 8970, 0 },
+ { "lowast", 8727, 0 },
+ { "loz", 9674, 0 },
+ { "lsaquo", 8249, 0 },
+ { "lsq", 8216, 0 },
+ { "lsquo", 8216, 0 },
+ { "lstrok", 322, 0 },
+ { "lt", '<', 0 },
+ { "macr", 175, 0 },
+ { "mdash", 8212, 0 },
+ { "micro", 181, 0 },
+ { "middot", 183, 0 },
+ { "minus", 8722, 0 },
+ { "mu", 956, 0 },
+ { "nabla", 8711, 0 },
+ { "nacute", 324, 0 },
+ { "nbsp", QSPACE, 0 },
+ { "ncaron", 328, 0 },
+ { "ndash", 8211, 0 },
+ { "ne", 8800, 0 },
+ { "ni", 8715, 0 },
+ { "not", 172, 0 },
+ { "notin", 8713, 0 },
+ { "nsub", 8836, 0 },
+ { "ntilde", 241, 0 },
+ { "nu", 957, 0 },
+ { "oacute", 243, 0 },
+ { "ocirc", 244, 0 },
+ { "odblac", 337, 0 },
+ { "oelig", 339, 0 },
+ { "ogon", 731, 0 },
+ { "ograve", 242, 0 },
+ { "oline", 8254, 0 },
+ { "omega", 969, 0 },
+ { "omicron", 959, 0 },
+ { "oplus", 8853, 0 },
+ { "or", 8744, 0 },
+ { "ordf", 170, 0 },
+ { "ordm", 186, 0 },
+ { "oslash", 248, 0 },
+ { "otilde", 245, 0 },
+ { "otimes", 8855, 0 },
+ { "ouml", 246, 0 },
+ { "para", 182, 0 },
+ { "part", 8706, 0 },
+ { "permil", 8240, 0 },
+ { "perp", 8869, 0 },
+ { "phi", 966, 0 },
+ { "pi", 960, 0 },
+ { "piv", 982, 0 },
+ { "plusmn", 177, 0 },
+ { "pound", 163, 0 },
+ { "prime", 8242, 0 },
+ { "prod", 8719, 0 },
+ { "prop", 8733, 0 },
+ { "psi", 968, 0 },
+ { "quot", '"', 0 },
+ { "rArr", 8658, 0 },
+ { "racute", 341, 0 },
+ { "radic", 8730, 0 },
+ { "rang", 9002, 0 },
+ { "raquo", 187, 0 },
+ { "rarr", 8594, 0 },
+ { "rcaron", 345, 0 },
+ { "rceil", 8969, 0 },
+ { "rdq", 8221, 0 },
+ { "rdquo", 8221, 0 },
+ { "real", 8476, 0 },
+ { "reg", 174, 0 },
+ { "rfloor", 8971, 0 },
+ { "rho", 961, 0 },
+ { "rsaquo", 8250, 0 },
+ { "rsq", 8217, 0 },
+ { "rsquo", 8217, 0 },
+ { "sacute", 347, 0 },
+ { "sbquo", 8218, 0 },
+ { "scaron", 353, 0 },
+ { "scedil", 351, 0 },
+ { "sdot", 8901, 0 },
+ { "sect", 167, 0 },
+ { "shy", 173, 0 },
+ { "sigma", 963, 0 },
+ { "sigmaf", 962, 0 },
+ { "sim", 8764, 0 },
+ { "spades", 9824, 0 },
+ { "sub", 8834, 0 },
+ { "sube", 8838, 0 },
+ { "sum", 8721, 0 },
+ { "sup", 8835, 0 },
+ { "sup1", 185, 0 },
+ { "sup2", 178, 0 },
+ { "sup3", 179, 0 },
+ { "supe", 8839, 0 },
+ { "szlig", 223, 0 },
+ { "tau", 964, 0 },
+ { "tcaron", 357, 0 },
+ { "tcedil", 355, 0 },
+ { "there4", 8756, 0 },
+ { "theta", 952, 0 },
+ { "thetasym", 977, 0 },
+ { "thorn", 254, 0 },
+ { "thorn", 254, 0 },
+ { "tilde", 732, 0 },
+ { "times", 215, 0 },
+ { "trade", 8482, 0 },
+ { "uArr", 8657, 0 },
+ { "uacute", 250, 0 },
+ { "uarr", 8593, 0 },
+ { "ucirc", 251, 0 },
+ { "udblac", 369, 0 },
+ { "ugrave", 249, 0 },
+ { "uml", 168, 0 },
+ { "upsih", 978, 0 },
+ { "upsilon", 965, 0 },
+ { "uring", 367, 0 },
+ { "uuml", 252, 0 },
+ { "weierp", 8472, 0 },
+ { "xi", 958, 0 },
+ { "yacute", 253, 0 },
+ { "yen", 165, 0 },
+ { "yuml", 255, 0 },
+ { "zacute", 378, 0 },
+ { "zcaron", 382, 0 },
+ { "zdot", 380, 0 },
+ { "zeta", 950, 0 }
+};
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * turn on CAPS mode for a stream
+ */
+static void outcaps_stream(out_stream_info *stream)
+{
+ /* turn on CAPS mode */
+ stream->capsflag = TRUE;
+
+ /* turn off NOCAPS and ALLCAPS mode */
+ stream->nocapsflag = FALSE;
+ stream->allcapsflag = FALSE;
+}
+
+/*
+ * turn on NOCAPS mode for a stream
+ */
+static void outnocaps_stream(out_stream_info *stream)
+{
+ /* turn on NOCAPS mode */
+ stream->nocapsflag = TRUE;
+
+ /* turn off CAPS and ALLCAPS mode */
+ stream->capsflag = FALSE;
+ stream->allcapsflag = FALSE;
+}
+
+/*
+ * turn on or off ALLCAPS mode for a stream
+ */
+static void outallcaps_stream(out_stream_info *stream, int all_caps)
+{
+ /* set the ALLCAPS flag */
+ stream->allcapsflag = all_caps;
+
+ /* clear the CAPS and NOCAPS flags */
+ stream->capsflag = FALSE;
+ stream->nocapsflag = FALSE;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * write a string to a stream
+ */
+static void stream_print(out_stream_info *stream, char *str)
+{
+ /* call the stream's do_print method */
+ (*stream->do_print)(stream, str);
+}
+
+/*
+ * Write out a line
+ */
+static void t_outline(out_stream_info *stream, int nl,
+ const char *txt, const int *attr)
+{
+ extern int scrquiet;
+
+ /*
+ * Check the "script quiet" mode - this indicates that we're reading
+ * a script and not echoing output to the display. If this mode is
+ * on, and we're writing to the display, suppress this write. If
+ * the mode is off, or we're writing to another stream (such as the
+ * log file), show the output as normal.
+ */
+ if (!scrquiet || stream != &G_std_disp)
+ {
+ size_t i;
+ char buf[MAXWIDTH];
+ char *dst;
+
+ /*
+ * Check to see if we've reached the end of the screen, and if
+ * so run the MORE prompt. Note that we don't make this check
+ * at all if USE_MORE is undefined, since this means that the OS
+ * layer code is taking responsibility for pagination issues.
+ * We also don't display a MORE prompt when reading from a
+ * script file.
+ *
+ * Note that we suppress the MORE prompt if nl == 0, since this
+ * is used to flush a partial line of text without starting a
+ * new line (for example, when displaying a prompt where the
+ * input will appear on the same line following the prompt).
+ *
+ * Skip the MORE prompt if this stream doesn't use it.
+ */
+ if (stream->use_more_mode
+ && scrfp == 0
+ && G_os_moremode
+ && nl != 0 && nl != 4
+ && stream->linecnt++ >= G_os_pagelength)
+ {
+ /* display the MORE prompt */
+ out_more_prompt();
+ }
+
+ /*
+ * Display the text. Run through the text in pieces; each time the
+ * attributes change, set attributes at the osifc level.
+ */
+ for (i = 0, dst = buf ; txt[i] != '\0' ; ++i)
+ {
+ /* if the attribute is changing, notify osifc */
+ if (attr != 0 && attr[i] != stream->os_attr)
+ {
+ /* flush the preceding text */
+ if (dst != buf)
+ {
+ *dst = '\0';
+ stream_print(stream, buf);
+ }
+
+ /* set the new attribute */
+ os_set_text_attr(attr[i]);
+
+ /* remember this as the last OS attribute */
+ stream->os_attr = attr[i];
+
+ /* start with a fresh buffer */
+ dst = buf;
+ }
+
+ /* buffer this character */
+ *dst++ = txt[i];
+ }
+
+ /* flush the last chunk of text */
+ if (dst != buf)
+ {
+ *dst = '\0';
+ stream_print(stream, buf);
+ }
+ }
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Flush the current line to the display. The 'nl' argument specifies
+ * what kind of flushing to do:
+ *
+ * 0: flush the current line but do not start a new line; more text will
+ * follow on the current line. This is used, for example, to flush text
+ * after displaying a prompt and before waiting for user input.
+ *
+ * 1: flush the line and start a new line.
+ *
+ * 2: flush the line as though starting a new line, but don't add an
+ * actual newline character to the output, since the underlying OS
+ * display code will handle this. Instead, add a space after the line.
+ * (This differs from mode 0 in that mode 0 shouldn't add anything at
+ * all after the line.)
+ *
+ * 3: "preview" mode. Flush the line, but do not start a new line, and
+ * retain the current text in the buffer. This is used for systems that
+ * handle the line wrapping in the underlying system code to flush a
+ * partially filled line that will need to be flushed again later.
+ *
+ * 4: same as mode 0, but used for internal buffer flushes only. Do not
+ * involve the underlying OS layer in this type of flush - simply flush
+ * our buffers with no separation.
+ */
+
+/* flush a given output stream */
+static void outflushn_stream(out_stream_info *stream, int nl)
+{
+ int i;
+
+ /* null-terminate the current output line buffer */
+ stream->linebuf[stream->linepos] = '\0';
+
+ /* note the position of the last character to display */
+ i = stream->linepos - 1;
+
+ /* if we're adding anything, remove trailing spaces */
+ if (nl != 0 && nl != 4)
+ {
+ /* look for last non-space character */
+ for ( ; i >= 0 && outissp(stream->linebuf[i]) ; --i) ;
+ }
+
+ /* check the output mode */
+ if (nl == 3)
+ {
+ /*
+ * this is the special "preview" mode -- only display the part
+ * that we haven't already previewed for this same line
+ */
+ if (i + 1 > stream->preview)
+ {
+ /* write out the line */
+ t_outline(stream, 0, &stream->linebuf[stream->preview],
+ &stream->attrbuf[stream->preview]);
+
+ /* skip past the part we wrote */
+ stream->preview += strlen(&stream->linebuf[stream->preview]);
+ }
+ }
+ else
+ {
+ char *suffix = nullptr; /* extra text to add after the flushed text */
+ int countnl = 0; /* true if line counts for [more] paging */
+
+ /* null-terminate the buffer at the current position */
+ stream->linebuf[++i] = '\0';
+
+ /* check the mode */
+ switch(nl)
+ {
+ case 0:
+ case 3:
+ case 4:
+ /* no newline - just flush out what we have with no suffix */
+ suffix = 0;
+ break;
+
+ case 1:
+ /*
+ * Add a newline. If there's nothing in the current line,
+ * or we just wrote out a newline, do not add an extra
+ * newline. Keep all newlines in PRE mode.
+ */
+ if (stream->linecol != 0 || !stream->just_did_nl
+ || stream->html_pre_level != 0)
+ {
+ /* add a newline after the text */
+ suffix = "\n";
+
+ /* count the line in the page size */
+ countnl = 1;
+ }
+ else
+ {
+ /* don't add a newline */
+ suffix = 0;
+ }
+ break;
+
+ case 2:
+ /*
+ * we're going to depend on the underlying OS output layer
+ * to do line breaking, so don't add a newline, but do add a
+ * space, so that the underlying OS layer knows we have a
+ * word break here
+ */
+ suffix = " ";
+ break;
+ }
+
+ /*
+ * display the line, as long as we have something buffered to
+ * display; even if we don't, display it if our column is
+ * non-zero and we didn't just do a newline, since this must
+ * mean that we've flushed a partial line and are just now doing
+ * the newline
+ */
+ if (stream->linebuf[stream->preview] != '\0'
+ || (stream->linecol != 0 && !stream->just_did_nl)
+ || stream->html_pre_level > 0)
+ {
+ /* write it out */
+ t_outline(stream, countnl, &stream->linebuf[stream->preview],
+ &stream->attrbuf[stream->preview]);
+
+ /* write the suffix, if any */
+ if (suffix != 0)
+ t_outline(stream, 0, suffix, 0);
+ }
+
+ /* generate an HTML line break if necessary */
+ if (nl == 1 && stream->html_mode && stream->html_target)
+ t_outline(stream, 0, "<BR HEIGHT=0>", 0);
+
+ if (nl == 0)
+ {
+ /* we're not displaying a newline, so flush what we have */
+ os_flush();
+ }
+ else
+ {
+ /* we displayed a newline, so reset the column position */
+ stream->linecol = 0;
+ }
+
+ /* reset the line output buffer position */
+ stream->linepos = stream->preview = 0;
+
+ /*
+ * If we just output a newline, note it. If we didn't just
+ * output a newline, but we did write out anything else, note
+ * that we're no longer at the start of a line on the underlying
+ * output device.
+ */
+ if (nl == 1)
+ stream->just_did_nl = TRUE;
+ else if (stream->linebuf[stream->preview] != '\0')
+ stream->just_did_nl = FALSE;
+ }
+
+ /*
+ * If the osifc-level attributes don't match the current attributes,
+ * bring the osifc layer up to date. This is necessary in cases where
+ * we set attributes immediately before asking for input - we
+ * essentially need to flush the attributes without flushing any text.
+ */
+ if (stream->cur_attr != stream->os_attr)
+ {
+ /* set the osifc attributes */
+ os_set_text_attr(stream->cur_attr);
+
+ /* remember the new attributes as the current osifc attributes */
+ stream->os_attr = stream->cur_attr;
+ }
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Determine if we're showing output. Returns true if output should be
+ * displayed, false if it should be suppressed. We'll note the output
+ * for hidden display accounting as needed.
+ */
+static int out_is_hidden()
+{
+ /* check the output flag */
+ if (!outflag)
+ {
+ /* trace the hidden output if desired */
+ if (dbghid && !hidout)
+ trchid();
+
+ /* note the hidden output */
+ hidout = 1;
+
+ /*
+ * unless we're showing hidden text in the debugger, we're
+ * suppressing output, so return true
+ */
+ if (!dbghid)
+ return TRUE;
+ }
+
+ /* we're not suppressing output */
+ return FALSE;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Display a blank line to the given stream
+ */
+static void outblank_stream(out_stream_info *stream)
+{
+ /* flush the stream */
+ outflushn_stream(stream, 1);
+
+ /* if generating for an HTML display target, add an HTML line break */
+ if (stream->html_mode && stream->html_target)
+ outstring_stream(stream, "<BR>");
+
+ /* write out the newline */
+ t_outline(stream, 1, "\n", 0);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Generate a tab for a "\t" sequence in the game text.
+ *
+ * Standard (non-HTML) version: we'll generate enough spaces to take us
+ * to the next tab stop.
+ *
+ * HTML version: if we're in native HTML mode, we'll just generate a
+ * <TAB MULTIPLE=4>; if we're not in HTML mode, we'll generate a hard
+ * tab character, which the HTML formatter will interpret as a <TAB
+ * MULTIPLE=4>.
+ */
+static void outtab_stream(out_stream_info *stream)
+{
+ /* check to see what the underlying system is expecting */
+ if (stream->html_target)
+ {
+ /* the underlying system is HTML - check for HTML mode */
+ if (stream->html_mode)
+ {
+ /* we're in HTML mode, so use the HTML <TAB> tag */
+ outstring_stream(stream, "<TAB MULTIPLE=4>");
+ }
+ else
+ {
+ /* we're not in HTML mode, so generate a hard tab character */
+ outchar_noxlat_stream(stream, QTAB);
+ }
+ }
+ else
+ {
+ int maxcol;
+
+ /*
+ * We're not in HTML mode - expand the tab with spaces. Figure
+ * the maximum column: if we're doing our own line wrapping, never
+ * go beyond the actual display width.
+ */
+ maxcol = (stream->os_line_wrap ? OS_MAXWIDTH : G_os_linewidth);
+
+ /* add the spaces */
+ do
+ {
+ stream->attrbuf[stream->linepos] = stream->cur_attr;
+ stream->linebuf[stream->linepos++] = ' ';
+ ++(stream->linecol);
+ } while (((stream->linecol + 1) & 3) != 0
+ && stream->linecol < maxcol);
+ }
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Flush a line
+ */
+static void out_flushline(out_stream_info *stream, int padding)
+{
+ /*
+ * check to see if we're using the underlying display layer's line
+ * wrapping
+ */
+ if (stream->os_line_wrap)
+ {
+ /*
+ * In the HTML version, we don't need the normal *MORE*
+ * processing, since the HTML layer will handle that.
+ * Furthermore, we don't need to provide actual newline breaks
+ * -- that happens after the HTML is parsed, so we don't have
+ * enough information here to figure out actual line breaks.
+ * So, we'll just flush out our buffer whenever it fills up, and
+ * suppress newlines.
+ *
+ * Similarly, if we have OS-level MORE processing, don't try to
+ * figure out where the line breaks go -- just flush our buffer
+ * without a trailing newline whenever the buffer is full, and
+ * let the OS layer worry about formatting lines and paragraphs.
+ *
+ * If we're using padding, use mode 2. If we don't want padding
+ * (which is the case if we completely fill up the buffer
+ * without finding any word breaks), write out in mode 0, which
+ * just flushes the buffer exactly like it is.
+ */
+ outflushn_stream(stream, padding ? 2 : 4);
+ }
+ else
+ {
+ /*
+ * Normal mode - we process the *MORE* prompt ourselves, and we
+ * are responsible for figuring out where the actual line breaks
+ * go. Use outflush() to generate an actual newline whenever we
+ * flush out our buffer.
+ */
+ outflushn_stream(stream, 1);
+ }
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Write a character to an output stream without translation
+ */
+static void outchar_noxlat_stream(out_stream_info *stream, char c)
+{
+ int i;
+ int qspace;
+
+ /* check for the special quoted space character */
+ if (c == QSPACE)
+ {
+ /* it's a quoted space - note it and convert it to a regular space */
+ qspace = 1;
+ c = ' ';
+ }
+ else if (c == QTAB)
+ {
+ /* it's a hard tab - convert it to an ordinary tab */
+ c = '\t';
+ qspace = 0;
+ }
+ else
+ {
+ /* translate any whitespace character to a regular space character */
+ if (outissp(c))
+ c = ' ';
+
+ /* it's not a quoted space */
+ qspace = 0;
+ }
+
+ /* check for the caps/nocaps flags */
+ if ((stream->capsflag || stream->allcapsflag) && outisal(c))
+ {
+ /* capsflag is set, so capitalize this character */
+ if (outislo(c))
+ c = toupper(c);
+
+ /* okay, we've capitalized something; clear flag */
+ stream->capsflag = 0;
+ }
+ else if (stream->nocapsflag && outisal(c))
+ {
+ /* nocapsflag is set, so minisculize this character */
+ if (outisup(c))
+ c = tolower(c);
+
+ /* clear the flag now that we've done the job */
+ stream->nocapsflag = 0;
+ }
+
+ /* if in capture mode, simply capture the character */
+ if (stream->capturing)
+ {
+ uchar *p;
+
+ /* if we have a valid capture object, copy to it */
+ if (stream->capture_obj != MCMONINV)
+ {
+ /* lock the object holding the captured text */
+ p = mcmlck(stream->capture_ctx, stream->capture_obj);
+
+ /* make sure the capture object is big enough */
+ if (mcmobjsiz(stream->capture_ctx, stream->capture_obj)
+ <= stream->capture_ofs)
+ {
+ /* expand the object by another 256 bytes */
+ p = mcmrealo(stream->capture_ctx, stream->capture_obj,
+ (ushort)(stream->capture_ofs + 256));
+ }
+
+ /* add this character */
+ *(p + stream->capture_ofs++) = c;
+
+ /* unlock the capture object */
+ mcmtch(stream->capture_ctx, stream->capture_obj);
+ mcmunlck(stream->capture_ctx, stream->capture_obj);
+ }
+
+ /*
+ * we're done - we don't want to actually display the character
+ * while capturing
+ */
+ return;
+ }
+
+ /* add the character to out output buffer, flushing as needed */
+ if (stream->linecol + 1 < G_os_linewidth)
+ {
+ /*
+ * there's room for this character, so add it to the buffer
+ */
+
+ /* ignore non-quoted space at start of line outside of PRE */
+ if (outissp(c) && c != '\t' && stream->linecol == 0 && !qspace
+ && stream->html_pre_level == 0)
+ return;
+
+ /* is this a non-quoted space not at the start of the line? */
+ if (outissp(c) && c != '\t' && stream->linecol != 0 && !qspace
+ && stream->html_pre_level == 0)
+ {
+ int pos1 = stream->linepos - 1;
+ char p = stream->linebuf[pos1]; /* check previous character */
+
+ /* ignore repeated spaces - collapse into a single space */
+ if (outissp(p))
+ return;
+
+ /*
+ * Certain punctuation requires a double space: a period, a
+ * question mark, an exclamation mark, or a colon; or any of
+ * these characters followed by any number of single and/or
+ * double quotes. First, scan back to before any quotes, if
+ * are on one now, then check the preceding character; if
+ * it's one of the punctuation marks requiring a double
+ * space, add this space a second time. (In addition to
+ * scanning back past quotes, scan past parentheses,
+ * brackets, and braces.) Don't double the spacing if we're
+ * not in the normal doublespace mode; some people may
+ * prefer single spacing after punctuation, so we make this
+ * a run-time option.
+ */
+ if (doublespace)
+ {
+ /* find the previous relevant punctuation character */
+ while (pos1 &&
+ (p == '"' || p == '\'' || p == ')' || p == ']'
+ || p == '}'))
+ {
+ p = stream->linebuf[--pos1];
+ }
+ if ( p == '.' || p == '?' || p == '!' || p == ':' )
+ {
+ /* a double-space is required after this character */
+ stream->attrbuf[stream->linepos] = stream->cur_attr;
+ stream->linebuf[stream->linepos++] = c;
+ ++(stream->linecol);
+ }
+ }
+ }
+
+ /* add this character to the buffer */
+ stream->attrbuf[stream->linepos] = stream->cur_attr;
+ stream->linebuf[stream->linepos++] = c;
+
+ /* advance the output column position */
+ ++(stream->linecol);
+ return;
+ }
+
+ /*
+ * The line would overflow if this character were added. Find the
+ * most recent word break, and output the line up to the previous
+ * word. Note that if we're trying to output a space, we'll just
+ * add it to the line buffer. If the last character of the line
+ * buffer is already a space, we won't do anything right now.
+ */
+ if (outissp(c) && c != '\t' && !qspace)
+ {
+ /* this is a space, so we're at a word break */
+ if (stream->linebuf[stream->linepos - 1] != ' ')
+ {
+ stream->attrbuf[stream->linepos] = stream->cur_attr;
+ stream->linebuf[stream->linepos++] = ' ';
+ }
+ return;
+ }
+
+ /*
+ * Find the most recent word break: look for a space or dash, starting
+ * at the end of the line.
+ *
+ * If we're about to write a hyphen, we want to skip all contiguous
+ * hyphens, because we want to keep them together as a single
+ * punctuation mark; then keep going in the normal manner, which will
+ * keep the hyphens plus the word they're attached to together as a
+ * single unit. If spaces precede the sequence of hyphens, include
+ * the prior word as well.
+ */
+ i = stream->linepos - 1;
+ if (c == '-')
+ {
+ /* skip any contiguous hyphens at the end of the line */
+ for ( ; i >= 0 && stream->linebuf[i] == '-' ; --i) ;
+
+ /* skip any spaces preceding the sequence of hyphens */
+ for ( ; i >= 0 && outissp(stream->linebuf[i]) ; --i) ;
+ }
+
+ /*
+ * Now find the preceding space. If we're doing our own wrapping
+ * (i.e., we're not using OS line wrapping), then look for the
+ * nearest hyphen as well.
+ */
+ for ( ; i >= 0 && !outissp(stream->linebuf[i])
+ && !(!stream->os_line_wrap && stream->linebuf[i] == '-') ; --i) ;
+
+ /* check to see if we found a good place to break */
+ if (i < 0)
+ {
+ /*
+ * we didn't find any good place to break - flush the entire
+ * line as-is, breaking arbitrarily in the middle of a word
+ */
+ out_flushline(stream, FALSE);
+
+ /*
+ * we've completely cleared out the line buffer, so reset all of
+ * the line buffer counters
+ */
+ stream->linepos = 0;
+ stream->linecol = 0;
+ stream->linebuf[0] = '\0';
+ }
+ else
+ {
+ char brkchar;
+ char tmpbuf[MAXWIDTH];
+ int tmpattr[MAXWIDTH];
+ size_t tmpcnt;
+
+ /* remember the word-break character */
+ brkchar = stream->linebuf[i];
+
+ /* null-terminate the line buffer */
+ stream->linebuf[stream->linepos] = '\0';
+
+ /* the next line starts after the break - save a copy */
+ tmpcnt = strlen(&stream->linebuf[i+1]);
+ memcpy(tmpbuf, &stream->linebuf[i+1], tmpcnt + 1);
+ memcpy(tmpattr, &stream->attrbuf[i+1], tmpcnt * sizeof(tmpattr[0]));
+
+ /*
+ * terminate the buffer at the space or after the hyphen,
+ * depending on where we broke
+ */
+ if (outissp(brkchar))
+ stream->linebuf[i] = '\0';
+ else
+ stream->linebuf[i+1] = '\0';
+
+ /* write out everything up to the word break */
+ out_flushline(stream, TRUE);
+
+ /* copy the next line into line buffer */
+ memcpy(stream->linebuf, tmpbuf, tmpcnt + 1);
+ memcpy(stream->attrbuf, tmpattr, tmpcnt * sizeof(tmpattr[0]));
+ stream->linepos = tmpcnt;
+
+ /*
+ * figure what column we're now in - count all of the printable
+ * characters in the new line
+ */
+ for (stream->linecol = 0, i = 0 ; i < stream->linepos ; ++i)
+ {
+ /* if it's printable, count it */
+ if (((unsigned char)stream->linebuf[i]) >= 26)
+ ++(stream->linecol);
+ }
+ }
+
+ /* add the new character to buffer */
+ stream->attrbuf[stream->linepos] = stream->cur_attr;
+ stream->linebuf[stream->linepos++] = c;
+
+ /* advance the column counter */
+ ++(stream->linecol);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Write out a character, translating to the local system character set
+ * from the game's internal character set.
+ */
+static void outchar_stream(out_stream_info *stream, char c)
+{
+ outchar_noxlat_stream(stream, cmap_i2n(c));
+}
+
+/*
+ * write out a string, translating to the local system character set
+ */
+static void outstring_stream(out_stream_info *stream, char *s)
+{
+ /* write out each character in the string */
+ for ( ; *s ; ++s)
+ outchar_stream(stream, *s);
+}
+
+/*
+ * write out a string without translation
+ */
+static void outstring_noxlat_stream(out_stream_info *stream, char *s)
+{
+ for ( ; *s ; ++s)
+ outchar_noxlat_stream(stream, *s);
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Write out an HTML character value, translating to the local character
+ * set.
+ */
+static void outchar_html_stream(out_stream_info *stream,
+ unsigned int htmlchar)
+{
+ amp_tbl_t *ampptr;
+
+ /*
+ * search for a mapping entry for this entity, in case it's defined
+ * in an external mapping file
+ */
+ for (ampptr = amp_tbl ;
+ ampptr < amp_tbl + sizeof(amp_tbl)/sizeof(amp_tbl[0]) ; ++ampptr)
+ {
+ /* if this is the one, stop looking */
+ if (ampptr->html_cval == htmlchar)
+ break;
+ }
+
+ /*
+ * If we found a mapping table entry, and the entry has an expansion
+ * from the external character mapping table file, use the external
+ * expansion; otherwise, use the default expansion.
+ */
+ if (ampptr >= amp_tbl + sizeof(amp_tbl)/sizeof(amp_tbl[0])
+ || ampptr->expan == 0)
+ {
+ char xlat_buf[50];
+
+ /*
+ * there's no external mapping table file expansion -- use the
+ * default OS mapping routine
+ */
+ os_xlat_html4(htmlchar, xlat_buf, sizeof(xlat_buf));
+ outstring_noxlat_stream(stream, xlat_buf);
+ }
+ else
+ {
+ /*
+ * use the explicit mapping from the mapping table file
+ */
+ outstring_noxlat_stream(stream, ampptr->expan);
+ }
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Enter a recursion level. Returns TRUE if the caller should proceed
+ * with the operation, FALSE if not.
+ *
+ * If we're making a recursive call, thereby re-entering the formatter,
+ * and this stream is not the same as the enclosing stream, we want to
+ * ignore this call and suppress any output to this stream, so we'll
+ * return FALSE.
+ */
+static int out_push_stream(out_stream_info *stream)
+{
+ /*
+ * if we're already in the formatter, and the new stream doesn't
+ * match the enclosing recursion level's stream, tell the caller to
+ * abort the operation
+ */
+ if (G_recurse != 0 && G_cur_stream != stream)
+ return FALSE;
+
+ /* note the active stream */
+ G_cur_stream = stream;
+
+ /* count the entry */
+ ++G_recurse;
+
+ /* tell the caller to proceed */
+ return TRUE;
+}
+
+/*
+ * Leave a recursion level
+ */
+static void out_pop_stream()
+{
+ /* count the exit */
+ --G_recurse;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * nextout() returns the next character in a string, and updates the
+ * string pointer and remaining length. Returns zero if no more
+ * characters are available in the string.
+ */
+/* static char nextout(char **s, uint *len); */
+#define nextout(s, len) ((char)(*(len) == 0 ? 0 : (--(*(len)), *((*(s))++))))
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Get the next character, writing the previous character to the given
+ * output stream if it's not null.
+ */
+static char nextout_copy(char **s, size_t *slen,
+ char prv, out_stream_info *stream)
+{
+ /* if there's a stream, write the previous character to the stream */
+ if (stream != 0)
+ outchar_stream(stream, prv);
+
+ /* return the next character */
+ return nextout(s, slen);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Read an HTML tag, for our primitive mini-parser. If 'stream' is not
+ * null, we'll copy each character we read to the output stream. Returns
+ * the next character after the tag name.
+ */
+static char read_tag(char *dst, size_t dstlen, int *is_end_tag,
+ char **s, size_t *slen, out_stream_info *stream)
+{
+ char c;
+
+ /* skip the opening '<' */
+ c = nextout_copy(s, slen, '<', stream);
+
+ /* skip spaces */
+ while (outissp(c))
+ c = nextout_copy(s, slen, c, stream);
+
+ /* note if this is a closing tag */
+ if (c == '/' || c == '\\')
+ {
+ /* it's an end tag - note it and skip the slash */
+ *is_end_tag = TRUE;
+ c = nextout_copy(s, slen, c, stream);
+
+ /* skip yet more spaces */
+ while (outissp(c))
+ c = nextout_copy(s, slen, c, stream);
+ }
+ else
+ *is_end_tag = FALSE;
+
+ /*
+ * find the end of the tag name - the tag continues to the next space,
+ * '>', or end of line
+ */
+ for ( ; c != '\0' && !outissp(c) && c != '>' ;
+ c = nextout_copy(s, slen, c, stream))
+ {
+ /* add this to the tag buffer if it fits */
+ if (dstlen > 1)
+ {
+ *dst++ = c;
+ --dstlen;
+ }
+ }
+
+ /* null-terminate the tag name */
+ if (dstlen > 0)
+ *dst = '\0';
+
+ /* return the next character */
+ return c;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * display a string of a given length to a given stream
+ */
+static int outformatlen_stream(out_stream_info *stream,
+ char *s, size_t slen)
+{
+ char c;
+ int done = 0;
+ char fmsbuf[40]; /* space for constructing translation string */
+ uint fmslen;
+ char *f = 0;
+ char *f1;
+ int infmt = 0;
+
+ /*
+ * This routine can recurse because of format strings ("%xxx%"
+ * sequences). When we recurse, we want to ensure that the
+ * recursion is directed to the original stream only. So, note the
+ * current stream statically in case we re-enter the formatter.
+ */
+ if (!out_push_stream(stream))
+ return 0;
+
+ /* get the first character */
+ c = nextout(&s, &slen);
+
+ /* if we have anything to show, show it */
+ while (c != '\0')
+ {
+ /* check if we're collecting translation string */
+ if (infmt)
+ {
+ /*
+ * if the string is too long for our buffer, or we've come
+ * across a backslash (illegal in a format string), or we've
+ * come across an HTML-significant character ('&' or '<') in
+ * HTML mode, we must have a stray percent sign; dump the
+ * whole string so far and act as though we have no format
+ * string
+ */
+ if (c == '\\'
+ || f == &fmsbuf[sizeof(fmsbuf)]
+ || (stream->html_mode && (c == '<' || c == '&')))
+ {
+ outchar_stream(stream, '%');
+ for (f1 = fmsbuf ; f1 < f ; ++f1)
+ outchar_stream(stream, *f1);
+ infmt = 0;
+
+ /* process this character again */
+ continue;
+ }
+ else if (c == '%' && f == fmsbuf) /* double percent sign? */
+ {
+ outchar_stream(stream, '%'); /* send out a single '%' */
+ infmt = 0; /* no longer processing translation string */
+ }
+ else if (c == '%') /* found end of string? translate it if so */
+ {
+ uchar *fms;
+ int initcap = FALSE;
+ int allcaps = FALSE;
+ char fmsbuf_srch[sizeof(fmsbuf)];
+
+ /* null-terminate the string */
+ *f = '\0';
+
+ /* check for an init cap */
+ if (outisup(fmsbuf[0]))
+ {
+ /*
+ * note the initial capital, so that we follow the
+ * original capitalization in the substituted string
+ */
+ initcap = TRUE;
+
+ /*
+ * if the second letter is capitalized as well,
+ * capitalize the entire substituted string
+ */
+ if (fmsbuf[1] != '\0' && outisup(fmsbuf[1]))
+ {
+ /* use all caps */
+ allcaps = TRUE;
+ }
+ }
+
+ /* convert the entire string to lower case for searching */
+ strcpy(fmsbuf_srch, fmsbuf);
+ os_strlwr(fmsbuf_srch);
+
+ /* find the string in the format string table */
+ fmslen = strlen(fmsbuf_srch);
+ for (fms = fmsbase ; fms < fmstop ; )
+ {
+ uint propnum;
+ uint len;
+
+ /* get the information on this entry */
+ propnum = osrp2(fms);
+ len = osrp2(fms + 2) - 2;
+
+ /* check for a match */
+ if (len == fmslen &&
+ !memcmp(fms + 4, fmsbuf_srch, (size_t)len))
+ {
+ int old_all_caps;
+
+ /* note the current ALLCAPS mode */
+ old_all_caps = stream->allcapsflag;
+
+ /*
+ * we have a match - set the appropriate
+ * capitalization mode
+ */
+ if (allcaps)
+ outallcaps_stream(stream, TRUE);
+ else if (initcap)
+ outcaps_stream(stream);
+
+ /*
+ * evaluate the associated property to generate
+ * the substitution text
+ */
+ runppr(runctx, cmdActor, (prpnum)propnum, 0);
+
+ /* turn off ALLCAPS mode */
+ outallcaps_stream(stream, old_all_caps);
+
+ /* no need to look any further */
+ break;
+ }
+
+ /* move on to next formatstring if not yet found */
+ fms += len + 4;
+ }
+
+ /* if we can't find it, dump the format string as-is */
+ if (fms == fmstop)
+ {
+ outchar_stream(stream, '%');
+ for (f1 = fmsbuf ; f1 < f ; ++f1)
+ outchar_stream(stream, *f1);
+ outchar_stream(stream, '%');
+ }
+
+ /* no longer reading format string */
+ infmt = 0;
+ }
+ else
+ {
+ /* copy this character of the format string */
+ *f++ = c;
+ }
+
+ /* move on to the next character and continue scanning */
+ c = nextout(&s, &slen);
+ continue;
+ }
+
+ /*
+ * If we're parsing HTML here, and we're inside a tag, skip
+ * characters until we reach the end of the tag.
+ */
+ if (stream->html_mode_flag != HTML_MODE_NORMAL)
+ {
+ switch(stream->html_mode_flag)
+ {
+ case HTML_MODE_TAG:
+ /*
+ * keep skipping up to the closing '>', but note when we
+ * enter any quoted section
+ */
+ switch(c)
+ {
+ case '>':
+ /* we've reached the end of the tag */
+ stream->html_mode_flag = HTML_MODE_NORMAL;
+
+ /* if we have a deferred <BR>, process it now */
+ switch(stream->html_defer_br)
+ {
+ case HTML_DEFER_BR_NONE:
+ /* no deferred <BR> */
+ break;
+
+ case HTML_DEFER_BR_FLUSH:
+ outflushn_stream(stream, 1);
+ break;
+
+ case HTML_DEFER_BR_BLANK:
+ outblank_stream(stream);
+ break;
+ }
+
+ /* no more deferred <BR> pending */
+ stream->html_defer_br = HTML_DEFER_BR_NONE;
+
+ /* no more ALT attribute allowed */
+ stream->html_allow_alt = FALSE;
+ break;
+
+ case '"':
+ /* enter a double-quoted string */
+ stream->html_mode_flag = HTML_MODE_DQUOTE;
+ break;
+
+ case '\'':
+ /* enter a single-quoted string */
+ stream->html_mode_flag = HTML_MODE_SQUOTE;
+ break;
+
+ default:
+ /* if it's alphabetic, note the attribute name */
+ if (outisal(c))
+ {
+ char attrname[128];
+ char attrval[256];
+ char *dst;
+
+ /* gather up the attribute name */
+ for (dst = attrname ;
+ dst + 1 < attrname + sizeof(attrname) ; )
+ {
+ /* store this character */
+ *dst++ = c;
+
+ /* get the next character */
+ c = nextout(&s, &slen);
+
+ /* if it's not alphanumeric, stop scanning */
+ if (!outisal(c) && !outisdg(c))
+ break;
+ }
+
+ /* null-terminate the result */
+ *dst++ = '\0';
+
+ /* gather the value if present */
+ if (c == '=')
+ {
+ char qu;
+
+ /* skip the '=' */
+ c = nextout(&s, &slen);
+
+ /* if we have a quote, so note */
+ if (c == '"' || c == '\'')
+ {
+ /* remember the quote */
+ qu = c;
+
+ /* skip it */
+ c = nextout(&s, &slen);
+ }
+ else
+ {
+ /* no quote */
+ qu = 0;
+ }
+
+ /* read the value */
+ for (dst = attrval ;
+ dst + 1 < attrval + sizeof(attrval) ; )
+ {
+ /* store this character */
+ *dst++ = c;
+
+ /* read the next one */
+ c = nextout(&s, &slen);
+ if (c == '\0')
+ {
+ /*
+ * we've reached the end of the
+ * string, and we're still inside
+ * this attribute - abandon the
+ * attribute but note that we're
+ * inside a quoted string if
+ * necessary
+ */
+ if (qu == '"')
+ stream->html_mode_flag =
+ HTML_MODE_DQUOTE;
+ else if (qu == '\'')
+ stream->html_mode_flag =
+ HTML_MODE_SQUOTE;
+ else
+ stream->html_mode_flag
+ = HTML_MODE_TAG;
+
+ /* stop scanning the string */
+ break;
+ }
+
+ /*
+ * if we're looking for a quote, check
+ * for the closing quote; otherwise,
+ * check for alphanumerics
+ */
+ if (qu != 0)
+ {
+ /* if this is our quote, stop scanning */
+ if (c == qu)
+ break;
+ }
+ else
+ {
+ /* if it's non-alphanumeric, we're done */
+ if (!outisal(c) && !outisdg(c))
+ break;
+ }
+ }
+
+ /* skip the closing quote, if necessary */
+ if (qu != 0 && c == qu)
+ c = nextout(&s, &slen);
+
+ /* null-terminate the value string */
+ *dst = '\0';
+ }
+ else
+ {
+ /* no value */
+ attrval[0] = '\0';
+ }
+
+ /*
+ * see if we recognize it, and it's meaningful
+ * in the context of the current tag
+ */
+ if (!scumm_stricmp(attrname, "height")
+ && stream->html_defer_br != HTML_DEFER_BR_NONE)
+ {
+ int ht;
+
+ /*
+ * If the height is zero, always treat this
+ * as a non-blanking flush. If it's one,
+ * treat it as we originally planned to. If
+ * it's greater than one, add n blank lines.
+ */
+ ht = atoi(attrval);
+ if (ht == 0)
+ {
+ /* always use non-blanking flush */
+ stream->html_defer_br = HTML_DEFER_BR_FLUSH;
+ }
+ else if (ht == 1)
+ {
+ /* keep original setting */
+ }
+ else
+ {
+ for ( ; ht > 0 ; --ht)
+ outblank_stream(stream);
+ }
+ }
+ else if (!scumm_stricmp(attrname, "alt")
+ && !stream->html_in_ignore
+ && stream->html_allow_alt)
+ {
+ /* write out the ALT string */
+ outstring_stream(stream, attrval);
+ }
+
+ /*
+ * since we already read the next character,
+ * simply loop back immediately
+ */
+ continue;
+ }
+ break;
+ }
+ break;
+
+ case HTML_MODE_DQUOTE:
+ /* if we've reached the closing quote, return to tag state */
+ if (c == '"')
+ stream->html_mode_flag = HTML_MODE_TAG;
+ break;
+
+ case HTML_MODE_SQUOTE:
+ /* if we've reached the closing quote, return to tag state */
+ if (c == '\'')
+ stream->html_mode_flag = HTML_MODE_TAG;
+ break;
+ }
+
+ /*
+ * move on to the next character, and start over with the
+ * new character
+ */
+ c = nextout(&s, &slen);
+ continue;
+ }
+
+ /*
+ * If we're in a title, and this isn't the start of a new tag,
+ * skip the character - we suppress all regular text output
+ * inside a <TITLE> ... </TITLE> sequence.
+ */
+ if (stream->html_in_ignore && c != '<')
+ {
+ /* check for entities */
+ char cbuf[50];
+ if (c == '&')
+ {
+ /* translate the entity */
+ c = out_parse_entity(cbuf, sizeof(cbuf), &s, &slen);
+ }
+ else
+ {
+ /* it's an ordinary character - copy it out literally */
+ cbuf[0] = c;
+ cbuf[1] = '\0';
+
+ /* get the next character */
+ c = nextout(&s, &slen);
+ }
+
+ /*
+ * if we're gathering a title, and there's room in the title
+ * buffer for more (always leaving room for a null
+ * terminator), add this to the title buffer
+ */
+ if (stream->html_in_title)
+ {
+ char *cbp;
+ for (cbp = cbuf ; *cbp != '\0' ; ++cbp)
+ {
+ /* if there's room, add it */
+ if (stream->html_title_ptr + 1 <
+ stream->html_title_buf
+ + sizeof(stream->html_title_buf))
+ *stream->html_title_ptr++ = *cbp;
+ }
+ }
+
+ /* don't display anything in an ignore section */
+ continue;
+ }
+
+ if ( c == '%' ) /* translation string? */
+ {
+ infmt = 1;
+ f = fmsbuf;
+ }
+ else if ( c == '\\' ) /* special escape code? */
+ {
+ c = nextout(&s, &slen);
+
+ if (stream->capturing && c != '^' && c != 'v' && c != '\0')
+ {
+ outchar_stream(stream, '\\');
+ outchar_stream(stream, c);
+
+ /* keep the \- and also put out the next two chars */
+ if (c == '-')
+ {
+ outchar_stream(stream, nextout(&s, &slen));
+ outchar_stream(stream, nextout(&s, &slen));
+ }
+ }
+ else
+ {
+ switch(c)
+ {
+ case 'H': /* HTML mode entry */
+ /* turn on HTML mode in the renderer */
+ switch(c = nextout(&s, &slen))
+ {
+ case '-':
+ /* if we have an HTML target, notify it */
+ if (stream->html_target)
+ {
+ /* flush its stream */
+ outflushn_stream(stream, 0);
+
+ /* tell the OS layer to switch to normal mode */
+ out_end_html(stream);
+ }
+
+ /* switch to normal mode */
+ stream->html_mode = FALSE;
+ break;
+
+ case '+':
+ default:
+ /* if we have an HTML target, notify it */
+ if (stream->html_target)
+ {
+ /* flush the underlying stream */
+ outflushn_stream(stream, 0);
+
+ /* tell the OS layer to switch to HTML mode */
+ out_start_html(stream);
+ }
+
+ /* switch to HTML mode */
+ stream->html_mode = TRUE;
+
+ /*
+ * if the character wasn't a "+", it's not part
+ * of the "\H" sequence, so display it normally
+ */
+ if (c != '+' && c != 0)
+ outchar_stream(stream, c);
+ break;
+ }
+
+ /* this sequence doesn't result in any actual output */
+ break;
+
+ case 'n': /* newline? */
+ outflushn_stream(stream, 1); /* yes, output line */
+ break;
+
+ case 't': /* tab? */
+ outtab_stream(stream);
+ break;
+
+ case 'b': /* blank line? */
+ outblank_stream(stream);
+ break;
+
+ case '\0': /* line ends here? */
+ done = 1;
+ break;
+
+ case ' ': /* quoted space */
+ if (stream->html_target && stream->html_mode)
+ {
+ /*
+ * we're generating for an HTML target and we're
+ * in HTML mode - generate the HTML non-breaking
+ * space
+ */
+ outstring_stream(stream, " ");
+ }
+ else
+ {
+ /*
+ * we're not in HTML mode - generate our
+ * internal quoted space character
+ */
+ outchar_stream(stream, QSPACE);
+ }
+ break;
+
+ case '^': /* capitalize next character */
+ stream->capsflag = 1;
+ stream->nocapsflag = 0;
+ break;
+
+ case 'v':
+ stream->nocapsflag = 1;
+ stream->capsflag = 0;
+ break;
+
+ case '(':
+ /* generate HTML if in the appropriate mode */
+ if (stream->html_mode && stream->html_target)
+ {
+ /* send HTML to the renderer */
+ outstring_stream(stream, "<B>");
+ }
+ else
+ {
+ /* turn on the 'hilite' attribute */
+ stream->cur_attr |= OS_ATTR_HILITE;
+ }
+ break;
+
+ case ')':
+ /* generate HTML if in the appropriate mode */
+ if (stream->html_mode && stream->html_target)
+ {
+ /* send HTML to the renderer */
+ outstring_stream(stream, "</B>");
+ }
+ else
+ {
+ /* turn off the 'hilite' attribute */
+ stream->cur_attr &= ~OS_ATTR_HILITE;
+ }
+ break;
+
+ case '-':
+ outchar_stream(stream, nextout(&s, &slen));
+ outchar_stream(stream, nextout(&s, &slen));
+ break;
+
+ default: /* just pass invalid escapes as-is */
+ outchar_stream(stream, c);
+ break;
+ }
+ }
+ }
+ else if (!stream->html_target
+ && stream->html_mode
+ && (c == '<' || c == '&'))
+ {
+ /*
+ * We're in HTML mode, but the underlying target does not
+ * accept HTML sequences. It appears we're at the start of
+ * an "&" entity or a tag sequence, so parse it, remove it,
+ * and replace it (if possible) with a text-only equivalent.
+ */
+ if (c == '<')
+ {
+ /* read the tag */
+ char tagbuf[50];
+ int is_end_tag;
+ c = read_tag(tagbuf, sizeof(tagbuf), &is_end_tag,
+ &s, &slen, 0);
+
+ /*
+ * Check to see if we recognize the tag. We only
+ * recognize a few simple tags that map easily to
+ * character mode.
+ */
+ if (!scumm_stricmp(tagbuf, "br"))
+ {
+ /*
+ * line break - if there's anything buffered up,
+ * just flush the current line, otherwise write out
+ * a blank line
+ */
+ if (stream->html_in_ignore)
+ /* suppress breaks in ignore mode */;
+ else if (stream->linepos != 0)
+ stream->html_defer_br = HTML_DEFER_BR_FLUSH;
+ else
+ stream->html_defer_br = HTML_DEFER_BR_BLANK;
+ }
+ else if (!scumm_stricmp(tagbuf, "b")
+ || !scumm_stricmp(tagbuf, "i")
+ || !scumm_stricmp(tagbuf, "em")
+ || !scumm_stricmp(tagbuf, "strong"))
+ {
+ int attr;
+
+ /* choose the attribute flag */
+ switch (tagbuf[0])
+ {
+ case 'b':
+ case 'B':
+ attr = OS_ATTR_BOLD;
+ break;
+
+ case 'i':
+ case 'I':
+ attr = OS_ATTR_ITALIC;
+ break;
+
+ case 'e':
+ case 'E':
+ attr = OS_ATTR_EM;
+ break;
+
+ case 's':
+ case 'S':
+ attr = OS_ATTR_STRONG;
+ break;
+ }
+
+ /* bold on/off - send out appropriate os-layer code */
+ if (stream->html_in_ignore)
+ {
+ /* suppress any change in 'ignore' mode */
+ }
+ else if (!is_end_tag)
+ {
+ /* turn on the selected attribute */
+ stream->cur_attr |= attr;
+ }
+ else
+ {
+ /* turn off the selected attribute */
+ stream->cur_attr &= ~attr;
+ }
+ }
+ else if (!scumm_stricmp(tagbuf, "p"))
+ {
+ /* paragraph - send out a blank line */
+ if (!stream->html_in_ignore)
+ outblank_stream(stream);
+ }
+ else if (!scumm_stricmp(tagbuf, "tab"))
+ {
+ /* tab - send out a \t */
+ if (!stream->html_in_ignore)
+ outtab_stream(stream);
+ }
+ else if (!scumm_stricmp(tagbuf, "img") || !scumm_stricmp(tagbuf, "sound"))
+ {
+ /* IMG and SOUND - allow ALT attributes */
+ stream->html_allow_alt = TRUE;
+ }
+ else if (!scumm_stricmp(tagbuf, "hr"))
+ {
+ int rem;
+
+ if (!stream->html_in_ignore)
+ {
+ /* start a new line */
+ outflushn_stream(stream, 1);
+
+ /* write out underscores to the display width */
+ for (rem = G_os_linewidth - 1 ; rem > 0 ; )
+ {
+ char dashbuf[100];
+ int cur;
+
+ /* do as much as we can on this pass */
+ cur = rem;
+ if ((size_t)cur > sizeof(dashbuf) - 1)
+ cur = sizeof(dashbuf) - 1;
+
+ /* do a buffer-full of dashes */
+ memset(dashbuf, '_', cur);
+ dashbuf[cur] = '\0';
+ outstring_stream(stream, dashbuf);
+
+ /* deduct this from the total */
+ rem -= cur;
+ }
+
+ /* put a blank line after the underscores */
+ outblank_stream(stream);
+ }
+ }
+ else if (!scumm_stricmp(tagbuf, "q"))
+ {
+ unsigned int htmlchar;
+
+ if (!stream->html_in_ignore)
+ {
+ /* if it's an open quote, increment the level */
+ if (!is_end_tag)
+ ++(stream->html_quote_level);
+
+ /* add the open quote */
+ htmlchar =
+ (!is_end_tag
+ ? ((stream->html_quote_level & 1) == 1
+ ? 8220 : 8216)
+ : ((stream->html_quote_level & 1) == 1
+ ? 8221 : 8217));
+
+ /*
+ * write out the HTML character, translated to
+ * the local character set
+ */
+ outchar_html_stream(stream, htmlchar);
+
+ /* if it's a close quote, decrement the level */
+ if (is_end_tag)
+ --(stream->html_quote_level);
+ }
+ }
+ else if (!scumm_stricmp(tagbuf, "title"))
+ {
+ /*
+ * Turn ignore mode on or off as appropriate, and
+ * turn on or off title mode as well.
+ */
+ if (is_end_tag)
+ {
+ /*
+ * note that we're leaving an ignore section and
+ * a title section
+ */
+ --(stream->html_in_ignore);
+ --(stream->html_in_title);
+
+ /*
+ * if we're no longer in a title, call the OS
+ * layer to tell it the title string, in case it
+ * wants to change the window title or otherwise
+ * make use of the title
+ */
+ if (stream->html_in_title == 0)
+ {
+ /* null-terminate the title string */
+ *stream->html_title_ptr = '\0';
+
+ /* tell the OS about the title */
+ os_set_title(stream->html_title_buf);
+ }
+ }
+ else
+ {
+ /*
+ * if we aren't already in a title, set up to
+ * capture the title into the title buffer
+ */
+ if (!stream->html_in_title)
+ stream->html_title_ptr = stream->html_title_buf;
+
+ /*
+ * note that we're in a title and in an ignore
+ * section, since nothing within gets displayed
+ */
+ ++(stream->html_in_ignore);
+ ++(stream->html_in_title);
+ }
+ }
+ else if (!scumm_stricmp(tagbuf, "aboutbox"))
+ {
+ /* turn ignore mode on or off as appropriate */
+ if (is_end_tag)
+ --(stream->html_in_ignore);
+ else
+ ++(stream->html_in_ignore);
+ }
+ else if (!scumm_stricmp(tagbuf, "pre"))
+ {
+ /* count the nesting level if starting PRE mode */
+ if (!is_end_tag)
+ stream->html_pre_level += 1;
+
+ /* surround the PRE block with line breaks */
+ outblank_stream(stream);
+
+ /* count the nesting level if ending PRE mode */
+ if (is_end_tag && stream->html_pre_level != 0)
+ stream->html_pre_level -= 1;
+ }
+
+ /* suppress everything up to the next '>' */
+ stream->html_mode_flag = HTML_MODE_TAG;
+
+ /*
+ * continue with the current character; since we're in
+ * html tag mode, we'll skip everything until we get to
+ * the closing '>'
+ */
+ continue;
+ }
+ else if (c == '&')
+ {
+ /* parse it */
+ char xlat_buf[50];
+ c = out_parse_entity(xlat_buf, sizeof(xlat_buf), &s, &slen);
+
+ /* write it out (we've already translated it) */
+ outstring_noxlat_stream(stream, xlat_buf);
+
+ /* proceed with the next character */
+ continue;
+ }
+ }
+ else if (stream->html_target && stream->html_mode && c == '<')
+ {
+ /*
+ * We're in HTML mode, and we have an underlying HTML target.
+ * We don't need to do much HTML interpretation at this level.
+ * However, we do need to keep track of when we're in a PRE
+ * block, so that we can pass whitespaces and newlines through
+ * to the underlying HTML engine without filtering when we're
+ * in preformatted text.
+ */
+ char tagbuf[50];
+ int is_end_tag;
+ c = read_tag(tagbuf, sizeof(tagbuf), &is_end_tag,
+ &s, &slen, stream);
+
+ /* check for special tags */
+ if (!scumm_stricmp(tagbuf, "pre"))
+ {
+ /* count the nesting level */
+ if (!is_end_tag)
+ stream->html_pre_level += 1;
+ else if (is_end_tag && stream->html_pre_level != 0)
+ stream->html_pre_level -= 1;
+ }
+
+ /* copy the last character after the tag to the stream */
+ outchar_stream(stream, c);
+ }
+ else
+ {
+ /* normal character */
+ outchar_stream(stream, c);
+ }
+
+ /* move on to the next character, unless we're finished */
+ if (done)
+ c = '\0';
+ else
+ c = nextout(&s, &slen);
+ }
+
+ /* if we ended up inside what looked like a format string, dump string */
+ if (infmt)
+ {
+ outchar_stream(stream, '%');
+ for (f1 = fmsbuf ; f1 < f ; ++f1)
+ outchar_stream(stream, *f1);
+ }
+
+ /* exit a recursion level */
+ out_pop_stream();
+
+ /* success */
+ return 0;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Parse an HTML entity markup
+ */
+static char out_parse_entity(char *outbuf, size_t outbuf_size,
+ char **sp, size_t *slenp)
+{
+ char ampbuf[10];
+ char *dst;
+ char *orig_s;
+ size_t orig_slen;
+ const amp_tbl_t *ampptr;
+ size_t lo, hi, cur;
+ char c;
+
+ /*
+ * remember where the part after the '&' begins, so we can come back
+ * here later if necessary
+ */
+ orig_s = *sp;
+ orig_slen = *slenp;
+
+ /* get the character after the ampersand */
+ c = nextout(sp, slenp);
+
+ /* if it's numeric, parse the number */
+ if (c == '#')
+ {
+ uint val;
+
+ /* skip the '#' */
+ c = nextout(sp, slenp);
+
+ /* check for hex */
+ if (c == 'x' || c == 'X')
+ {
+ /* skip the 'x' */
+ c = nextout(sp, slenp);
+
+ /* read the hex number */
+ for (val = 0 ; Common::isXDigit((uchar)c) ; c = nextout(sp, slenp))
+ {
+ /* accumulate the current digit into the value */
+ val *= 16;
+ if (outisdg(c))
+ val += c - '0';
+ else if (c >= 'a' && c <= 'f')
+ val += c - 'a' + 10;
+ else
+ val += c - 'A' + 10;
+ }
+ }
+ else
+ {
+ /* read the number */
+ for (val = 0 ; outisdg(c) ; c = nextout(sp, slenp))
+ {
+ /* accumulate the current digit into the value */
+ val *= 10;
+ val += c - '0';
+ }
+ }
+
+ /* if we found a ';' at the end, skip it */
+ if (c == ';')
+ c = nextout(sp, slenp);
+
+ /* translate the character into the output buffer */
+ os_xlat_html4(val, outbuf, outbuf_size);
+
+ /* we're done with this character */
+ return c;
+ }
+
+ /*
+ * Parse the sequence after the '&'. Parse up to the closing
+ * semicolon, or any non-alphanumeric, or until we fill up the buffer.
+ */
+ for (dst = ampbuf ;
+ c != '\0' && (outisdg(c) || outisal(c))
+ && dst < ampbuf + sizeof(ampbuf) - 1 ;
+ *dst++ = c, c = nextout(sp, slenp)) ;
+
+ /* null-terminate the name */
+ *dst = '\0';
+
+ /* do a binary search for the name */
+ lo = 0;
+ hi = sizeof(amp_tbl)/sizeof(amp_tbl[0]) - 1;
+ for (;;)
+ {
+ int diff;
+
+ /* if we've converged, look no further */
+ if (lo > hi || lo >= sizeof(amp_tbl)/sizeof(amp_tbl[0]))
+ {
+ ampptr = 0;
+ break;
+ }
+
+ /* split the difference */
+ cur = lo + (hi - lo)/2;
+ ampptr = &_tbl[cur];
+
+ /* see where we are relative to the target item */
+ diff = strcmp(ampptr->cname, ampbuf);
+ if (diff == 0)
+ {
+ /* this is it */
+ break;
+ }
+ else if (diff > 0)
+ {
+ /* make sure we don't go off the end */
+ if (cur == hi && cur == 0)
+ {
+ /* we've failed to find it */
+ ampptr = 0;
+ break;
+ }
+
+ /* this one is too high - check the lower half */
+ hi = (cur == hi ? hi - 1 : cur);
+ }
+ else
+ {
+ /* this one is too low - check the upper half */
+ lo = (cur == lo ? lo + 1 : cur);
+ }
+ }
+
+ /* skip to the appropriate next character */
+ if (c == ';')
+ {
+ /* name ended with semicolon - skip the semicolon */
+ c = nextout(sp, slenp);
+ }
+ else if (ampptr != 0)
+ {
+ int skipcnt;
+
+ /* found the name - skip its exact length */
+ skipcnt = strlen(ampptr->cname);
+ for (*sp = orig_s, *slenp = orig_slen ; skipcnt != 0 ;
+ c = nextout(sp, slenp), --skipcnt) ;
+ }
+
+ /* if we found the entry, write out the character */
+ if (ampptr != 0)
+ {
+ /*
+ * if this one has an external mapping table entry, use the mapping
+ * table entry; otherwise, use the default OS routine mapping
+ */
+ if (ampptr->expan != 0)
+ {
+ /*
+ * we have an explicit expansion from the mapping table file -
+ * use it
+ */
+ size_t copylen = strlen(ampptr->expan);
+ if (copylen > outbuf_size - 1)
+ copylen = outbuf_size - 1;
+
+ memcpy(outbuf, ampptr->expan, copylen);
+ outbuf[copylen] = '\0';
+ }
+ else
+ {
+ /*
+ * there's no mapping table expansion - use the default OS code
+ * expansion
+ */
+ os_xlat_html4(ampptr->html_cval, outbuf, outbuf_size);
+ }
+ }
+ else
+ {
+ /*
+ * didn't find it - output the '&' literally, then back up and
+ * output the entire sequence following
+ */
+ *sp = orig_s;
+ *slenp = orig_slen;
+ c = nextout(sp, slenp);
+
+ /* fill in the '&' return value */
+ outbuf[0] = '&';
+ outbuf[1] = '\0';
+ }
+
+ /* return the next character */
+ return c;
+}
+
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Initialize the output formatter
+ */
+void out_init()
+{
+ /* not yet hiding output */
+ outflag = 1;
+ outcnt = 0;
+ hidout = 0;
+
+ /* initialize the standard display stream */
+ out_init_std(&G_std_disp);
+
+ /* initialize the log file stream */
+ out_init_log(&G_log_disp);
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * initialize the property translation table
+ */
+void tiosetfmt(tiocxdef *ctx, runcxdef *rctx, uchar *fbase, uint flen)
+{
+ VARUSED(ctx);
+ fmsbase = fbase;
+ fmstop = fbase + flen;
+ runctx = rctx;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Map an HTML entity to a local character value. The character table
+ * reader will call this routine during initialization if it finds HTML
+ * entities in the mapping table file. We'll remember these mappings
+ * for use in translating HTML entities to the local character set.
+ *
+ * Note that the standard run-time can only display a single character
+ * set, so every HTML entity that we display must be mapped to the
+ * single active native character set.
+ */
+void tio_set_html_expansion(unsigned int html_char_val,
+ const char *expansion, size_t expansion_len)
+{
+ amp_tbl_t *p;
+
+ /* find the character value */
+ for (p = amp_tbl ;
+ p < amp_tbl + sizeof(amp_tbl)/sizeof(amp_tbl[0]) ; ++p)
+ {
+ /* if this is the one, store it */
+ if (p->html_cval == html_char_val)
+ {
+ /* allocate space for it */
+ p->expan = (char *)osmalloc(expansion_len + 1);
+
+ /* save it */
+ memcpy(p->expan, expansion, expansion_len);
+ p->expan[expansion_len] = '\0';
+
+ /* no need to look any further */
+ return;
+ }
+ }
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Write out a c-style (null-terminated) string.
+ */
+int outformat(char *s)
+{
+ return outformatlen(s, strlen(s));
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * This routine sends out a string, one character at a time (via outchar).
+ * Escape codes ('\n', and so forth) are handled here.
+ */
+int outformatlen(char *s, uint slen)
+{
+ char c;
+ uint orig_slen;
+ char *orig_s;
+ int ret;
+ int called_filter;
+
+ /* presume we'll return success */
+ ret = 0;
+
+ /* presume we won't call the filter function */
+ called_filter = FALSE;
+
+ /* if there's a user filter function to invoke, call it */
+ if (G_user_filter != MCMONINV)
+ {
+ /* push the string */
+ runpstr(runctx, s, slen, 1);
+
+ /* call the filter */
+ runfn(runctx, G_user_filter, 1);
+
+ /*
+ * note that we called the filter, so that we'll remove the
+ * result of the filter from the stack before we return
+ */
+ called_filter = TRUE;
+
+ /* if the result is a string, use it in place of the original text */
+ if (runtostyp(runctx) == DAT_SSTRING)
+ {
+ runsdef val;
+ uchar *p;
+
+ /* pop the value */
+ runpop(runctx, &val);
+
+ /*
+ * get the text from the string, and use it as a replacement
+ * for the original string
+ */
+ p = val.runsv.runsvstr;
+ slen = osrp2(p) - 2;
+ s = (char *)(p + 2);
+
+ /*
+ * push the string back onto the stack - this will ensure
+ * that the string stays referenced while we're working, so
+ * that the garbage collector won't delete it
+ */
+ runrepush(runctx, &val);
+ }
+ }
+
+ /* remember the original string, before we scan the first character */
+ orig_s = s;
+ orig_slen = slen;
+
+ /* get the first character to display */
+ c = nextout(&s, &slen);
+
+ /* if the string is non-empty, note that we've displayed something */
+ if (c != 0)
+ outcnt = 1;
+
+ /* check to see if we're hiding output */
+ if (out_is_hidden())
+ goto done;
+
+ /* if the debugger is showing watchpoints, suppress all output */
+ if (outwxflag)
+ goto done;
+
+ /* display the string */
+ ret = outformatlen_stream(&G_std_disp, orig_s, orig_slen);
+
+ /* if there's a log file, write to the log file as well */
+ if (logfp != 0)
+ {
+ outformatlen_stream(&G_log_disp, orig_s, orig_slen);
+ osfflush(logfp);
+ }
+
+done:
+ /* if we called the filter, remove the result from the stack */
+ if (called_filter)
+ rundisc(runctx);
+
+ /* return the result from displaying to the screen */
+ return ret;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Display a blank line
+ */
+void outblank()
+{
+ /* note that we've displayed something */
+ outcnt = 1;
+
+ /* check to see if we're hiding output */
+ if (out_is_hidden())
+ return;
+
+ /* generate the newline to the standard display */
+ outblank_stream(&G_std_disp);
+
+ /* if we're logging, generate the newline to the log file as well */
+ if (logfp != 0)
+ {
+ outblank_stream(&G_log_disp);
+ osfflush(logfp);
+ }
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * outcaps() - sets an internal flag which makes the next letter output
+ * a capital, whether it came in that way or not. Set the same state in
+ * both formatters (standard and log).
+ */
+void outcaps(void)
+{
+ outcaps_stream(&G_std_disp);
+ outcaps_stream(&G_log_disp);
+}
+
+/*
+ * outnocaps() - sets the next letter to a miniscule, whether it came in
+ * that way or not.
+ */
+void outnocaps(void)
+{
+ outnocaps_stream(&G_std_disp);
+ outnocaps_stream(&G_log_disp);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Open a log file
+ */
+int tiologopn(tiocxdef *ctx, char *fn)
+{
+ /* if there's an old log file, close it */
+ if (tiologcls(ctx))
+ return 1;
+
+ /* save the filename for later */
+ strcpy(logfname, fn);
+
+ /* open the new file */
+ logfp = osfopwt(fn, OSFTLOG);
+
+ /*
+ * Reset the log file's output formatter state, since we're opening
+ * a new file.
+ */
+ out_init_log(&G_log_disp);
+
+ /*
+ * Set the log file's HTML source mode flag to the same value as is
+ * currently being used in the main display stream, so that it will
+ * interpret source markups the same way that the display stream is
+ * going to.
+ */
+ G_log_disp.html_mode = G_std_disp.html_mode;
+
+ /* return 0 on success, non-zero on failure */
+ return (logfp == 0);
+}
+
+/*
+ * Close the log file
+ */
+int tiologcls(tiocxdef *ctx)
+{
+ /* if we have a file, close it */
+ if (logfp != 0)
+ {
+ /* close the handle */
+ osfcls(logfp);
+
+ /* set the system file type to "log file" */
+ os_settype(logfname, OSFTLOG);
+
+ /* forget about our log file handle */
+ logfp = 0;
+ }
+
+ /* success */
+ return 0;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Write text explicitly to the log file. This can be used to add
+ * special text (such as prompt text) that would normally be suppressed
+ * from the log file. When more mode is turned off, we don't
+ * automatically copy text to the log file; any text that the caller
+ * knows should be in the log file during times when more mode is turned
+ * off can be explicitly added with this function.
+ *
+ * If nl is true, we'll add a newline at the end of this text. The
+ * caller should not include any newlines in the text being displayed
+ * here.
+ */
+void out_logfile_print(char *txt, int nl)
+{
+ /* if there's no log file, there's nothing to do */
+ if (logfp == 0)
+ return;
+
+ /* add the text */
+ os_fprintz(logfp, txt);
+
+ /* add a newline if desired */
+ if (nl)
+ {
+ /* add a normal newline */
+ os_fprintz(logfp, "\n");
+
+ /* if the logfile is an html target, write an HTML line break */
+ if (G_log_disp.html_target && G_log_disp.html_mode)
+ os_fprintz(logfp, "<BR HEIGHT=0>\n");
+ }
+
+ /* flush the output */
+ osfflush(logfp);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Set the current MORE mode
+ */
+int setmore(int state)
+{
+ int oldstate = G_os_moremode;
+
+ G_os_moremode = state;
+ return oldstate;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Run the MORE prompt. If the output layer takes responsibility for
+ * pagination issues (i.e., USE_MORE is defined), we'll simply display
+ * the prompt and wait for input. Otherwise, the OS layer controls the
+ * MORE prompt, so we'll call the OS-layer function to display the
+ * prompt.
+ */
+void out_more_prompt()
+{
+#ifdef USE_MORE
+ /*
+ * USE_MORE defined - we take responsibility for pagination. Show
+ * our default MORE prompt and wait for a keystroke.
+ */
+
+ int done;
+ int next_page = FALSE;
+
+ /* display the "MORE" prompt */
+ os_printz("[More]");
+ os_flush();
+
+ /* wait for an acceptable keystroke */
+ for (done = FALSE ; !done ; )
+ {
+ os_event_info_t evt;
+
+ /* get an event */
+ switch(os_get_event(0, FALSE, &evt))
+ {
+ case OS_EVT_KEY:
+ switch(evt.key[0])
+ {
+ case ' ':
+ /* stop waiting, show one page */
+ done = TRUE;
+ next_page = TRUE;
+ break;
+
+ case '\r':
+ case '\n':
+ /* stop waiting, show one line */
+ done = TRUE;
+ next_page = FALSE;
+ break;
+
+ default:
+ /* ignore any other keystrokes */
+ break;
+ }
+ break;
+
+ case OS_EVT_EOF:
+ /* end of file - there's nothing to wait for now */
+ done = TRUE;
+ next_page = TRUE;
+
+ /* don't use more prompts any more, as the user can't respond */
+ G_os_moremode = FALSE;
+ break;
+
+ default:
+ /* ignore other events */
+ break;
+ }
+ }
+
+ /*
+ * Remove the prompt from the screen by backing up and overwriting
+ * it with spaces. (Note that this assumes that we're running in
+ * some kind of terminal or character mode with a fixed-pitch font;
+ * if that's not the case, the OS layer should be taking
+ * responsibility for pagination anyway, so this code shouldn't be
+ * in use in the first place.)
+ */
+ os_printz("\r \r");
+
+ /*
+ * if they pressed the space key, it means that we should show an
+ * entire new page, so reset the line count to zero; otherwise,
+ * we'll want to display another MORE prompt at the very next line,
+ * so leave the line count alone
+ */
+ if (next_page)
+ G_std_disp.linecnt = 0;
+
+#else /* USE_MORE */
+
+ /*
+ * USE_MORE is undefined - this means that the OS layer is taking
+ * all responsibility for pagination. We must ask the OS layer to
+ * display the MORE prompt, because we can't make any assumptions
+ * about what the prompt looks like.
+ */
+
+ os_more_prompt();
+
+#endif /* USE_MORE */
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * reset output
+ */
+void outreset(void)
+{
+ G_std_disp.linecnt = 0;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Determine if HTML mode is active. Returns true if so, false if not.
+ * Note that this merely indicates whether an "\H+" sequence is
+ * currently active -- this will return true after an "\H+" sequence,
+ * even on text-only interpreters.
+ */
+int tio_is_html_mode()
+{
+ /* return the current HTML mode flag for the standard display stream */
+ return G_std_disp.html_mode;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Capture routines. Capture affects only the standard display output
+ * stream; there's no need to capture information redundantly in the log
+ * file stream.
+ */
+
+/*
+ * Begin/end capturing
+ */
+void tiocapture(tiocxdef *tioctx, mcmcxdef *memctx, int flag)
+{
+ if (flag)
+ {
+ /* create a new object if necessary */
+ if (G_std_disp.capture_obj == MCMONINV)
+ {
+ mcmalo(memctx, 256, &G_std_disp.capture_obj);
+ mcmunlck(memctx, G_std_disp.capture_obj);
+ }
+
+ /* remember the memory context */
+ G_std_disp.capture_ctx = memctx;
+ }
+
+ /*
+ * remember capture status in the standard output stream as well as
+ * the log stream
+ */
+ G_std_disp.capturing = flag;
+ G_log_disp.capturing = flag;
+}
+
+/* clear all captured output */
+void tioclrcapture(tiocxdef *tioctx)
+{
+ G_std_disp.capture_ofs = 0;
+}
+
+/* clear captured output back to a given size */
+void tiopopcapture(tiocxdef *tioctx, uint orig_size)
+{
+ G_std_disp.capture_ofs = orig_size;
+}
+
+/* get the object handle of the captured output */
+mcmon tiogetcapture(tiocxdef *ctx)
+{
+ return G_std_disp.capture_obj;
+}
+
+/* get the amount of text captured */
+uint tiocapturesize(tiocxdef *ctx)
+{
+ return G_std_disp.capture_ofs;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * set the current actor
+ */
+void tiosetactor(tiocxdef *ctx, objnum actor)
+{
+ VARUSED(ctx);
+ cmdActor = actor;
+}
+
+/*
+ * get the current actor
+ */
+objnum tiogetactor(tiocxdef *ctx)
+{
+ VARUSED(ctx);
+ return cmdActor;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Flush the output line. We'll write to both the standard display and
+ * the log file, as needed.
+ */
+void outflushn(int nl)
+{
+ /* flush the display stream */
+ outflushn_stream(&G_std_disp, nl);
+
+ /* flush the log stream, if we have an open log file */
+ if (logfp != 0)
+ {
+ outflushn_stream(&G_log_disp, nl);
+ osfflush(logfp);
+ }
+}
+
+/*
+ * flush the current line, and start a new line
+ */
+void outflush(void)
+{
+ /* use the common flushing routine in mode 1 (regular newline) */
+ outflushn(1);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Hidden text routines
+ */
+
+/*
+ * outhide - hide output in the standard display stream
+ */
+void outhide(void)
+{
+ outflag = 0;
+ outcnt = 0;
+ hidout = 0;
+}
+
+/*
+ * Check output status. Indicate whether output is currently hidden,
+ * and whether any hidden output has occurred.
+ */
+void outstat(int *hidden, int *output_occurred)
+{
+ *hidden = !outflag;
+ *output_occurred = outcnt;
+}
+
+/* set the flag to indicate that output has occurred */
+void outsethidden(void)
+{
+ outcnt = 1;
+ hidout = 1;
+}
+
+/*
+ * outshow() - turns output back on, and returns TRUE (1) if any output
+ * has occurred since the last outshow(), FALSE (0) otherwise.
+ */
+int outshow(void)
+{
+ /* turn output back on */
+ outflag = 1;
+
+ /* if we're debugging, note the end of hidden output */
+ if (dbghid && hidout)
+ {
+ hidout = 0;
+ trcsho();
+ }
+
+ /* return the flag indicating whether hidden output occurred */
+ return outcnt;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * start/end watchpoint evaluation - suppress all dstring output
+ */
+void outwx(int flag)
+{
+ outwxflag = flag;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Set the user filter function. Setting this to MCMONINV clears the
+ * filter.
+ */
+void out_set_filter(objnum filter_fn)
+{
+ /* remember the filter function */
+ G_user_filter = filter_fn;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Set the double-space mode
+ */
+void out_set_doublespace(int dbl)
+{
+ /* remember the new setting */
+ doublespace = dbl;
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/run.cpp b/engines/glk/tads/tads2/run.cpp
index 5123d65..5a9ede0 100644
--- a/engines/glk/tads/tads2/run.cpp
+++ b/engines/glk/tads/tads2/run.cpp
@@ -22,30 +22,2410 @@
#include "glk/tads/tads2/run.h"
#include "glk/tads/tads2/data.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/list.h"
+#include "glk/tads/tads2/os.h"
+#include "glk/tads/tads2/post_compilation.h"
#include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/os_glk.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
-int runsdef::runsiz() const {
- switch (runstyp) {
+/* forward declarations */
+struct bifcxdef;
+
+/*
+* Create a new object
+*/
+static void run_new(runcxdef *ctx, uchar *noreg *codepp,
+ objnum callobj, prpnum callprop)
+{
+ objnum sc = 0;
+ objnum objn;
+ objdef *objp;
+ int sccnt;
+ vocidef *voci;
+
+ /* get the superclass (nil means no superclass) */
+ if (runtostyp(ctx) == DAT_NIL)
+ sccnt = 0;
+ else
+ {
+ /* get the superclass */
+ sc = runpopobj(ctx);
+ sccnt = 1;
+
+ /* make sure it's not a dynamically-allocated object */
+ voci = vocinh(ctx->runcxvoc, sc);
+ if (voci->vociflg & VOCIFNEW)
+ runsig(ctx, ERR_BADNEWSC);
+ }
+
+ /* create a new object and set its superclass */
+ objp = objnew(ctx->runcxmem, sccnt, 64, &objn, FALSE);
+ if (sccnt) oswp2(objsc(objp), sc);
+
+ /* save undo for the object creation */
+ vocdusave_newobj(ctx->runcxvoc, objn);
+
+ /* touch and unlock the object */
+ mcmtch(ctx->runcxmem, (mcmon)objn);
+ mcmunlck(ctx->runcxmem, (mcmon)objn);
+
+ /* add a vocabulary inheritance record for the new object */
+ vociadd(ctx->runcxvoc, objn, MCMONINV, sccnt, &sc, VOCIFNEW | VOCIFVOC);
+
+ /* set up its vocabulary, inheriting from the class */
+ if (sccnt)
+ supivoc1((struct supcxdef *)0, ctx->runcxvoc,
+ vocinh(ctx->runcxvoc, objn), objn, TRUE, VOCFNEW);
+
+ /* run the constructor */
+ runpprop(ctx, codepp, callobj, callprop, objn, PRP_CONSTRUCT,
+ FALSE, 0, objn);
+#ifdef NEVER
+ /*
+ * add it to its location's contents list by calling
+ * newobj.moveInto(newobj.location)
+ */
+ runppr(ctx, objn, PRP_LOCATION, 0);
+ if (runtostyp(ctx) == DAT_OBJECT)
+ runppr(ctx, objn, PRP_MOVEINTO, 1);
+ else
+ rundisc(ctx);
+#endif
+
+ /* return the new object */
+ runpobj(ctx, objn);
+}
+
+/*
+* Delete an object
+*/
+static void run_delete(runcxdef *ctx, uchar *noreg *codepp,
+ objnum callobj, prpnum callprop)
+{
+ objnum objn;
+ vocidef *voci;
+ int i;
+ voccxdef *vctx = ctx->runcxvoc;
+
+ /* get the object to be deleted */
+ objn = runpopobj(ctx);
+
+ /* make sure it was allocated with "new" */
+ voci = vocinh(vctx, objn);
+ if (voci == 0 || !(voci->vociflg & VOCIFNEW))
+ runsig(ctx, ERR_BADDEL);
+
+ /* run the destructor */
+ runpprop(ctx, codepp, callobj, callprop, objn, PRP_DESTRUCT,
+ FALSE, 0, objn);
+#ifdef NEVER
+ /* remove it from its location, if any, by using moveInto(nil) */
+ runpnil(ctx);
+ runppr(ctx, objn, PRP_MOVEINTO, 1);
+#endif
+
+ /* save undo for the object deletion */
+ vocdusave_delobj(vctx, objn);
+
+ /* delete the object's inheritance and vocabulary records */
+ vocdel(vctx, objn);
+ vocidel(vctx, objn);
+
+ /* forget 'it' if the deleted object is 'it' (or 'them', etc) */
+ if (vctx->voccxit == objn) vctx->voccxit = MCMONINV;
+ if (vctx->voccxhim == objn) vctx->voccxhim = MCMONINV;
+ if (vctx->voccxher == objn) vctx->voccxher = MCMONINV;
+ for (i = 0; i < vctx->voccxthc; ++i)
+ {
+ if (vctx->voccxthm[i] == objn)
+ {
+ /* forget the entire 'them' list when deleting from it */
+ vctx->voccxthc = 0;
+ break;
+ }
+ }
+
+ /* forget the 'again' statistics if necessary */
+ if (vctx->voccxlsd.vocolobj == objn
+ || vctx->voccxlsi.vocolobj == objn
+ || vctx->voccxlsa == objn
+ || vctx->voccxlsv == objn
+ || vctx->voccxlsp == objn)
+ {
+ /* forget the verb */
+ vctx->voccxlsv = MCMONINV;
+
+ /*
+ * note in the flags why we lost the "again" verb, for better
+ * error reporting if the player tries to type "again"
+ */
+ vctx->voccxflg |= VOCCXAGAINDEL;
+ }
+
+ /* delete the memory manager object */
+ mcmfre(ctx->runcxmem, (mcmon)objn);
+}
+
+
+/*
+* invoke a function
+*/
+void runfn(runcxdef *ctx, noreg objnum objn, int argc)
+{
+ uchar *fn;
+ int err;
+
+ NOREG((&objn))
+
+ /* get a lock on the object */
+ fn = mcmlck(ctx->runcxmem, objn);
+
+ /* catch any errors, so we can unlock the object */
+ ERRBEGIN(ctx->runcxerr)
+
+ /* execute the object */
+ runexe(ctx, fn, MCMONINV, objn, (prpnum)0, argc);
+
+ /* in case of error, unlock the object and resignal the error */
+ ERRCATCH(ctx->runcxerr, err)
+ mcmunlck(ctx->runcxmem, objn); /* release the lock on the object */
+ if (err < ERR_RUNEXIT || err > ERR_RUNEXITOBJ)
+ dbgdump(ctx->runcxdbg); /* dump the stack */
+ errrse(ctx->runcxerr);
+ ERREND(ctx->runcxerr)
+
+ /* we're done with the object, so unlock it */
+ mcmunlck(ctx->runcxmem, objn);
+}
+
+/*
+* compress the heap - remove unreferenced items
+*/
+void runhcmp(runcxdef *ctx, uint siz, uint below,
+ runsdef *val1, runsdef *val2, runsdef *val3)
+{
+ uchar *hp = ctx->runcxheap;
+ uchar *htop = ctx->runcxhp;
+ runsdef *stop = ctx->runcxsp + below;
+ runsdef *stk = ctx->runcxstk;
+ runsdef *sp;
+ uchar *dst = hp;
+ uchar *hnxt;
+ int ref;
+
+ /* go through heap, finding references on stack */
+ for (; hp < htop; hp = hnxt)
+ {
+ hnxt = hp + osrp2(hp); /* remember next heap element */
+
+ for (ref = FALSE, sp = stk; sp < stop; ++sp)
+ {
+ switch (sp->runstyp)
+ {
+ case DAT_SSTRING:
+ case DAT_LIST:
+ if (sp->runsv.runsvstr == hp) /* reference to this item? */
+ {
+ ref = TRUE; /* this heap item is referenced */
+ sp->runsv.runsvstr = dst; /* reflect imminent move */
+ }
+ break;
+
+ default: /* other types do not refer to the heap */
+ break;
+ }
+ }
+
+ /* check the explicitly referenced value pointers as well */
+#define CHECK_VAL(val) \
+ if (val && val->runsv.runsvstr == hp) \
+ ref = TRUE, val->runsv.runsvstr = dst;
+ CHECK_VAL(val1);
+ CHECK_VAL(val2);
+ CHECK_VAL(val3);
+#undef CHECK_VAL
+
+ /* if referenced, copy it to dst and advance dst */
+ if (ref)
+ {
+ if (hp != dst) memmove(dst, hp, (size_t)osrp2(hp));
+ dst += osrp2(dst);
+ }
+ }
+
+ /* set heap pointer based on shuffled heap */
+ ctx->runcxhp = dst;
+
+ /* check for space requested, and signal error if not available */
+ if ((uint)(ctx->runcxhtop - ctx->runcxhp) < siz)
+ runsig(ctx, ERR_HPOVF);
+}
+
+/*
+* push a value onto the stack that's already been allocated in heap
+*/
+void runrepush(runcxdef *ctx, runsdef *val)
+{
+ /* check for stack overflow */
+ runstkovf(ctx);
+
+ OSCPYSTRUCT(*(ctx->runcxsp), *val);
+
+ /* increment stack pointer */
+ ++(ctx->runcxsp);
+}
+
+/* push a counted-length string onto the stack */
+void runpstr(runcxdef *ctx, char *str, int len, int sav)
+{
+ runsdef val;
+
+ /* allocate space and set up new string */
+ runhres(ctx, len + 2, sav);
+ oswp2(ctx->runcxhp, len + 2);
+ memcpy(ctx->runcxhp + 2, str, (size_t)len);
+
+ /* push return value */
+ val.runsv.runsvstr = ctx->runcxhp;
+ val.runstyp = DAT_SSTRING;
+ ctx->runcxhp += len + 2;
+ runrepush(ctx, &val);
+}
+
+/* push a C-style string, converting escape codes */
+void runpushcstr(runcxdef *ctx, char *str, size_t len, int sav)
+{
+ char *p;
+ char *dst;
+ size_t need;
+ runsdef val;
+
+ /* determine how much space we'll need after converting escapes */
+ for (p = str, need = len; p < str + len; ++p)
+ {
+ switch (*p)
+ {
+ case '\\':
+ case '\n':
+ case '\r':
+ case '\t':
+ /* these characters need to be escaped */
+ ++need;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* reserve space */
+ runhres(ctx, need + 2, sav);
+
+ /* set up the length prefix */
+ oswp2(ctx->runcxhp, need + 2);
+
+ /* copy the string, expanding escapes */
+ for (p = str, dst = (char *)ctx->runcxhp + 2; p < str + len; ++p)
+ {
+ switch (*p)
+ {
+ case '\\':
+ *dst++ = '\\';
+ *dst++ = '\\';
+ break;
+
+ case '\n':
+ case '\r':
+ *dst++ = '\\';
+ *dst++ = 'n';
+ break;
+
+ case '\t':
+ *dst++ = '\\';
+ *dst++ = '\t';
+ break;
+
+ default:
+ *dst++ = *p;
+ break;
+ }
+ }
+
+ /* push the return value */
+ val.runsv.runsvstr = ctx->runcxhp;
+ val.runstyp = DAT_SSTRING;
+ ctx->runcxhp += need + 2;
+ runrepush(ctx, &val);
+}
+
+/* push a value onto the stack */
+void runpush(runcxdef *ctx, dattyp typ, runsdef *val)
+{
+ int len;
+
+ /* check for stack overflow */
+ runstkovf(ctx);
+
+ OSCPYSTRUCT(*(ctx->runcxsp), *val);
+ ctx->runcxsp->runstyp = typ;
+
+ /* variable-length data must be copied into the heap */
+ if (typ == DAT_SSTRING || typ == DAT_LIST)
+ {
+ len = osrp2(val->runsv.runsvstr);
+ runhres(ctx, len, 0); /* reserve space in heap */
+ memcpy(ctx->runcxhp, val->runsv.runsvstr, (size_t)len);
+ ctx->runcxsp->runsv.runsvstr = ctx->runcxhp;
+ ctx->runcxhp += len;
+ }
+
+ /* increment stack pointer */
+ ++(ctx->runcxsp);
+}
+
+/* push a number onto the stack */
+void runpnum(runcxdef *ctx, long num)
+{
+ runsdef val;
+
+ val.runsv.runsvnum = num;
+ runpush(ctx, DAT_NUMBER, &val);
+}
+
+/* push an object onto the stack (or nil if obj is MCMONINV) */
+void runpobj(runcxdef *ctx, objnum obj)
+{
+ runsdef val;
+
+ if (obj == MCMONINV)
+ runpnil(ctx);
+ else
+ {
+ val.runsv.runsvobj = obj;
+ runpush(ctx, DAT_OBJECT, &val);
+ }
+}
+
+/* push nil */
+void runpnil(runcxdef *ctx)
+{
+ runsdef val;
+ runpush(ctx, DAT_NIL, &val);
+}
+
+/* copy datatype + value from a runsdef into a buffer (such as list) */
+static void runputbuf(uchar *dstp, runsdef *val)
+{
+ *dstp++ = val->runstyp;
+ switch (val->runstyp)
+ {
+ case DAT_LIST:
+ case DAT_SSTRING:
+ memcpy(dstp, val->runsv.runsvstr, (size_t)osrp2(val->runsv.runsvstr));
+ break;
+
case DAT_NUMBER:
- return 4;
+ oswp4s(dstp, val->runsv.runsvnum);
+ break;
+
+ case DAT_PROPNUM:
+ oswp2(dstp, val->runsv.runsvprp);
+ break;
+
+ case DAT_OBJECT:
+ case DAT_FNADDR:
+ oswp2(dstp, val->runsv.runsvobj);
+ break;
+ }
+}
+
+/* push a value from a buffer (list, property, etc) onto stack */
+void runpbuf(runcxdef *ctx, int typ, void *valp)
+{
+ runsdef val;
+
+ switch (typ)
+ {
+ case DAT_NUMBER:
+ val.runsv.runsvnum = osrp4s(valp);
+ break;
+
+ case DAT_OBJECT:
+ case DAT_FNADDR:
+ val.runsv.runsvobj = osrp2(valp);
+ break;
+
+ case DAT_PROPNUM:
+ val.runsv.runsvprp = osrp2(valp);
+ break;
+
+ case DAT_SSTRING:
+ case DAT_LIST:
+ val.runsv.runsvstr = (uchar *)valp;
+ break;
+
+ case DAT_NIL:
+ case DAT_TRUE:
+ break;
+ }
+ runpush(ctx, typ, &val);
+}
+
+/* compare items at top of stack for equality; TRUE->equal, FALSE->unequal */
+int runeq(runcxdef *ctx)
+{
+ runsdef val1, val2;
+
+ /* get values, and see if they have identical type; not equal if not */
+ runpop(ctx, &val1);
+ runpop(ctx, &val2);
+ if (val1.runstyp != val2.runstyp) return(FALSE);
+
+ /* types match, so check values */
+ switch (val1.runstyp)
+ {
+ case DAT_NUMBER:
+ return(val1.runsv.runsvnum == val2.runsv.runsvnum);
+
case DAT_SSTRING:
case DAT_LIST:
- return osrp2(runsv.runsvstr);
+ return(osrp2(val1.runsv.runsvstr) == osrp2(val2.runsv.runsvstr)
+ && !memcmp(val1.runsv.runsvstr, val2.runsv.runsvstr,
+ (size_t)osrp2(val1.runsv.runsvstr)));
+
case DAT_PROPNUM:
+ return(val1.runsv.runsvprp == val2.runsv.runsvprp);
+
case DAT_OBJECT:
case DAT_FNADDR:
- return 2;
+ return(val1.runsv.runsvobj == val2.runsv.runsvobj);
+
+ default:
+ return(TRUE);
+ }
+}
+
+/* compare magnitudes of numbers/strings at top of stack; strcmp-like value */
+int runmcmp(runcxdef *ctx)
+{
+ if (runtostyp(ctx) == DAT_NUMBER)
+ {
+ long num2 = runpopnum(ctx);
+ long num1 = runpopnum(ctx);
+
+ if (num1 > num2) return(1);
+ else if (num1 < num2) return(-1);
+ else return(0);
+ }
+ else if (runtostyp(ctx) == DAT_SSTRING)
+ {
+ uchar *str2 = runpopstr(ctx);
+ uchar *str1 = runpopstr(ctx);
+ uint len1 = osrp2(str1) - 2;
+ uint len2 = osrp2(str2) - 2;
+
+ str1 += 2;
+ str2 += 2;
+ while (len1 && len2)
+ {
+ if (*str1 < *str2) return(-1); /* character from 1 is greater */
+ else if (*str1 > *str2) return(1); /* char from 1 is less */
+
+ ++str1;
+ ++str2;
+ --len1;
+ --len2;
+ }
+ if (len1) return(1); /* match up to len2, but string 1 is longer */
+ else if (len2) return(-1); /* match up to len1, but str2 is longer */
+ else return(0); /* strings are identical */
+ }
+ else
+ {
+ runsig(ctx, ERR_INVCMP);
+ }
+ return 0;
+}
+
+/* determine size of a runsdef item */
+int runsiz(runsdef *item) {
+ switch (item->runstyp) {
+ case DAT_NUMBER:
+ return(4);
+ case DAT_SSTRING:
+ case DAT_LIST:
+ return(osrp2(item->runsv.runsvstr));
+ case DAT_PROPNUM:
+ case DAT_OBJECT:
+ case DAT_FNADDR:
+ return(2);
+ default:
+ return(0);
+ }
+}
+
+/* find a sublist within a list */
+uchar *runfind(uchar *lst, runsdef *item)
+{
+ uint len;
+ uint curlen;
+
+ for (len = osrp2(lst) - 2, lst += 2; len; lst += curlen, len -= curlen)
+ {
+ if (*lst == item->runstyp)
+ {
+ switch (*lst)
+ {
+ case DAT_LIST:
+ case DAT_SSTRING:
+ if (osrp2(lst + 1) == osrp2(item->runsv.runsvstr) &&
+ !memcmp(lst + 1, item->runsv.runsvstr, (size_t)osrp2(lst + 1)))
+ return(lst);
+ break;
+ case DAT_NUMBER:
+ if (osrp4s(lst + 1) == item->runsv.runsvnum)
+ return(lst);
+ break;
+
+ case DAT_TRUE:
+ case DAT_NIL:
+ return(lst);
+
+ case DAT_OBJECT:
+ case DAT_FNADDR:
+ if (osrp2(lst + 1) == item->runsv.runsvobj)
+ return(lst);
+ break;
+
+ case DAT_PROPNUM:
+ if (osrp2(lst + 1) == item->runsv.runsvprp)
+ return(lst);
+ break;
+ }
+ }
+ curlen = datsiz(*lst, lst + 1) + 1;
+ }
+ return((uchar *)0);
+}
+
+/* add values */
+void runadd(runcxdef *ctx, runsdef *val, runsdef *val2, uint below)
+{
+ if (val->runstyp == DAT_LIST)
+ {
+ int len1 = osrp2(val->runsv.runsvstr);
+ int len2 = runsiz(val2);
+ int newlen;
+
+ /* if concatenating a list, take out length + datatype from 2nd */
+ if (val2->runstyp == DAT_LIST)
+ newlen = len1 + len2 - 2; /* leave out second list len */
+ else
+ newlen = len1 + len2 + 1; /* add in datatype header */
+
+ /* get space in heap, copy first list, and set new length */
+ runhres2(ctx, newlen, below, val, val2);
+ memcpy(ctx->runcxhp, val->runsv.runsvstr, (size_t)len1);
+ oswp2(ctx->runcxhp, newlen);
+
+ /* append the new element or list of elements */
+ if (val2->runstyp == DAT_LIST)
+ memcpy(ctx->runcxhp + len1, val2->runsv.runsvstr + 2,
+ (size_t)(len2 - 2));
+ else
+ runputbuf(ctx->runcxhp + len1, val2);
+
+ /* set up return value and update heap pointer */
+ val->runsv.runsvstr = ctx->runcxhp;
+ ctx->runcxhp += newlen;
+ }
+ else if (val->runstyp == DAT_SSTRING && val2->runstyp == DAT_SSTRING)
+ {
+ int len1 = osrp2(val->runsv.runsvstr);
+ int len2 = osrp2(val2->runsv.runsvstr);
+
+ /* reserve space, and concatenate the two strings */
+ runhres2(ctx, len1 + len2 - 2, below, val, val2);
+ memcpy(ctx->runcxhp, val->runsv.runsvstr, (size_t)len1);
+ memcpy(ctx->runcxhp + len1, val2->runsv.runsvstr + 2,
+ (size_t)len2 - 2);
+
+ /* set length to sum of two lengths, minus 2nd length word */
+ oswp2(ctx->runcxhp, len1 + len2 - 2);
+ val->runsv.runsvstr = ctx->runcxhp;
+ ctx->runcxhp += len1 + len2 - 2;
+ }
+ else if (val->runstyp == DAT_NUMBER && val2->runstyp == DAT_NUMBER)
+ val->runsv.runsvnum += val2->runsv.runsvnum;
+ else
+ runsig(ctx, ERR_INVADD);
+}
+
+/* returns TRUE if value changed */
+int runsub(runcxdef *ctx, runsdef *val, runsdef *val2, uint below)
+{
+ if (val->runstyp == DAT_LIST)
+ {
+ uchar *sublist;
+ int subsize;
+ int listsize;
+ int part1sz;
+
+ if (val2->runstyp == DAT_LIST)
+ {
+ uchar *p1;
+ uchar *p2;
+ uint rem1;
+ uint rem2;
+ uchar *dst;
+
+ /* reserve space for another copy of first list */
+ listsize = runsiz(val);
+ runhres2(ctx, listsize, below, val, val2);
+ dst = ctx->runcxhp + 2;
+
+ /* get pointer to first list */
+ p1 = val->runsv.runsvstr;
+ rem1 = osrp2(p1) - 2;
+ p1 += 2;
+
+ /*
+ * loop through left list, copying elements to output if
+ * not in the right list
+ */
+ for (; rem1; lstadv(&p1, &rem1))
+ {
+ int found = FALSE;
+
+ /* find current element of first list in second list */
+ p2 = val2->runsv.runsvstr;
+ rem2 = osrp2(p2) - 2;
+ p2 += 2;
+ for (; rem2; lstadv(&p2, &rem2))
+ {
+ if (*p1 == *p2)
+ {
+ int siz1 = datsiz(*p1, p1 + 1);
+ int siz2 = datsiz(*p2, p2 + 1);
+
+ if (siz1 == siz2 &&
+ (siz1 == 0 || !memcmp(p1 + 1, p2 + 1, (size_t)siz1)))
+ {
+ found = TRUE;
+ break;
+ }
+ }
+ }
+
+ /* if this element wasn't found, copy to output list */
+ if (!found)
+ {
+ uint siz;
+
+ *dst++ = *p1;
+ if ((siz = datsiz(*p1, p1 + 1)) != 0)
+ {
+ memcpy(dst, p1 + 1, siz);
+ dst += siz;
+ }
+ }
+ }
+
+ /* we've built the list; write size and we're done */
+ oswp2(ctx->runcxhp, dst - ctx->runcxhp);
+ val->runsv.runsvstr = ctx->runcxhp;
+ ctx->runcxhp = dst;
+ }
+ else if ((sublist = runfind(val->runsv.runsvstr, val2)) != 0)
+ {
+ subsize = datsiz(*sublist, sublist + 1) + 1;
+ listsize = runsiz(val);
+ part1sz = sublist - (uchar *)val->runsv.runsvstr;
+
+ runhres2(ctx, listsize - subsize, below, val, val2);
+ memcpy(ctx->runcxhp, val->runsv.runsvstr, (size_t)part1sz);
+ memcpy(ctx->runcxhp + part1sz, sublist + subsize,
+ (size_t)(listsize - subsize - part1sz));
+ oswp2(ctx->runcxhp, listsize - subsize);
+ val->runsv.runsvstr = ctx->runcxhp;
+ ctx->runcxhp += listsize - subsize;
+ }
+ else
+ {
+ return(FALSE); /* no change - value can be re-pushed */
+ }
+ }
+ else if (val->runstyp == DAT_NUMBER && val2->runstyp == DAT_NUMBER)
+ val->runsv.runsvnum -= val2->runsv.runsvnum;
+ else
+ runsig(ctx, ERR_INVSUB);
+
+ return(TRUE); /* value has changed; must be pushed anew */
+}
+
+/* return code pointer offset */
+static uint runcpsav(runcxdef *ctx, uchar *noreg *cp, objnum obj, prpnum prop)
+{
+ uint ofs;
+
+ VARUSED(prop);
+
+ /* get offset from start of object */
+ ofs = *cp - mcmobjptr(ctx->runcxmem, (mcmon)obj);
+
+ /* clear the pointer so the caller knows the object is unlocked */
+ *cp = 0;
+
+ /* unlock the object, and return the derived offset */
+ mcmunlck(ctx->runcxmem, (mcmon)obj);
+ return(ofs);
+}
+
+/* restore code pointer based on object.property */
+uchar *runcprst(runcxdef *ctx, uint ofs, objnum obj, prpnum prop)
+{
+ uchar *ptr;
+
+ VARUSED(prop);
+
+ /* lock object, and get pointer based on offset */
+ ptr = mcmlck(ctx->runcxmem, (mcmon)obj) + ofs;
+
+ return(ptr);
+}
+
+/* get offset of an element within a list */
+static uint runindofs(runcxdef *ctx, uint indx, uchar *lstp)
+{
+ uint lstsiz;
+ uchar *orgp = lstp;
+
+ /* verify that index is in range */
+ if (indx <= 0) runsig(ctx, ERR_LOWINX);
+
+ /* get list's size, and point to its data string */
+ lstsiz = osrp2(lstp) - 2;
+ lstp += 2;
+
+ /* skip the first indx-1 elements */
+ for (--indx; indx && lstsiz; --indx) lstadv(&lstp, &lstsiz);
+
+ /* if we ran out of list, the index is out of range */
+ if (!lstsiz) runsig(ctx, ERR_HIGHINX);
+
+ /* return the offset */
+ return((uint)(lstp - orgp));
+}
+
+/* push an indexed element of a list; index is tos, list is next on stack */
+static void runpind(runcxdef *ctx, uint indx, uchar *lstp)
+{
+ uchar *ele;
+ runsdef val;
+
+ /* find the element we want to push */
+ ele = lstp + runindofs(ctx, indx, lstp);
+
+ /* reserve space first, in case lstp gets moved around */
+ val.runstyp = DAT_LIST;
+ val.runsv.runsvstr = lstp;
+ runhres1(ctx, datsiz(*ele, ele + 1), 0, &val);
+ if (val.runsv.runsvstr != lstp)
+ ele = val.runsv.runsvstr + runindofs(ctx, indx, val.runsv.runsvstr);
+
+ /* push the operand */
+ runpbuf(ctx, *ele, ele + 1);
+}
+
+/*
+* Check a property to ensure that it's a data property. Throws an
+* error if the property contains a method. This is used for debugger
+* speculative evaluation to ensure that we don't call any methods from
+* within speculative expressions.
+*/
+static void runcheckpropdata(runcxdef *ctx, objnum obj, prpnum prop)
+{
+ uint pofs;
+ objnum target;
+ objdef *objptr;
+ prpdef *prpptr;
+ int typ;
+
+ /* if the object is invalid, it's an error */
+ if (obj == MCMONINV)
+ errsig(ctx->runcxerr, ERR_REQVOB);
+
+ /* get the property */
+ pofs = objgetap(ctx->runcxmem, obj, prop, &target, FALSE);
+
+ /* if there's no property, it's okay - it will just return nil */
+ if (pofs == 0)
+ return;
+
+ /* get the object */
+ objptr = mcmlck(ctx->runcxmem, target);
+
+ /* get the property */
+ prpptr = (prpdef *)(((uchar *)objptr) + pofs);
+ typ = prptype(prpptr);
+
+ /* we're done with the object's memory now */
+ mcmunlck(ctx->runcxmem, target);
+
+ /* check the type */
+ switch (typ)
+ {
+ case DAT_CODE:
+ case DAT_DSTRING:
+ /*
+ * we can't call code or evaluate (i.e., print) double-quoted
+ * strings during speculative evaluation
+ */
+ errsig(ctx->runcxerr, ERR_RTBADSPECEXPR);
+
+ default:
+ /* other types do not involve method calls, so they're okay */
+ break;
+ }
+}
+
+/* push an object's property */
+void runpprop(runcxdef *ctx, uchar *noreg *codepp,
+ objnum callobj, prpnum callprop,
+ noreg objnum obj, prpnum prop, int inh, int argc, objnum self)
+{
+ uint pofs;
+ uint saveofs = 0;
+ objdef *objptr;
+ prpdef *prpptr;
+ uchar *val;
+ int typ;
+ runsdef sval;
+ objnum target;
+ int times_through = 0;
+ int err;
+ objnum otherobj = 0;
+
+ NOREG((&obj, &codepp));
+
+ if (obj == MCMONINV) runsig(ctx, ERR_RUNNOBJ);
+
+startover:
+ pofs = objgetap(ctx->runcxmem, obj, prop, &target, inh);
+
+ /* if nothing was found, push nil */
+ if (!pofs)
+ {
+ runpush(ctx, DAT_NIL, &sval);
+ return;
+ }
+
+ /* found a property; get the prpdef, and the value and type of data */
+ objptr = mcmlck(ctx->runcxmem, target);
+ ERRBEGIN(ctx->runcxerr) /* catch errors so we can unlock object */
+
+ prpptr = (prpdef *)(((uchar *)objptr) + pofs);
+ val = prpvalp(prpptr);
+ typ = prptype(prpptr);
+
+ /* determine what to do based on property type */
+ switch (typ)
+ {
+ case DAT_CODE:
+ /* save caller's code offset - caller's object may move */
+ if (codepp)
+ saveofs = runcpsav(ctx, codepp, callobj, callprop);
+
+ /* execute the code */
+ runexe(ctx, val, self, target, prop, argc);
+
+ /* restore caller's code pointer in case object moved */
+ if (codepp)
+ *codepp = runcprst(ctx, saveofs, callobj, callprop);
+ break;
+
+ case DAT_REDIR:
+ otherobj = osrp2(val);
+ break;
+
+ case DAT_DSTRING:
+ outfmt(ctx->runcxtio, val);
+ break;
+
+ case DAT_DEMAND:
+ break;
+
default:
- return 0;
+ runpbuf(ctx, typ, val);
+ break;
+ }
+
+ /* we're done - unlock the object */
+ mcmunlck(ctx->runcxmem, target);
+
+ /* if it's redirected, redirect it now */
+ if (typ == DAT_REDIR)
+ {
+ runpprop(ctx, codepp, callobj, callprop, otherobj, prop,
+ FALSE, argc, otherobj);
}
+
+ /* if an error occurs, unlock the object, and resignal the error */
+ ERRCATCH(ctx->runcxerr, err)
+ mcmunlck(ctx->runcxmem, target);
+ if (err < ERR_RUNEXIT || err > ERR_RUNEXITOBJ)
+ dbgdump(ctx->runcxdbg); /* dump the stack */
+ errrse(ctx->runcxerr);
+ ERREND(ctx->runcxerr)
+
+ /* apply special handling for set-on-first-use data */
+ if (typ == DAT_DEMAND)
+ {
+ /*
+ * if we've already done this, the property isn't being set by
+ * the callback, so we'll never get out of this loop - abort if
+ * so
+ */
+ if (++times_through != 1)
+ runsig(ctx, ERR_DMDLOOP);
+
+ /* save caller's code offset - caller's object may move */
+ if (codepp)
+ saveofs = runcpsav(ctx, codepp, callobj, callprop);
+
+ /* invoke the callback to set the property on demand */
+ (*ctx->runcxdmd)(ctx->runcxdmc, obj, prop);
+
+ /* restore caller's code pointer */
+ if (codepp)
+ *codepp = runcprst(ctx, saveofs, callobj, callprop);
+
+ /* try again now that it's been set up */
+ goto startover;
+ }
}
-/*--------------------------------------------------------------------------*/
+/* ======================================================================== */
+/*
+* user exit callbacks
+*/
+/* External fnctions are now obsolete */
+#if 0
+static int runuftyp(runuxdef *ctx)
+{
+ return(runtostyp(ctx->runuxctx));
+}
+
+static long runufnpo(runuxdef *ctx)
+{
+ return(runpopnum(ctx->runuxctx));
+}
+
+static uchar *runufspo(runuxdef *ctx)
+{
+ return(runpopstr(ctx->runuxctx));
+}
+
+static void runufdsc(runuxdef *ctx)
+{
+ rundisc(ctx->runuxctx);
+}
+
+static void runufnpu(runuxdef *ctx, long num)
+{
+ runpnum(ctx->runuxctx, num);
+}
+
+static void runufspu(runuxdef *ctx, uchar *str)
+{
+ runsdef val;
+
+ val.runstyp = DAT_SSTRING;
+ val.runsv.runsvstr = str - 2;
+ runrepush(ctx->runuxctx, &val);
+}
+
+static void runufcspu(runuxdef *ctx, char *str)
+{
+ runpstr(ctx->runuxctx, str, (int)strlen(str), ctx->runuxargc);
+}
+
+static uchar *runufsal(runuxdef *ctx, int len)
+{
+ uchar *ret;
+
+ len += 2;
+ runhres(ctx->runuxctx, len, ctx->runuxargc);
+ ret = ctx->runuxctx->runcxhp;
+ oswp2(ret, len);
+ ret += 2;
+
+ ctx->runuxctx->runcxhp += len;
+ return(ret);
+}
+
+static void runuflpu(runuxdef *ctx, int typ)
+{
+ runsdef val;
+
+ val.runstyp = typ;
+ runrepush(ctx->runuxctx, &val);
+}
+
+#endif
+
+/* convert an osrp2 value to a signed short value */
+#define runrp2s(p) ((short)(ushort)osrp2(p))
+
+
+/* ======================================================================== */
+/*
+* execute p-code
+*/
+void runexe(runcxdef *ctx, uchar *p0, objnum self, objnum target,
+ prpnum targprop, int argc)
+{
+ uchar *noreg p = p0;
+ uchar opc; /* opcode we're currently working on */
+ runsdef val; /* stack element (for pushing) */
+ runsdef val2; /* another one (for popping in two-op instructions) */
+ uint ofs; /* offset in code of current execution */
+ prpnum prop; /* property number, when needed */
+ objnum obj; /* object number, when needed */
+ runsdef *noreg rstsp; /* sp to reset to on DISCARD instructions */
+ uchar *lstp; /* list pointer */
+ int nargc; /* argument count of called function */
+ runsdef *valp;
+ runsdef *stkval;
+ int i;
+ int brkchk;
+
+#ifndef DBG_OFF
+ int err;
+#endif
+
+ NOREG((&rstp, &p));
+
+ /* save entry SP - this is reset point until ENTER */
+ rstsp = ctx->runcxsp;
+
+#ifndef DBG_OFF
+ /*
+ * For the debugger's sake, set up an error frame so that we catch
+ * any errors thrown during p-code execution within this function.
+ * If an error occurs, and the debugger is present, we'll set the
+ * instruction pointer back to the start of the line that caused the
+ * error and enter the debugger with the error indication. If the
+ * debugger isn't present, we'll simply re-throw the error. This
+ * entire block can be compiled out of the execution engine when
+ * linking a stand-alone (non-debug) version of the run-time.
+ */
+resume_from_error:
+ ERRBEGIN(ctx->runcxerr)
+#endif /* DBG_OFF */
+
+ for (brkchk = 0;; ++brkchk)
+ {
+ /* check for break - signal if user has hit break */
+ if (brkchk == 1000)
+ {
+ brkchk = 0;
+ if (os_break()) runsig(ctx, ERR_USRINT);
+ }
+
+ opc = *p++;
+
+ switch (opc)
+ {
+ case OPCPUSHNUM:
+ val.runsv.runsvnum = osrp4s(p);
+ runpush(ctx, DAT_NUMBER, &val);
+ p += 4;
+ break;
+
+ case OPCPUSHOBJ:
+ val.runsv.runsvobj = osrp2(p);
+ runpush(ctx, DAT_OBJECT, &val);
+ p += 2;
+ break;
+
+ case OPCPUSHSELF:
+ val.runsv.runsvobj = self;
+ runpush(ctx, DAT_OBJECT, &val);
+ break;
+
+ case OPCPUSHSTR:
+ val.runsv.runsvstr = p;
+ runpush(ctx, DAT_SSTRING, &val);
+ p += osrp2(p); /* skip past string */
+ break;
+
+ case OPCPUSHLST:
+ val.runsv.runsvstr = p;
+ runpush(ctx, DAT_LIST, &val);
+ p += osrp2(p); /* skip past list */
+ break;
+
+ case OPCPUSHNIL:
+ runpush(ctx, DAT_NIL, &val);
+ break;
+
+ case OPCPUSHTRUE:
+ runpush(ctx, DAT_TRUE, &val);
+ break;
+
+ case OPCPUSHFN:
+ val.runsv.runsvobj = osrp2(p);
+ runpush(ctx, DAT_FNADDR, &val);
+ p += 2;
+ break;
+
+ case OPCPUSHPN:
+ val.runsv.runsvprp = osrp2(p);
+ runpush(ctx, DAT_PROPNUM, &val);
+ p += 2;
+ break;
+
+ case OPCNEG:
+ val.runstyp = DAT_NUMBER;
+ val.runsv.runsvnum = -runpopnum(ctx);
+ runrepush(ctx, &val);
+ break;
+
+ case OPCBNOT:
+ val.runstyp = DAT_NUMBER;
+ val.runsv.runsvnum = ~runpopnum(ctx);
+ runrepush(ctx, &val);
+ break;
+
+ case OPCNOT:
+ if (runtoslog(ctx))
+ runpush(ctx, runclog(!runpoplog(ctx)), &val);
+ else
+ runpush(ctx, runclog(runpopnum(ctx)), &val);
+ break;
+
+ case OPCADD:
+ runpop(ctx, &val2); /* right op is pushed last -> popped 1st */
+ runpop(ctx, &val);
+ runadd(ctx, &val, &val2, 2);
+ runrepush(ctx, &val);
+ break;
+
+ case OPCSUB:
+ runpop(ctx, &val2); /* right op is pushed last -> popped 1st */
+ runpop(ctx, &val);
+ (void)runsub(ctx, &val, &val2, 2);
+ runrepush(ctx, &val);
+ break;
+
+ case OPCMUL:
+ val.runstyp = DAT_NUMBER;
+ val.runsv.runsvnum = runpopnum(ctx);
+ val.runsv.runsvnum *= runpopnum(ctx);
+ runrepush(ctx, &val);
+ break;
+
+ case OPCBAND:
+ val.runstyp = DAT_NUMBER;
+ val.runsv.runsvnum = runpopnum(ctx);
+ val.runsv.runsvnum &= runpopnum(ctx);
+ runrepush(ctx, &val);
+ break;
+
+ case OPCBOR:
+ val.runstyp = DAT_NUMBER;
+ val.runsv.runsvnum = runpopnum(ctx);
+ val.runsv.runsvnum |= runpopnum(ctx);
+ runrepush(ctx, &val);
+ break;
+
+ case OPCSHL:
+ val.runstyp = DAT_NUMBER;
+ val.runsv.runsvnum = runpopnum(ctx);
+ val.runsv.runsvnum = runpopnum(ctx) << val.runsv.runsvnum;
+ runrepush(ctx, &val);
+ break;
+
+ case OPCSHR:
+ val.runstyp = DAT_NUMBER;
+ val.runsv.runsvnum = runpopnum(ctx);
+ val.runsv.runsvnum = runpopnum(ctx) >> val.runsv.runsvnum;
+ runrepush(ctx, &val);
+ break;
+
+ case OPCXOR:
+ /* allow logical ^ logical or number ^ number */
+ if (runtoslog(ctx))
+ {
+ int a, b;
+
+ /* logicals - return a logical value */
+ a = runpoplog(ctx);
+ b = runpoplog(ctx);
+ val.runstyp = runclog(a ^ b);
+ }
+ else
+ {
+ /* numeric value - return binary xor */
+ val.runstyp = DAT_NUMBER;
+ val.runsv.runsvnum = runpopnum(ctx);
+ val.runsv.runsvnum ^= runpopnum(ctx);
+ }
+ runrepush(ctx, &val);
+ break;
+
+ case OPCDIV:
+ val.runsv.runsvnum = runpopnum(ctx);
+ if (val.runsv.runsvnum == 0)
+ runsig(ctx, ERR_DIVZERO);
+ val.runsv.runsvnum = runpopnum(ctx) / val.runsv.runsvnum;
+ val.runstyp = DAT_NUMBER;
+ runrepush(ctx, &val);
+ break;
+
+ case OPCMOD:
+ val.runsv.runsvnum = runpopnum(ctx);
+ if (val.runsv.runsvnum == 0)
+ runsig(ctx, ERR_DIVZERO);
+ val.runsv.runsvnum = runpopnum(ctx) % val.runsv.runsvnum;
+ val.runstyp = DAT_NUMBER;
+ runrepush(ctx, &val);
+ break;
+
+#ifdef NEVER
+ case OPCAND:
+ if (runtostyp(ctx) == DAT_LIST)
+ runlstisect(ctx);
+ else
+ runpush(ctx, runclog(runpoplog(ctx) && runpoplog(ctx)), &val);
+ break;
+
+ case OPCOR:
+ runpush(ctx, runclog(runpoplog(ctx) || runpoplog(ctx)), &val);
+ break;
+#endif /* NEVER */
+
+ case OPCEQ:
+ runpush(ctx, runclog(runeq(ctx)), &val);
+ break;
+
+ case OPCNE:
+ runpush(ctx, runclog(!runeq(ctx)), &val);
+ break;
+
+ case OPCLT:
+ runpush(ctx, runclog(runmcmp(ctx) < 0), &val);
+ break;
+
+ case OPCLE:
+ runpush(ctx, runclog(runmcmp(ctx) <= 0), &val);
+ break;
+
+ case OPCGT:
+ runpush(ctx, runclog(runmcmp(ctx) > 0), &val);
+ break;
+
+ case OPCGE:
+ runpush(ctx, runclog(runmcmp(ctx) >= 0), &val);
+ break;
+
+ case OPCCALL:
+ {
+ objnum o;
+
+ /* get the argument count */
+ nargc = *p++;
+
+ /* ensure we have enough values to pass as arguments */
+ runcheckargc(ctx, &nargc);
+
+ /* object could move--save offset to restore 'p' after call */
+ o = osrp2(p);
+ ofs = runcpsav(ctx, &p, target, targprop);
+
+ /* execute the function */
+ runfn(ctx, o, nargc);
+
+ /* restore code pointer in case target object moved */
+ p = runcprst(ctx, ofs, target, targprop) + 2;
+ break;
+ }
+
+ case OPCGETP:
+ nargc = *p++;
+ runcheckargc(ctx, &nargc);
+ prop = osrp2(p);
+ p += 2;
+ obj = runpopobj(ctx);
+ runpprop(ctx, &p, target, targprop, obj, prop, FALSE, nargc,
+ obj);
+ break;
+
+ case OPCGETPDATA:
+ prop = osrp2(p);
+ p += 2;
+ obj = runpopobj(ctx);
+ runcheckpropdata(ctx, obj, prop);
+ runpprop(ctx, &p, target, targprop, obj, prop, FALSE, 0, obj);
+ break;
+
+ case OPCGETDBLCL:
+#ifdef DBG_OFF
+ /* non-debug mode - this will always throw an error */
+ dbgfrfind(ctx->runcxdbg, 0, 0);
+#else
+ /* debug mode - look up the local in the stack frame */
+ {
+ objnum frobj;
+ uint frofs;
+ runsdef *otherbp;
+
+ frobj = osrp2(p);
+ frofs = osrp2(p + 2);
+ otherbp = dbgfrfind(ctx->runcxdbg, frobj, frofs);
+ runrepush(ctx, otherbp + runrp2s(p + 4) - 1);
+ p += 6;
+ }
+ break;
+#endif
+
+ case OPCGETLCL:
+ runrepush(ctx, ctx->runcxbp + runrp2s(p) - 1);
+ p += 2;
+ break;
+
+ case OPCRETURN:
+ runleave(ctx, argc /* was: osrp2(p) */);
+ dbgleave(ctx->runcxdbg, DBGEXRET);
+ goto done;
+
+ case OPCRETVAL:
+ /* if there's nothing on the stack, return nil */
+ if (runtostyp(ctx) != DAT_BASEPTR)
+ runpop(ctx, &val);
+ else
+ val.runstyp = DAT_NIL;
+
+ runleave(ctx, argc /* was: osrp2(p) */);
+ runrepush(ctx, &val);
+ dbgleave(ctx->runcxdbg, DBGEXVAL);
+ goto done;
+
+ case OPCENTER:
+ /* push old base pointer and set up new one */
+ ctx->runcxsp = rstsp;
+ val.runsv.runsvstr = (uchar *)ctx->runcxbp;
+ runpush(ctx, DAT_BASEPTR, &val);
+ ctx->runcxbp = ctx->runcxsp;
+
+ /* add a trace record */
+ dbgenter(ctx->runcxdbg, ctx->runcxbp, self, target, targprop,
+ 0, argc);
+
+ /* initialize locals to nil */
+ for (i = osrp2(p); i; --i) runpush(ctx, DAT_NIL, &val);
+ p += 2; /* skip the local count operand */
+
+ /* save stack pointer - reset sp to this value on DISCARD */
+ rstsp = ctx->runcxsp;
+ break;
+
+ case OPCDISCARD:
+ ctx->runcxsp = rstsp;
+ break;
+
+ case OPCSWITCH:
+ {
+ int tostyp;
+ int match, typmatch;
+
+ runpop(ctx, &val);
+ tostyp = val.runstyp;
+ switch (tostyp)
+ {
+ case DAT_SSTRING:
+ tostyp = OPCPUSHSTR;
+ break;
+ case DAT_LIST:
+ tostyp = OPCPUSHLST;
+ break;
+ case DAT_PROPNUM:
+ tostyp = OPCPUSHPN;
+ break;
+ case DAT_FNADDR:
+ tostyp = OPCPUSHFN;
+ break;
+ case DAT_TRUE:
+ tostyp = OPCPUSHTRUE;
+ break;
+ case DAT_NIL:
+ tostyp = OPCPUSHNIL;
+ break;
+ }
+
+ p += osrp2(p); /* find the switch table */
+ i = osrp2(p); /* get number of cases */
+
+ /* look for a matching case */
+ for (match = FALSE; i && !match; --i)
+ {
+ p += 2; /* skip previous jump/size word */
+ typmatch = (*p == tostyp);
+ switch (*p++)
+ {
+ case OPCPUSHNUM:
+ match = (typmatch
+ && val.runsv.runsvnum == osrp4s(p));
+ p += 4;
+ break;
+
+ case OPCPUSHLST:
+ case OPCPUSHSTR:
+ match = (typmatch
+ && osrp2(val.runsv.runsvstr) == osrp2(p)
+ && !memcmp(val.runsv.runsvstr,
+ p, (size_t)osrp2(p)));
+ p += runrp2s(p);
+ break;
+
+ case OPCPUSHPN:
+ match = (typmatch
+ && val.runsv.runsvprp == osrp2(p));
+ p += 2;
+ break;
+
+ case OPCPUSHOBJ:
+ case OPCPUSHFN:
+ match = (typmatch
+ && val.runsv.runsvobj == osrp2(p));
+ p += 2;
+ break;
+
+ case OPCPUSHSELF:
+ match = (typmatch && val.runsv.runsvobj == self);
+ break;
+
+ case OPCPUSHTRUE:
+ case OPCPUSHNIL:
+ match = typmatch;
+ break;
+ }
+ }
+
+ if (!match) p += 2; /* if default, skip to default case */
+ p += runrp2s(p); /* wherever we left off, p points to jump */
+ break;
+ }
+
+ case OPCJMP:
+ p += runrp2s(p);
+ break;
+
+ case OPCJT:
+ if (runtoslog(ctx))
+ p += (runpoplog(ctx) ? runrp2s(p) : 2);
+ else
+ p += (runpopnum(ctx) != 0 ? runrp2s(p) : 2);
+ break;
+
+ case OPCJF:
+ if (runtoslog(ctx))
+ p += ((!runpoplog(ctx)) ? runrp2s(p) : 2);
+ else if (runtostyp(ctx) == DAT_NUMBER)
+ p += ((runpopnum(ctx) == 0) ? runrp2s(p) : 2);
+ else /* consider any other type to be true */
+ {
+ rundisc(ctx); /* throw away the item considered to be true */
+ p += 2;
+ }
+ break;
+
+ case OPCSAY:
+ outfmt(ctx->runcxtio, p);
+ p += osrp2(p); /* skip past string */
+ break;
+
+ case OPCBUILTIN:
+ {
+ int binum;
+ runsdef *stkp;
+
+ nargc = *p++;
+ runcheckargc(ctx, &nargc);
+ binum = osrp2(p);
+ ofs = runcpsav(ctx, &p, target, targprop);
+ stkp = ctx->runcxsp - nargc;
+
+ dbgenter(ctx->runcxdbg, ctx->runcxsp + 1, MCMONINV, MCMONINV,
+ (prpnum)0, binum, nargc);
+ (*ctx->runcxbi[binum])((struct bifcxdef *)ctx->runcxbcx,
+ nargc);
+ dbgleave(ctx->runcxdbg,
+ ctx->runcxsp != stkp ? DBGEXVAL : DBGEXRET);
+
+ p = runcprst(ctx, ofs, target, targprop);
+ p += 2;
+ break;
+ }
+
+ case OPCPTRCALL:
+ nargc = *p++;
+ runcheckargc(ctx, &nargc);
+ ofs = runcpsav(ctx, &p, target, targprop);
+ runfn(ctx, runpopfn(ctx), nargc);
+ p = runcprst(ctx, ofs, target, targprop);
+ break;
+
+ case OPCINHERIT:
+ nargc = *p++;
+ runcheckargc(ctx, &nargc);
+ prop = osrp2(p);
+ p += 2;
+ runpprop(ctx, &p, target, targprop, target, prop, TRUE, nargc,
+ self);
+ break;
+
+ case OPCPTRINH:
+ nargc = *p++;
+ runcheckargc(ctx, &nargc);
+ prop = runpopprp(ctx);
+ runpprop(ctx, &p, target, targprop, target, prop, TRUE, nargc,
+ self);
+ break;
+
+ case OPCPTRGETP:
+ nargc = *p++;
+ runcheckargc(ctx, &nargc);
+ prop = runpopprp(ctx);
+ obj = runpopobj(ctx);
+ runpprop(ctx, &p, target, targprop, obj, prop, FALSE, nargc,
+ obj);
+ break;
+
+ case OPCPTRGETPDATA:
+ prop = runpopprp(ctx);
+ obj = runpopobj(ctx);
+ runcheckpropdata(ctx, obj, prop);
+ runpprop(ctx, &p, target, targprop, obj, prop, FALSE, 0, obj);
+ break;
+
+ case OPCEXPINH:
+ /* inheritance from explicit superclass */
+ nargc = *p++;
+ runcheckargc(ctx, &nargc);
+ prop = osrp2(p);
+ obj = osrp2(p + 2);
+ p += 4;
+
+ /*
+ * Evaluate the property of the given object, but keeping
+ * the same 'self' as is currently in effect. Note that the
+ * 'inherit' flag is FALSE in this call, even though we're
+ * inheriting, because the opcode explicitly specifies the
+ * object we want to inherit from.
+ */
+ runpprop(ctx, &p, target, targprop, obj, prop, FALSE,
+ nargc, self);
+ break;
+
+ case OPCEXPINHPTR:
+ nargc = *p++;
+ runcheckargc(ctx, &nargc);
+ prop = runpopprp(ctx);
+ obj = osrp2(p);
+ p += 2;
+ runpprop(ctx, &p, target, targprop, obj, prop, FALSE,
+ nargc, self);
+ break;
+
+ case OPCPASS:
+ prop = osrp2(p);
+ runleave(ctx, 0);
+ dbgleave(ctx->runcxdbg, DBGEXPASS);
+ runpprop(ctx, &p, target, targprop, target, prop, TRUE, argc,
+ self);
+ goto done;
+
+ case OPCEXIT:
+ errsig(ctx->runcxerr, ERR_RUNEXIT);
+ /* NOTREACHED */
+
+ case OPCABORT:
+ errsig(ctx->runcxerr, ERR_RUNABRT);
+ /* NOTREACHED */
+
+ case OPCASKDO:
+ errsig(ctx->runcxerr, ERR_RUNASKD);
+ /* NOTREACHED */
+
+ case OPCASKIO:
+ errsig1(ctx->runcxerr, ERR_RUNASKI, ERRTINT, osrp2(p));
+ /* NOTREACHED */
+
+ case OPCJE:
+ p += (runeq(ctx) ? runrp2s(p) : 2);
+ break;
+
+ case OPCJNE:
+ p += (!runeq(ctx) ? runrp2s(p) : 2);
+ break;
+
+ case OPCJGT:
+ p += (runmcmp(ctx) > 0 ? runrp2s(p) : 2);
+ break;
+
+ case OPCJGE:
+ p += (runmcmp(ctx) >= 0 ? runrp2s(p) : 2);
+ break;
+
+ case OPCJLT:
+ p += (runmcmp(ctx) < 0 ? runrp2s(p) : 2);
+ break;
+
+ case OPCJLE:
+ p += (runmcmp(ctx) <= 0 ? runrp2s(p) : 2);
+ break;
+
+ case OPCJNAND:
+ p += (!(runpoplog(ctx) && runpoplog(ctx)) ? runrp2s(p) : 2);
+ break;
+
+ case OPCJNOR:
+ p += (!(runpoplog(ctx) || runpoplog(ctx)) ? runrp2s(p) : 2);
+ break;
+
+ case OPCGETPSELF:
+ nargc = *p++;
+ runcheckargc(ctx, &nargc);
+ prop = osrp2(p);
+ p += 2;
+ runpprop(ctx, &p, target, targprop, self, prop, FALSE, nargc,
+ self);
+ break;
+
+ case OPCGETPSELFDATA:
+ prop = osrp2(p);
+ p += 2;
+ runcheckpropdata(ctx, self, prop);
+ runpprop(ctx, &p, target, targprop, self, prop, FALSE, 0, self);
+ break;
+
+ case OPCGETPPTRSELF:
+ nargc = *p++;
+ runcheckargc(ctx, &nargc);
+ prop = runpopprp(ctx);
+ runpprop(ctx, &p, target, targprop, self, prop, FALSE, nargc,
+ self);
+ break;
+
+ case OPCGETPOBJ:
+ nargc = *p++;
+ runcheckargc(ctx, &nargc);
+ obj = osrp2(p);
+ prop = osrp2(p + 2);
+ p += 4;
+ runpprop(ctx, &p, target, targprop, obj, prop, FALSE, nargc,
+ obj);
+ break;
+
+ case OPCINDEX:
+ i = runpopnum(ctx); /* get index */
+ lstp = runpoplst(ctx); /* get the list */
+ runpind(ctx, i, lstp);
+ break;
+
+ case OPCJST:
+ if (runtostyp(ctx) == DAT_TRUE)
+ p += runrp2s(p);
+ else
+ {
+ (void)runpoplog(ctx);
+ p += 2;
+ }
+ break;
+
+ case OPCJSF:
+ if (runtostyp(ctx) == DAT_NIL ||
+ (runtostyp(ctx) == DAT_NUMBER &&
+ (ctx->runcxsp - 1)->runsv.runsvnum == 0))
+ p += runrp2s(p);
+ else
+ {
+ runpop(ctx, &val);
+ p += 2;
+ }
+ break;
+
+ case OPCCALLEXT:
+ {
+#if 0 // external functions are now obsolete
+ static runufdef uf =
+ {
+ runuftyp, runufnpo, runufspo, runufdsc,
+ runufnpu, runufspu, runufcspu, runufsal,
+ runuflpu
+ };
+ int fn;
+ runxdef *ex;
+
+ runuxdef ux;
+
+ /* set up callback context */
+ ux.runuxctx = ctx;
+ ux.runuxvec = &uf;
+ ux.runuxargc = *p++;
+
+ fn = osrp2(p);
+ p += 2;
+ ex = &ctx->runcxext[fn];
+
+ if (!ex->runxptr)
+ {
+ if ((ex->runxptr = os_exfil(ex->runxnam)) == 0)
+ runsig1(ctx, ERR_EXTLOAD, ERRTSTR, ex->runxnam);
+ }
+ if (os_excall(ex->runxptr, &ux))
+ runsig1(ctx, ERR_EXTRUN, ERRTSTR, ex->runxnam);
+#else
+ /* external functions are obsolete - throw an error */
+ runxdef *ex;
+ p += 1;
+ ex = &ctx->runcxext[osrp2(p)];
+ p += 2;
+ runsig1(ctx, ERR_EXTRUN, ERRTSTR, ex->runxnam);
+#endif
+ }
+ break;
+
+ case OPCDBGRET:
+ goto done;
+
+ case OPCCONS:
+ {
+ uint totsiz;
+ uint oldsiz;
+ uint tot;
+ uint cursiz;
+ runsdef lstend;
+
+ tot = i = osrp2(p); /* get # of items to build into list */
+ p += 2;
+
+ /* reserve space for initial list (w/length word only) */
+ runhres(ctx, 2, 0);
+
+ /*
+ * Set up value to point to output list, making room
+ * for length prefix. Remember size-so-far separately.
+ */
+ lstend.runstyp = DAT_LIST;
+ lstend.runsv.runsvstr = ctx->runcxhp;
+ ctx->runcxhp += 2;
+ totsiz = 2;
+
+ while (i--)
+ {
+ runpop(ctx, &val); /* get next value off stack */
+ cursiz = runsiz(&val);
+
+ /*
+ * Set up to allocate space. Before doing so, make
+ * sure the list under construction is valid, to
+ * ensure that it stays around after garbage
+ * collection.
+ */
+ oldsiz = totsiz;
+ totsiz += cursiz + 1;
+ oswp2(lstend.runsv.runsvstr, oldsiz);
+ ctx->runcxhp = lstend.runsv.runsvstr + oldsiz;
+ runhres2(ctx, cursiz + 1, tot - i, &val, &lstend);
+
+ /* write this item to the list */
+ runputbuf(lstend.runsv.runsvstr + oldsiz, &val);
+ }
+ oswp2(lstend.runsv.runsvstr, totsiz);
+ ctx->runcxhp = lstend.runsv.runsvstr + totsiz;
+ runrepush(ctx, &lstend);
+ }
+ break;
+
+ case OPCARGC:
+ val.runsv.runsvnum = argc;
+ runpush(ctx, DAT_NUMBER, &val);
+ break;
+
+ case OPCCHKARGC:
+ if ((*p & 0x80) ? argc < (*p & 0x7f) : argc != *p)
+ {
+ char namebuf[128];
+ size_t namelen;
+
+ /*
+ * debugger is present - look up the name of the current
+ * function or method, so that we can report it in the
+ * error message
+ */
+ if (targprop == 0)
+ {
+ /* we're in a function */
+ namelen = dbgnam(ctx->runcxdbg, namebuf, TOKSTFUNC,
+ target);
+ }
+ else
+ {
+ /* we're in an object.method */
+ namelen = dbgnam(ctx->runcxdbg, namebuf, TOKSTOBJ,
+ target);
+ namebuf[namelen++] = '.';
+ namelen += dbgnam(ctx->runcxdbg, namebuf + namelen,
+ TOKSTPROP, targprop);
+ }
+ namebuf[namelen] = '\0';
+ runsig1(ctx, ERR_ARGC, ERRTSTR, namebuf);
+ }
+ ++p;
+ break;
+
+ case OPCLINE:
+ case OPCBP:
+ {
+ uchar *ptr = mcmobjptr(ctx->runcxmem, (mcmon)target);
+ uchar instr;
+
+ /* set up the debugger frame record for this line */
+ dbgframe(ctx->runcxdbg, osrp2(p + 1), p - ptr);
+
+ /* remember the instruction */
+ instr = *(p - 1);
+
+ /* remember the offset of the line record */
+ ctx->runcxlofs = ofs = (p + 2 - ptr);
+
+ /* skip to the next instruction */
+ p += *p;
+
+ /* let the debugger take over, if it wants to */
+ dbgssi(ctx->runcxdbg, ofs, instr, 0, &p);
+ break;
+ }
+
+ case OPCFRAME:
+ /* this is a frame record - just jump past it */
+ p += osrp2(p);
+ break;
+
+ case OPCASI_MASK | OPCASIDIR | OPCASILCL:
+ runpop(ctx, &val);
+ OSCPYSTRUCT(*(ctx->runcxbp + runrp2s(p) - 1), val);
+ stkval = &val;
+ p += 2;
+ goto no_assign;
+
+ case OPCASI_MASK | OPCASIDIR | OPCASIPRP:
+ obj = runpopobj(ctx);
+ prop = osrp2(p);
+ p += 2;
+ runpop(ctx, &val);
+ stkval = valp = &val;
+ goto assign_property;
+
+ case OPCASI_MASK | OPCASIDIR | OPCASIPRPPTR:
+ prop = runpopprp(ctx);
+ obj = runpopobj(ctx);
+ runpop(ctx, &val);
+ stkval = valp = &val;
+ goto assign_property;
+
+ case OPCNEW:
+ run_new(ctx, &p, target, targprop);
+ break;
+
+ case OPCDELETE:
+ run_delete(ctx, &p, target, targprop);
+ break;
+
+ default:
+ if ((opc & OPCASI_MASK) == OPCASI_MASK)
+ {
+ runsdef val3;
+ int asityp;
+ int asiext;
+ int lclnum;
+
+ valp = &val;
+ stkval = &val;
+
+ asityp = (opc & OPCASITYP_MASK);
+ if (asityp == OPCASIEXT)
+ asiext = *p++;
+
+ /* get list element/property number if needed */
+ switch (opc & OPCASIDEST_MASK)
+ {
+ case OPCASIPRP:
+ obj = runpopobj(ctx);
+ prop = osrp2(p);
+ p += 2;
+ break;
+
+ case OPCASIPRPPTR:
+ prop = runpopprp(ctx);
+ obj = runpopobj(ctx);
+ break;
+
+ case OPCASIIND:
+ i = runpopnum(ctx);
+ lstp = runpoplst(ctx);
+ break;
+
+ case OPCASILCL:
+ lclnum = runrp2s(p);
+ p += 2;
+ break;
+ }
+
+ if (asityp != OPCASIDIR)
+ {
+ /* we have an <op>= operator - get lval, modify, & set */
+ switch (opc & OPCASIDEST_MASK)
+ {
+ case OPCASILCL:
+ OSCPYSTRUCT(val, *(ctx->runcxbp + lclnum - 1));
+ break;
+
+ case OPCASIPRP:
+ case OPCASIPRPPTR:
+ runpprop(ctx, &p, target, targprop, obj, prop,
+ FALSE, 0, obj);
+ runpop(ctx, &val);
+ break;
+
+ case OPCASIIND:
+ runpind(ctx, i, lstp);
+ runpop(ctx, &val);
+ break;
+ }
+
+ /* if saving pre-inc/dec value, get the value now */
+ if ((opc & OPCASIPRE_MASK) == OPCASIPOST)
+ {
+ OSCPYSTRUCT(val3, val);
+ stkval = &val3;
+ }
+ }
+
+ /* get rvalue, except for inc/dec operations */
+ if (asityp != OPCASIINC && asityp != OPCASIDEC)
+ runpop(ctx, &val2);
+
+ /* now apply operation to lvalue using rvalue */
+ switch (asityp)
+ {
+ case OPCASIADD:
+ if ((opc & OPCASIIND) != 0)
+ {
+ runsdef val4;
+
+ /*
+ * we're adding to an indexed value out of a list -
+ * we need to make sure the list is protected from
+ * garbage collection, so push it back on the stack
+ * while we're working
+ */
+ val4.runstyp = DAT_LIST;
+ val4.runsv.runsvstr = lstp;
+ runrepush(ctx, &val4);
+
+ /* carry out the addition */
+ runadd(ctx, &val, &val2, 2);
+
+ /*
+ * in case the list got moved during garbage
+ * collection, retrieve it from the stack
+ */
+ lstp = runpoplst(ctx);
+ }
+ else
+ {
+ /* no list indexing - just carry out the addition */
+ runadd(ctx, &val, &val2, 2);
+ }
+ break;
+
+ case OPCASISUB:
+ if ((opc & OPCASIIND) != 0)
+ {
+ runsdef val4;
+ int result;
+
+ /* as with adding, protect the list from GC */
+ val4.runstyp = DAT_LIST;
+ val4.runsv.runsvstr = lstp;
+ runrepush(ctx, &val4);
+
+ /* carry out the subtraction and note the result */
+ result = runsub(ctx, &val, &val2, 2);
+
+ /* recover the list pointer */
+ lstp = runpoplst(ctx);
+
+ /* check to see if we have an assignment */
+ if (!result)
+ goto no_assign;
+ }
+ else
+ {
+ /* no list indexing - just do the subtraction */
+ if (!runsub(ctx, &val, &val2, 2))
+ goto no_assign;
+ }
+ break;
+
+ case OPCASIMUL:
+ if (val.runstyp != DAT_NUMBER
+ || val2.runstyp != DAT_NUMBER)
+ runsig(ctx, ERR_REQNUM);
+ val.runsv.runsvnum *= val2.runsv.runsvnum;
+ break;
+
+ case OPCASIDIV:
+ if (val.runstyp != DAT_NUMBER
+ || val2.runstyp != DAT_NUMBER)
+ runsig(ctx, ERR_REQNUM);
+ if (val2.runsv.runsvnum == 0)
+ runsig(ctx, ERR_DIVZERO);
+ val.runsv.runsvnum /= val2.runsv.runsvnum;
+ break;
+
+ case OPCASIINC:
+ if (val.runstyp != DAT_NUMBER)
+ runsig(ctx, ERR_REQNUM);
+ ++(val.runsv.runsvnum);
+ break;
+
+ case OPCASIDEC:
+ if (val.runstyp != DAT_NUMBER)
+ runsig(ctx, ERR_REQNUM);
+ --(val.runsv.runsvnum);
+ break;
+
+ case OPCASIDIR:
+ valp = stkval = &val2;
+ break;
+
+ case OPCASIEXT:
+ switch (asiext)
+ {
+ case OPCASIMOD:
+ if (val.runstyp != DAT_NUMBER
+ || val2.runstyp != DAT_NUMBER)
+ runsig(ctx, ERR_REQNUM);
+ if (val2.runsv.runsvnum == 0)
+ runsig(ctx, ERR_DIVZERO);
+ val.runsv.runsvnum %= val2.runsv.runsvnum;
+ break;
+
+ case OPCASIBAND:
+ if ((val.runstyp == DAT_TRUE
+ || val.runstyp == DAT_NIL)
+ && (val2.runstyp == DAT_TRUE
+ || val2.runstyp == DAT_NIL))
+ {
+ int a, b;
+
+ a = (val.runstyp == DAT_TRUE ? 1 : 0);
+ b = (val2.runstyp == DAT_TRUE ? 1 : 0);
+ val.runstyp = runclog(a && b);
+ }
+ else if (val.runstyp == DAT_NUMBER
+ && val2.runstyp == DAT_NUMBER)
+ val.runsv.runsvnum &= val2.runsv.runsvnum;
+ else
+ runsig(ctx, ERR_REQNUM);
+ break;
+
+ case OPCASIBOR:
+ if ((val.runstyp == DAT_TRUE
+ || val.runstyp == DAT_NIL)
+ && (val2.runstyp == DAT_TRUE
+ || val2.runstyp == DAT_NIL))
+ {
+ int a, b;
+
+ a = (val.runstyp == DAT_TRUE ? 1 : 0);
+ b = (val2.runstyp == DAT_TRUE ? 1 : 0);
+ val.runstyp = runclog(a || b);
+ }
+ else if (val.runstyp == DAT_NUMBER
+ && val2.runstyp == DAT_NUMBER)
+ val.runsv.runsvnum |= val2.runsv.runsvnum;
+ else
+ runsig(ctx, ERR_REQNUM);
+ break;
+
+ case OPCASIXOR:
+ if ((val.runstyp == DAT_TRUE || val.runstyp == DAT_NIL)
+ && (val2.runstyp == DAT_TRUE
+ || val2.runstyp == DAT_NIL))
+ {
+ int a, b;
+
+ a = (val.runstyp == DAT_TRUE ? 1 : 0);
+ b = (val2.runstyp == DAT_TRUE ? 1 : 0);
+ val.runstyp = runclog(a ^ b);
+ }
+ else if (val.runstyp == DAT_NUMBER
+ && val2.runstyp == DAT_NUMBER)
+ val.runsv.runsvnum ^= val2.runsv.runsvnum;
+ else
+ runsig(ctx, ERR_REQNUM);
+ break;
+
+ case OPCASISHL:
+ if (val.runstyp != DAT_NUMBER
+ || val2.runstyp != DAT_NUMBER)
+ runsig(ctx, ERR_REQNUM);
+ val.runsv.runsvnum <<= val2.runsv.runsvnum;
+ break;
+
+ case OPCASISHR:
+ if (val.runstyp != DAT_NUMBER
+ || val2.runstyp != DAT_NUMBER)
+ runsig(ctx, ERR_REQNUM);
+ val.runsv.runsvnum >>= val2.runsv.runsvnum;
+ break;
+
+ default:
+ runsig(ctx, ERR_INVOPC);
+ }
+ break;
+
+ default:
+ runsig(ctx, ERR_INVOPC);
+ }
+
+ /* write the rvalue at *valp to the lvalue */
+ switch (opc & OPCASIDEST_MASK)
+ {
+ case OPCASILCL:
+ OSCPYSTRUCT(*(ctx->runcxbp + lclnum - 1), *valp);
+ break;
+
+ case OPCASIPRP:
+ case OPCASIPRPPTR:
+ assign_property:
+ {
+ void *valbuf;
+ uchar outbuf[4];
+
+ switch (valp->runstyp)
+ {
+ case DAT_LIST:
+ case DAT_SSTRING:
+ valbuf = valp->runsv.runsvstr;
+ break;
+
+ case DAT_NUMBER:
+ valbuf = outbuf;
+ oswp4s(outbuf, valp->runsv.runsvnum);
+ break;
+
+ case DAT_OBJECT:
+ case DAT_FNADDR:
+ valbuf = outbuf;
+ oswp2(outbuf, valp->runsv.runsvobj);
+ break;
+
+ case DAT_PROPNUM:
+ valbuf = outbuf;
+ oswp2(outbuf, valp->runsv.runsvprp);
+ break;
+
+ default:
+ valbuf = &valp->runsv;
+ break;
+ }
+
+ ofs = runcpsav(ctx, &p, target, targprop);
+ objsetp(ctx->runcxmem, obj, prop, valp->runstyp,
+ valbuf, ctx->runcxundo);
+ p = runcprst(ctx, ofs, target, targprop);
+ break;
+ }
+
+ case OPCASIIND:
+ {
+ uint newtot;
+ uint newsiz;
+ uint remsiz;
+ uint delsiz;
+ uchar *delp;
+ uchar *remp;
+
+ /* compute sizes and pointers to various parts */
+ ofs = runindofs(ctx, i, lstp);
+ delp = lstp + ofs; /* ptr to item to replace */
+ delsiz = datsiz(*delp, delp + 1); /* size of *delp */
+ remsiz = osrp2(lstp) - ofs - delsiz - 1;
+ newsiz = runsiz(valp); /* size of new item */
+ newtot = osrp2(lstp) + newsiz - delsiz; /* new tot */
+
+ /* reserve space for the new list & copy first part */
+ {
+ runsdef rval3;
+
+ /* make sure lstp stays valid before and after */
+ rval3.runstyp = DAT_LIST;
+ rval3.runsv.runsvstr = lstp;
+ runhres3(ctx, newtot, 3, &val, &val2, &rval3);
+
+ /* update all of the pointers within lstp */
+ lstp = rval3.runsv.runsvstr;
+ delp = lstp + ofs;
+ remp = lstp + ofs + delsiz + 1;
+ }
+ memcpy(ctx->runcxhp + 2, lstp + 2, (size_t)(ofs - 2));
+
+ /* set size of new list */
+ oswp2(ctx->runcxhp, newtot);
+
+ /* copy new item into buffer */
+ runputbuf(ctx->runcxhp + ofs, valp);
+
+ /* copy remainder and update heap pointer */
+ memcpy(ctx->runcxhp + ofs + newsiz + 1, remp,
+ (size_t)remsiz);
+ val.runstyp = DAT_LIST;
+ val.runsv.runsvstr = ctx->runcxhp;
+ stkval = &val;
+ ctx->runcxhp += newtot;
+ break;
+ }
+ }
+
+ no_assign: /* skip assignment - operation didn't change value */
+ if (*p == OPCDISCARD)
+ {
+ /* next assignment is DISCARD - deal with it now */
+ ++p;
+ ctx->runcxsp = rstsp;
+ }
+ else
+ runrepush(ctx, stkval);
+ }
+ else
+ errsig(ctx->runcxerr, ERR_INVOPC);
+ }
+ }
+
+ /*
+ * come here to return - don't use 'return' directly, since that
+ * would not properly exit the error frame
+ */
+done:;
+
+#ifndef DBG_OFF
+ /*
+ * Come here to catch any errors that occur during execution of this
+ * p-code
+ */
+ ERRCATCH(ctx->runcxerr, err)
+ {
+ /*
+ * if the debugger isn't present, or we're already in the
+ * debugger, or if the debugger can't resume from errors, or if
+ * we're not in user code (in which case the debugger can't
+ * resume from this error even if it normally could resume from
+ * an error), simply re-signal the error
+ */
+ if (!dbgpresent()
+ || ctx->runcxdbg->dbgcxfcn == 0
+ || !dbgu_err_resume(ctx->runcxdbg)
+ || (ctx->runcxdbg->dbgcxflg & DBGCXFIND) != 0)
+ errrse(ctx->runcxerr);
+
+ /* check the error code */
+ switch (err)
+ {
+ case ERR_RUNEXIT:
+ case ERR_RUNABRT:
+ case ERR_RUNASKD:
+ case ERR_RUNASKI:
+ case ERR_RUNQUIT:
+ case ERR_RUNRESTART:
+ case ERR_RUNEXITOBJ:
+ /* don't trap these errors - resignal it immediately */
+ errrse(ctx->runcxerr);
+
+ default:
+ /* trap other errors to the debugger */
+ break;
+ }
+
+ /* if the object was unlocked, re-lock it */
+ if (p == 0)
+ mcmlck(ctx->runcxmem, target);
+
+ /* set up after the last OPCLINE instruction */
+ p = mcmobjptr(ctx->runcxmem, (mcmon)target) + ctx->runcxlofs - 2;
+ p += *p;
+
+ /*
+ * Keep the current error's arguments around for handling
+ * outside of this handler, since we'll need them in dbgssi.
+ */
+ errkeepargs(ctx->runcxerr);
+
+ /* enter the debugger with the error code */
+ dbgssi(ctx->runcxdbg, ctx->runcxlofs, OPCLINE, err, &p);
+
+ /* check the error again */
+ switch (err)
+ {
+ case ERR_ARGC:
+ /* we can't continue from this - simply return */
+ break;
+
+ default:
+ /* resume execution */
+ goto resume_from_error;
+ }
+ }
+ ERREND(ctx->runcxerr);
+#endif /* DBG_OFF */
+}
+
+/*
+* Signal a run-time error. This function first calls the debugger
+* single-step function to allow the debugger to trap the error, then
+* signals the error as usual when the debugger returns.
+*/
+void runsign(runcxdef *ctx, int err)
+{
+ /*
+ * If the debugger isn't capable of resuming from a run-time error,
+ * trap to the debugger now so that the user can see what happened.
+ * Do not trap to the debugger here if the debugger can resume from
+ * an error; instead, we'll trap in the p-code loop, since we'll be
+ * able to resume execution from the point of the error.
+ *
+ * Note that we can't resume from an error when there's no stack
+ * frame, so we'll trap to the debugger here in that case.
+ */
+ if (ctx->runcxdbg->dbgcxfcn == 0
+ || !dbgu_err_resume(ctx->runcxdbg))
+ dbgssi(ctx->runcxdbg, ctx->runcxlofs, OPCLINE, err, 0);
+
+ /* signal the error */
+ errsign(ctx->runcxerr, err, "TADS");
+}
} // End of namespace TADS2
} // End of namespace TADS
diff --git a/engines/glk/tads/tads2/run.h b/engines/glk/tads/tads2/run.h
index b6694bf..71b8d78 100644
--- a/engines/glk/tads/tads2/run.h
+++ b/engines/glk/tads/tads2/run.h
@@ -46,14 +46,10 @@ namespace Glk {
namespace TADS {
namespace TADS2 {
-/* Forward declarations */
-struct runcxdef;
-struct runufdef;
-struct voccxdef;
+/* forward declarations */
+struct bifcxdef;
-/**
- * Stack element - the stack is an array of these structures
- */
+/* stack element - the stack is an array of these structures */
struct runsdef {
uchar runstyp; /* type of element */
union {
@@ -61,50 +57,36 @@ struct runsdef {
objnum runsvobj; /* object value */
prpnum runsvprp; /* property number value */
uchar *runsvstr; /* string/list value */
- } runsv;
-
-
- /**
- * Determine size of a data item
- */
- int runsiz() const;
+ } runsv;
};
-/**
- * External function control structure
- */
+/* external function control structure */
struct runxdef {
char runxnam[TOKNAMMAX + 1]; /* name of external function */
int (*runxptr)(void *); /* pointer to memory containing code */
};
-/**
- * External function context structure - passed to user exits
- */
+/* external function context structure - passed to user exits */
struct runuxdef {
- runcxdef *runuxctx; /* run-time context */
- runufdef *runuxvec; /* vector of functions */
- int runuxargc; /* count of arguments to function */
+ struct runcxdef osfar_t *runuxctx; /* run-time context */
+ struct runufdef osfar_t *runuxvec; /* vector of functions */
+ int runuxargc; /* count of arguments to function */
};
-/**
- * External function callback vector
- */
+/* external function callback vector */
struct runufdef {
- int (*runuftyp)(runuxdef *); /* type of top of stack */
- long (*runufnpo)(runuxdef *); /* pop a number */
- uchar *(*runufspo)(runuxdef *); /* pop a string */
- void (*runufdsc)(runuxdef *); /* discard item at top of stack */
- void (*runufnpu)(runuxdef *, long); /* push a number */
- void (*runufspu)(runuxdef *, uchar *); /* push alloc'd string */
- void (*runufcspu)(runuxdef *, char *); /* push a C-string */
- uchar *(*runufsal)(runuxdef *, int); /* allocate a new string */
- void (*runuflpu)(runuxdef *, int);/* push DAT_TRUE or DAT_NIL */
+ int (osfar_t *runuftyp)(runuxdef *); /* type of top of stack */
+ long (osfar_t *runufnpo)(runuxdef *); /* pop a number */
+ uchar *(osfar_t *runufspo)(runuxdef *); /* pop a string */
+ void (osfar_t *runufdsc)(runuxdef *); /* discard item at top of stack */
+ void (osfar_t *runufnpu)(runuxdef *, long); /* push a number */
+ void (osfar_t *runufspu)(runuxdef *, uchar *); /* push alloc'd string */
+ void (osfar_t *runufcspu)(runuxdef *, char *); /* push a C-string */
+ uchar *(osfar_t *runufsal)(runuxdef *, int); /* allocate a new string */
+ void (osfar_t *runuflpu)(runuxdef *, int);/* push DAT_TRUE or DAT_NIL */
};
-/**
- * Execution context
- */
+/* execution context */
struct runcxdef {
errcxdef *runcxerr; /* error management context */
mcmcxdef *runcxmem; /* cache manager context for object references */
@@ -120,8 +102,8 @@ struct runcxdef {
void *runcxbcx; /* context for built-in callback functions */
void (**runcxbi)(struct bifcxdef *ctx, int argc);
/* built-in functions */
- dbgcxdef *runcxdbg; /* debugger context */
- voccxdef *runcxvoc; /* player command parser context */
+ struct dbgcxdef *runcxdbg; /* debugger context */
+ struct voccxdef *runcxvoc; /* player command parser context */
void (*runcxdmd)(void *ctx, objnum obj, prpnum prp);
/* demand-loader callback function */
void *runcxdmc; /* demand-loader callback context */
@@ -130,120 +112,66 @@ struct runcxdef {
uint runcxlofs; /* offset of last line record encountered */
char *runcxgamename; /* name of the .GAM file */
char *runcxgamepath; /* absolute directory path of .GAM file */
-
- /**
- * Execute a function, given the function object number
- */
- void runfn(noreg objnum objn, int argc);
-
- /**
- * Execute p-code given a pointer to the code. p is the actual pointer
- * to the first byte of code to be executed. self is the object to be
- * used for the special 'self' pseudo-object, and target is the object
- * whose data are actually being executed. targprop is the property being
- * executed; 0 is used for functions.
- */
- void runexe(uchar *p, objnum self, objnum target,
- prpnum targprop, int argc);
-
- /**
- * Push a value onto the stack
- */
- void runpush(dattyp typ, runsdef *val);
-
- /**
- * Push a value onto the stack that's already in the heap
- */
- void runrepush(runsdef *val);
-
- /**
- * Push a number onto the stack
- */
- void runpnum(long val);
-
- /**
- * Push an object onto the stack
- */
- void runpobj(objnum obj);
-
- /**
- * push nil
- */
- void runpnil(runcxdef *ctx);
-
- /**
- * push a value onto the stack from a buffer (propdef, list)
- */
- void runpbuf(int typ, void *val);
-
- /**
- * Push a counted-length string onto the stack
- */
- void runpstr(char *str, int len, int sav);
-
- /**
- * Push a C-style string onto the stack, converting escape codes. If
- * the character contains backslashes, newline, or tab characters, we'll
- * convert these characters to their escaped equivalent.
- */
- void runpushcstr(char *str, size_t len, int sav);
-
- /**
- * Push a property onto the stack. codepp is a pointer to the caller's
- * code pointer, which will be updated if necessary; callobj and
- * callofsp are the object and starting offset within the object of the
- * code being executed by the caller, which are needed to update
- * *codepp. Property 0 is used if a function is being executed. obj
- * and prop are the object and property number whose value is to be
- * pushed. If 'inh' is TRUE, it means that only a property inherited
- * by 'obj' is to be considered; this is used for "pass"/"inherited"
- * operations, with the current target object given as 'obj'.
- */
- void runpprop(uchar *noreg *codepp, objnum callobj,
- prpnum callprop, noreg objnum obj, prpnum prop, int inh,
- int argc, objnum self);
-
- /**
- * compare magnitudes of numbers/strings on top of stack; strcmp-like value
- */
- int runmcmp(runcxdef *ctx);
-
- /**
- * True if items at top of stack are equal, FALSE otherwise
- */
- int runeq(runcxdef *ctx);
-
- /**
- * Garbage collect heap, making sure 'siz' bytes are available afterwards
- */
- void runhcmp(uint siz, uint below,
- runsdef *val1, runsdef *val2, runsdef *val3);
-
- /**
- * Find a sublist within a list, returning pointer to sublist or null
- */
- uchar *runfind(uchar *list, runsdef *item);
-
- /**
- * Restore code pointer from object.property + offset
- */
- uchar *runcprst(uint ofs, objnum obj, prpnum prop);
-
- /**
- * Add two runsdef values, returning result in *val
- */
- void runadd(runsdef *val2, uint below);
-
- /**
- * Subtract val2 from val, returning result in *val; return TRUE if
- * value changed, FALSE otherwise (this is returned when subtracting
- * something from a list that isn't in the list)
- */
- int runsub(runsdef *val2, uint below);
};
+/* execute a function, given the function object number */
+void runfn(runcxdef *ctx, noreg objnum objn, int argc);
+
+/*
+ * Execute p-code given a pointer to the code. p is the actual pointer
+ * to the first byte of code to be executed. self is the object to be
+ * used for the special 'self' pseudo-object, and target is the object
+ * whose data are actually being executed. targprop is the property being
+ * executed; 0 is used for functions.
+ */
+void runexe(runcxdef *ctx, uchar *p, objnum self, objnum target,
+ prpnum targprop, int argc);
+
+/* push a value onto the stack */
+void runpush(runcxdef *ctx, dattyp typ, runsdef *val);
+
+/* push a value onto the stack that's already in the heap */
+void runrepush(runcxdef *ctx, runsdef *val);
+
+/* push a number onto the stack */
+void runpnum(runcxdef *ctx, long val);
+
+/* push an object onto the stack */
+void runpobj(runcxdef *ctx, objnum obj);
+
+/* push nil */
+void runpnil(runcxdef *ctx);
+
+/* push a value onto the stack from a buffer (propdef, list) */
+void runpbuf(runcxdef *ctx, int typ, void *val);
+
+/* push a counted-length string onto the stack */
+void runpstr(runcxdef *ctx, char *str, int len, int sav);
+
+/*
+ * Push a C-style string onto the stack, converting escape codes. If
+ * the character contains backslashes, newline, or tab characters, we'll
+ * convert these characters to their escaped equivalent.
+ */
+void runpushcstr(runcxdef *ctx, char *str, size_t len, int sav);
+
+/*
+ * Push a property onto the stack. codepp is a pointer to the caller's
+ * code pointer, which will be updated if necessary; callobj and
+ * callofsp are the object and starting offset within the object of the
+ * code being executed by the caller, which are needed to update
+ * *codepp. Property 0 is used if a function is being executed. obj
+ * and prop are the object and property number whose value is to be
+ * pushed. If 'inh' is TRUE, it means that only a property inherited
+ * by 'obj' is to be considered; this is used for "pass"/"inherited"
+ * operations, with the current target object given as 'obj'.
+ */
+void runpprop(runcxdef *ctx, uchar *noreg *codepp, objnum callobj,
+ prpnum callprop, noreg objnum obj, prpnum prop, int inh,
+ int argc, objnum self);
+
/* top level runpprop, when caller is not executing in an object */
-/* void runppr(objnum obj, prpnum prp, int argc); */
+/* void runppr(runcxdef *ctx, objnum obj, prpnum prp, int argc); */
#define runppr(ctx, obj, prp, argc) \
runpprop(ctx, (uchar **)0, (objnum)0, (prpnum)0, obj, prp, FALSE, argc, obj)
@@ -252,7 +180,7 @@ struct runcxdef {
#define rundisc(ctx) (runstkund(ctx), (--((ctx)->runcxsp)))
/* pop the top element on the stack */
-/* void runpop(runsdef *val); */
+/* void runpop(runcxdef *ctx, runsdef *val); */
#define runpop(ctx, v) \
(runstkund(ctx), memcpy(v, (--((ctx)->runcxsp)), (size_t)sizeof(runsdef)))
@@ -327,6 +255,17 @@ struct runcxdef {
/* int runclog(int log); */
#define runclog(l) ((l) ? DAT_TRUE : DAT_NIL)
+/* compare magnitudes of numbers/strings on top of stack; strcmp-like value */
+int runmcmp(runcxdef *ctx);
+
+/* TRUE if items at top of stack are equal, FALSE otherwise */
+int runeq(runcxdef *ctx);
+
+/* check for stack underflow */
+/* void runstkund(runcxdef *ctx); */
+
+/* check for stack overflow */
+/* void runstkovf(runcxdef *ctx); */
/*
* Check to ensure we have enough arguments to pass to a function or method
@@ -352,7 +291,7 @@ struct runcxdef {
#endif /* RUNFAST */
/* reserve space in heap, collecting garbage if necessary */
-/* void runhres(uint siz, uint below); */
+/* void runhres(runcxdef *ctx, uint siz, uint below); */
#define runhres(ctx, siz, below) \
((uint)((ctx)->runcxhtop - (ctx)->runcxhp) > (uint)(siz) ? DISCARD 0 : \
(runhcmp(ctx, siz, below, (runsdef *)0, (runsdef *)0, (runsdef *)0),\
@@ -371,9 +310,31 @@ struct runcxdef {
((uint)((ctx)->runcxhtop - (ctx)->runcxhp) > (uint)(siz) ? DISCARD 0 : \
(runhcmp(ctx, siz, below, val1, val2, val3), DISCARD 0))
+/* garbage collect heap, making sure 'siz' bytes are available afterwards */
+void runhcmp(runcxdef *ctx, uint siz, uint below,
+ runsdef *val1, runsdef *val2, runsdef *val3);
+
+/* determine size of a data item */
+int runsiz(runsdef *item);
+
+/* find a sublist within a list, returning pointer to sublist or NULL */
+uchar *runfind(uchar *list, runsdef *item);
+
+/* add two runsdef values, returning result in *val */
+void runadd(runcxdef *ctx, runsdef *val, runsdef *val2, uint below);
+
+/*
+ * subtract val2 from val, returning result in *val; return TRUE if
+ * value changed, FALSE otherwise (this is returned when subtracting
+ * something from a list that isn't in the list)
+ */
+int runsub(runcxdef *ctx, runsdef *val, runsdef *val2, uint below);
+
+/* restore code pointer from object.property + offset */
+uchar *runcprst(runcxdef *ctx, uint ofs, objnum obj, prpnum prop);
/* leave a stack frame, removing arguments */
-/* void runleave(uint parms); */
+/* void runleave(runcxdef *ctx, uint parms); */
#define runleave(ctx, parms) \
(((ctx)->runcxsp = (ctx)->runcxbp), \
((ctx)->runcxbp = (runsdef *)((--((ctx)->runcxsp))->runsv.runsvstr)), \
@@ -385,6 +346,27 @@ struct runcxdef {
((ctx)->runcxhp = (ctx)->runcxheap), \
dbgrst(ctx->runcxdbg))
+/* set up runtime status line display */
+void runistat(struct voccxdef *vctx, struct runcxdef *rctx,
+ struct tiocxdef *tctx);
+
+/* signal a run-time error - allows debugger trapping */
+void runsign(runcxdef *ctx, int err);
+
+/* sign a run-time error with zero arguments */
+#define runsig(ctx, err) (errargc((ctx)->runcxerr,0),runsign(ctx,err))
+
+/* signal a run-time error with one argument */
+#define runsig1(ctx, err, typ, arg) \
+ (errargv((ctx)->runcxerr,0,typ,arg),errargc((ctx)->runcxerr,1),\
+ runsign(ctx,err))
+
+/* draw status line */
+void runstat(void);
+
+/* initialize output status */
+void runistat(struct voccxdef *vctx, struct runcxdef *rctx,
+ struct tiocxdef *tctx);
} // End of namespace TADS2
} // End of namespace TADS
diff --git a/engines/glk/tads/tads2/tokenizer.cpp b/engines/glk/tads/tads2/tokenizer.cpp
index 96bd2f1..3f9dc6e 100644
--- a/engines/glk/tads/tads2/tokenizer.cpp
+++ b/engines/glk/tads/tads2/tokenizer.cpp
@@ -24,6 +24,7 @@
#include "glk/tads/tads2/error.h"
#include "glk/tads/tads2/memory_cache_heap.h"
#include "glk/tads/tads2/os.h"
+#include "glk/tads/os_glk.h"
namespace Glk {
namespace TADS {
diff --git a/engines/glk/tads/tads2/vocabulary.cpp b/engines/glk/tads/tads2/vocabulary.cpp
index 516e9df..8c4fead 100644
--- a/engines/glk/tads/tads2/vocabulary.cpp
+++ b/engines/glk/tads/tads2/vocabulary.cpp
@@ -24,6 +24,7 @@
#include "glk/tads/tads2/vocabulary.h"
#include "glk/tads/tads2/error.h"
#include "glk/tads/tads2/memory_cache_heap.h"
+#include "glk/tads/os_glk.h"
namespace Glk {
namespace TADS {
Commit: f607792fa4e1f024dd8265034ac84425bde4aee7
https://github.com/scummvm/scummvm/commit/f607792fa4e1f024dd8265034ac84425bde4aee7
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00
Commit Message:
GLK: TADS2: More code files implemented
Changed paths:
A engines/glk/tads/tads2/opcode.h
A engines/glk/tads/tads2/post_compilation.cpp
R engines/glk/tads/tads2/memory_cache_loader.cpp
R engines/glk/tads/tads2/opcode_defs.h
engines/glk/module.mk
engines/glk/tads/osfrobtads.cpp
engines/glk/tads/osfrobtads.h
engines/glk/tads/tads2/file_io.cpp
engines/glk/tads/tads2/line_source_file.cpp
engines/glk/tads/tads2/memory_cache.cpp
engines/glk/tads/tads2/memory_cache_swap.cpp
engines/glk/tads/tads2/object.cpp
engines/glk/tads/tads2/run.h
diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index 93eff12..ef5dcb3 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -103,10 +103,11 @@ MODULE_OBJS := \
tads/tads2/list.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/object.o \
tads/tads2/os.o \
tads/tads2/output.o \
+ tads/tads2/post_compilation.o \
tads/tads2/regex.o \
tads/tads2/run.o \
tads/tads2/tads2.o \
diff --git a/engines/glk/tads/osfrobtads.cpp b/engines/glk/tads/osfrobtads.cpp
index df265be..eabd297 100644
--- a/engines/glk/tads/osfrobtads.cpp
+++ b/engines/glk/tads/osfrobtads.cpp
@@ -46,7 +46,7 @@ int osfrb(osfildef *fp, void *buf, size_t count) {
return dynamic_cast<Common::ReadStream *>(fp)->read(buf, count);
}
-bool osfwb(osfildef *fp, void *buf, size_t count) {
+bool osfwb(osfildef *fp, const void *buf, size_t count) {
return dynamic_cast<Common::WriteStream *>(fp)->write(buf, count) != count;
}
@@ -58,5 +58,39 @@ osfildef *osfopwt(const char *fname, os_filetype_t typ) {
return osfoprwtb(fname, typ);
}
+int osfseek(osfildef *fp, int ofs, int origin) {
+ return dynamic_cast<Common::SeekableReadStream *>(fp)->seek(ofs, origin);
+}
+
+int osfpos(osfildef *fp) {
+ return dynamic_cast<Common::SeekableReadStream *>(fp)->pos();
+}
+
+char *osfgets(char *buf, size_t count, osfildef *fp) {
+ Common::ReadStream *rs = dynamic_cast<Common::ReadStream *>(fp);
+ char *ptr = buf;
+ char c;
+ while (!rs->eos() && --count > 0) {
+ c = rs->readByte();
+ if (c == '\n' || c == '\0')
+ break;
+ *ptr++ = c;
+ }
+
+ *ptr++ = '\0';
+ return buf;
+}
+
+bool os_locate(const char *fname, int flen, const char *arg0, char *buf, size_t bufsiz) {
+ Common::String name = !flen ? Common::String(fname) : Common::String(fname, fname + flen);
+
+ if (!Common::File::exists(fname))
+ return false;
+
+ strncpy(buf, name.c_str(), bufsiz - 1);
+ buf[bufsiz - 1] = '\0';
+ return true;
+}
+
} // End of namespace TADS
} // End of namespace Glk
diff --git a/engines/glk/tads/osfrobtads.h b/engines/glk/tads/osfrobtads.h
index ce1624d..5c9bcd8 100644
--- a/engines/glk/tads/osfrobtads.h
+++ b/engines/glk/tads/osfrobtads.h
@@ -181,14 +181,14 @@ 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+"))
+#define osfoprwtt(fname,typ) osfoprwt
/* Open binary file for writing. */
-#define osfopwb(fname,typ) (fopen((fname),"wb"))
+#define osfopwb(fname,typ) osfoprwtb(fname, typ)
/* Open source file for reading - use the appropriate text or binary
* mode. */
-#define osfoprs osfoprt
+#define osfoprs osfoprb
/* Open binary file for reading. */
inline osfildef *osfoprb(const char *fname, os_filetype_t typ);
@@ -205,13 +205,13 @@ osfoprwb(const char *fname, os_filetype_t typ);
inline osfildef *osfoprwtb(const char *fname, os_filetype_t typ);
/* Get a line of text from a text file. */
-#define osfgets fgets
+char *osfgets(char *buf, size_t count, osfildef *fp);
/* Write a line of text to a text file. */
#define osfputs fputs
/* Write bytes to file. */
-inline bool osfwb(osfildef *fp, void *buf, size_t count);
+inline bool osfwb(osfildef *fp, const void *buf, size_t count);
/* Flush buffered writes to a file. */
inline void osfflush(osfildef *fp);
@@ -220,13 +220,13 @@ inline void osfflush(osfildef *fp);
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)))
+#define osfrbc(fp,buf,bufl) osfrb(fp,buf,bufl)
/* Get the current seek location in the file. */
-#define osfpos ftell
+inline int osfpos(osfildef *fp);
/* Seek to a location in the file. */
-#define osfseek fseek
+inline int osfseek(osfildef *fp, int ofs, int origin);
/* Close a file. */
#define osfcls delete
@@ -264,6 +264,8 @@ int os_file_stat( const char* fname, int follow_links,
*/
#define os_progress(fname,linenum)
+bool os_locate(const char *fname, int flen, const char *arg0, char *buf, size_t bufsiz);
+
} // End of namespace TADS
} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/file_io.cpp b/engines/glk/tads/tads2/file_io.cpp
index 581601f..d82f67a 100644
--- a/engines/glk/tads/tads2/file_io.cpp
+++ b/engines/glk/tads/tads2/file_io.cpp
@@ -21,12 +21,1754 @@
*/
#include "glk/tads/tads2/file_io.h"
+#include "glk/tads/tads2/appctx.h"
+#include "glk/tads/tads2/character_map.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/memory_cache_heap.h"
+#include "glk/tads/tads2/os.h"
+#include "glk/tads/tads2/run.h"
+#include "glk/tads/tads2/tokenizer.h"
+#include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/os_glk.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
+/* compare a resource string */
+/* int fioisrsc(uchar *filbuf, char *refnam); */
+#define fioisrsc(filbuf, refnam) \
+ (((filbuf)[0] == strlen(refnam)) && \
+ !memcmp(filbuf+1, refnam, (size_t)((filbuf)[0])))
+
+/* callback to load an object on demand */
+void OS_LOADDS fioldobj(void *ctx0, mclhd handle, uchar *ptr, ushort siz)
+{
+ fiolcxdef *ctx = (fiolcxdef *)ctx0;
+ ulong seekpos = (ulong)handle;
+ osfildef *fp = ctx->fiolcxfp;
+ char buf[7];
+ errcxdef *ec = ctx->fiolcxerr;
+ uint rdsiz;
+
+ /* figure out what type of object is to be loaded */
+ osfseek(fp, seekpos + ctx->fiolcxst, OSFSK_SET);
+ if (osfrb(fp, buf, 7)) errsig(ec, ERR_LDGAM);
+ switch(buf[0])
+ {
+ case TOKSTFUNC:
+ rdsiz = osrp2(buf + 3);
+ break;
+
+ case TOKSTOBJ:
+ rdsiz = osrp2(buf + 5);
+ break;
+
+ case TOKSTFWDOBJ:
+ case TOKSTFWDFN:
+ default:
+ errsig(ec, ERR_UNKOTYP);
+ }
+
+ if (siz < rdsiz) errsig(ec, ERR_LDBIG);
+ if (osfrb(fp, ptr, rdsiz)) errsig(ec, ERR_LDGAM);
+ if (ctx->fiolcxflg & FIOFCRYPT)
+ fioxor(ptr, rdsiz, ctx->fiolcxseed, ctx->fiolcxinc);
+}
+
+/* shut down load-on-demand subsystem (close load file) */
+void fiorcls(fiolcxdef *ctx)
+{
+ if (ctx != 0 && ctx->fiolcxfp != 0)
+ {
+ /* close the file */
+ osfcls(ctx->fiolcxfp);
+
+ /* forget the file object */
+ ctx->fiolcxfp = 0;
+ }
+}
+
+/*
+ * Read an HTMLRES resource map
+ */
+static void fiordhtml(errcxdef *ec, osfildef *fp, appctxdef *appctx,
+ int resfileno, const char *resfilename)
+{
+ uchar buf[256];
+
+ /*
+ * resource map - if the host system is interested, tell it about it
+ */
+ if (appctx != 0)
+ {
+ ulong entry_cnt;
+ ulong i;
+
+ /* read the index table header */
+ if (osfrb(fp, buf, 8))
+ errsig1(ec, ERR_RDRSC, ERRTSTR,
+ errstr(ec, resfilename, strlen(resfilename)));
+
+ /* get the number of entries in the table */
+ entry_cnt = osrp4(buf);
+
+ /* read the index entries */
+ for (i = 0 ; i < entry_cnt ; ++i)
+ {
+ ulong res_ofs;
+ ulong res_siz;
+ ushort res_namsiz;
+
+ /* read this entry */
+ if (osfrb(fp, buf, 10))
+ errsig1(ec, ERR_RDRSC, ERRTSTR,
+ errstr(ec, resfilename, strlen(resfilename)));
+
+ /* get the entry header */
+ res_ofs = osrp4(buf);
+ res_siz = osrp4(buf + 4);
+ res_namsiz = osrp2(buf + 8);
+
+ /* read this entry's name */
+ if (osfrb(fp, buf, res_namsiz))
+ errsig1(ec, ERR_RDRSC, ERRTSTR,
+ errstr(ec, resfilename, strlen(resfilename)));
+
+ /* tell the host system about this entry */
+ if (appctx->add_resource)
+ (*appctx->add_resource)(appctx->add_resource_ctx,
+ res_ofs, res_siz,
+ (char *)buf,
+ (size_t)res_namsiz,
+ resfileno);
+ }
+
+ /* tell the host system where the resources start */
+ if (appctx->set_resmap_seek != 0)
+ {
+ long pos = osfpos(fp);
+ (*appctx->set_resmap_seek)(appctx->set_resmap_seek_ctx,
+ pos, resfileno);
+ }
+ }
+}
+
+/*
+ * Read an external resource file. This is a limited version of the
+ * general file reader that can only read resource files, not full game
+ * files.
+ */
+static void fiordrscext(errcxdef *ec, osfildef *fp, appctxdef *appctx,
+ int resfileno, char *resfilename)
+{
+ uchar buf[TOKNAMMAX + 50];
+ unsigned long endpos;
+ unsigned long startofs;
+
+ /* note the starting offset */
+ startofs = osfpos(fp);
+
+ /* check file and version headers, and get flags and timestamp */
+ if (osfrb(fp, buf, (int)(sizeof(FIOFILHDR) + sizeof(FIOVSNHDR) + 2)))
+ errsig1(ec, ERR_RDRSC, ERRTSTR,
+ errstr(ec, resfilename, strlen(resfilename)));
+ if (memcmp(buf, FIOFILHDRRSC, (size_t)sizeof(FIOFILHDRRSC)))
+ errsig1(ec, ERR_BADHDRRSC, ERRTSTR,
+ errstr(ec, resfilename, strlen(resfilename)));
+ if (memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR,
+ (size_t)sizeof(FIOVSNHDR))
+ && memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR2,
+ (size_t)sizeof(FIOVSNHDR2))
+ && memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR3,
+ (size_t)sizeof(FIOVSNHDR3)))
+ errsig(ec, ERR_BADVSN);
+ if (osfrb(fp, buf, (size_t)26))
+ errsig1(ec, ERR_RDRSC, ERRTSTR,
+ errstr(ec, resfilename, strlen(resfilename)));
+
+ /* now read resources from the file */
+ for (;;)
+ {
+ /* read resource type and next-resource pointer */
+ if (osfrb(fp, buf, 1)
+ || osfrb(fp, buf + 1, (int)(buf[0] + 4)))
+ errsig1(ec, ERR_RDRSC, ERRTSTR,
+ errstr(ec, resfilename, strlen(resfilename)));
+ endpos = osrp4(buf + 1 + buf[0]);
+
+ /* check the resource type */
+ if (fioisrsc(buf, "HTMLRES"))
+ {
+ /* read the HTML resource map */
+ fiordhtml(ec, fp, appctx, resfileno, resfilename);
+
+ /*
+ * skip the resources - they're entirely for the host
+ * application's use
+ */
+ osfseek(fp, endpos + startofs, OSFSK_SET);
+ }
+ else if (fioisrsc(buf, "$EOF"))
+ {
+ /* we're done reading the file */
+ break;
+ }
+ else
+ errsig(ec, ERR_UNKRSC);
+ }
+}
+
+/*
+ * read a game from a binary file
+ *
+ * flags:
+ * &1 ==> run preinit
+ * &2 ==> preload objects
+ */
+static void fiord1(mcmcxdef *mctx, voccxdef *vctx, tokcxdef *tctx,
+ osfildef *fp, const char *fname,
+ fiolcxdef *setupctx, ulong startofs,
+ objnum *preinit, uint *flagp, tokpdef *path,
+ uchar **fmtsp, uint *fmtlp, uint *pcntptr, int flags,
+ appctxdef *appctx, char *argv0)
+{
+ int i;
+ int siz;
+ uchar buf[TOKNAMMAX + 50];
+ errcxdef *ec = vctx->voccxerr;
+ ulong endpos;
+ int obj;
+ ulong curpos;
+ runxdef *ex;
+ ulong eof_reset = 0; /* reset here at EOF if non-zero */
+#if 0 // XFCNs are obsolete
+ int xfcns_done = FALSE; /* already loaded XFCNs */
+#endif
+ ulong xfcn_pos = 0; /* location of XFCN's if preloadable */
+ uint xor_seed = 17; /* seed value for fioxor */
+ uint xor_inc = 29; /* increment value for fioxor */
+
+ /* set up loader callback context */
+ setupctx->fiolcxfp = fp;
+ setupctx->fiolcxerr = ec;
+ setupctx->fiolcxst = startofs;
+ setupctx->fiolcxseed = xor_seed;
+ setupctx->fiolcxinc = xor_inc;
+
+ /* check file and version headers, and get flags and timestamp */
+ if (osfrb(fp, buf, (int)(sizeof(FIOFILHDR) + sizeof(FIOVSNHDR) + 2)))
+ errsig(ec, ERR_RDGAM);
+ if (memcmp(buf, FIOFILHDR, (size_t)sizeof(FIOFILHDR)))
+ errsig(ec, ERR_BADHDR);
+ if (memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR,
+ (size_t)sizeof(FIOVSNHDR))
+ && memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR2,
+ (size_t)sizeof(FIOVSNHDR2))
+ && memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR3,
+ (size_t)sizeof(FIOVSNHDR3)))
+ errsig(ec, ERR_BADVSN);
+ if (osfrb(fp, vctx->voccxtim, (size_t)26)) errsig(ec, ERR_RDGAM);
+
+ /*
+ * if the game wasn't compiled with 2.2 or later, make a note,
+ * because we need to ignore certain property flags (due to a bug in
+ * the old compiler)
+ */
+ if (memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR2,
+ (size_t)sizeof(FIOVSNHDR2)) == 0
+ || memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR3,
+ (size_t)sizeof(FIOVSNHDR3)) == 0)
+ mctx->mcmcxflg |= MCMCXF_NO_PRP_DEL;
+
+ setupctx->fiolcxflg =
+ *flagp = osrp2(buf + sizeof(FIOFILHDR) + sizeof(FIOVSNHDR));
+
+ /* now read resources from the file */
+ for (;;)
+ {
+ /* read resource type and next-resource pointer */
+ if (osfrb(fp, buf, 1)
+ || osfrb(fp, buf + 1, (int)(buf[0] + 4)))
+ errsig(ec, ERR_RDGAM);
+ endpos = osrp4(buf + 1 + buf[0]);
+
+ if (fioisrsc(buf, "OBJ"))
+ {
+ /* skip regular objects if fast-load records are included */
+ if (*flagp & FIOFFAST)
+ {
+ osfseek(fp, endpos + startofs, OSFSK_SET);
+ continue;
+ }
+
+ curpos = osfpos(fp) - startofs;
+ while (curpos != endpos)
+ {
+ /* read type and object number */
+ if (osfrb(fp, buf, 3)) errsig(ec, ERR_RDGAM);
+ obj = osrp2(buf+1);
+
+ switch(buf[0])
+ {
+ case TOKSTFUNC:
+ case TOKSTOBJ:
+ if (osfrb(fp, buf + 3, 4)) errsig(ec, ERR_RDGAM);
+ mcmrsrv(mctx, (ushort)osrp2(buf + 3), (mcmon)obj,
+ (mclhd)curpos);
+ curpos += osrp2(buf + 5) + 7;
+
+ /* load object if preloading */
+ if (flags & 2)
+ {
+ (void)mcmlck(mctx, (mcmon)obj);
+ mcmunlck(mctx, (mcmon)obj);
+ }
+
+ /* seek past this object */
+ osfseek(fp, curpos + startofs, OSFSK_SET);
+ break;
+
+ case TOKSTFWDOBJ:
+ case TOKSTFWDFN:
+ {
+ ushort bsiz;
+ uchar *p;
+
+ if (osfrb(fp, buf+3, 2)) errsig(ec, ERR_RDGAM);
+ bsiz = osrp2(buf+3);
+ p = mcmalonum(mctx, bsiz, (mcmon)obj);
+ if (osfrb(fp, p, bsiz)) errsig(ec, ERR_RDGAM);
+ mcmunlck(mctx, (mcmon)obj);
+ curpos += 5 + bsiz;
+ break;
+ }
+
+ case TOKSTEXTERN:
+ if (!vctx->voccxrun->runcxext)
+ errsig(ec, ERR_UNXEXT);
+ ex = &vctx->voccxrun->runcxext[obj];
+
+ if (osfrb(fp, buf + 3, 1)
+ || osfrb(fp, ex->runxnam, (int)buf[3]))
+ errsig(ec, ERR_RDGAM);
+ ex->runxnam[buf[3]] = '\0';
+ curpos += buf[3] + 4;
+ break;
+
+ default:
+ errsig(ec, ERR_UNKOTYP);
+ }
+ }
+ }
+ else if (fioisrsc(buf, "FST"))
+ {
+ uchar *p;
+ uchar *bufp;
+ ulong bsiz;
+
+ if (!(*flagp & FIOFFAST))
+ {
+ osfseek(fp, endpos + startofs, OSFSK_SET);
+ continue;
+ }
+
+ curpos = osfpos(fp) - startofs;
+ bsiz = endpos - curpos;
+ if (bsiz && bsiz < OSMALMAX
+ && (bufp = p = (uchar *)osmalloc((size_t)bsiz)) != 0)
+ {
+ uchar *p1;
+ ulong siz2;
+ uint sizcur;
+
+ for (p1 = p, siz2 = bsiz ; siz2 ; siz2 -= sizcur, p1 += sizcur)
+ {
+ sizcur = (siz2 > (uint)0xffff ? (uint)0xffff : siz2);
+ if (osfrb(fp, p1, sizcur)) errsig(ec, ERR_RDGAM);
+ }
+
+ while (bsiz)
+ {
+ obj = osrp2(p + 1);
+ switch(*p)
+ {
+ case TOKSTFUNC:
+ case TOKSTOBJ:
+ mcmrsrv(mctx, (ushort)osrp2(p + 3), (mcmon)obj,
+ (mclhd)osrp4(p + 7));
+ p += 11;
+ bsiz -= 11;
+
+ /* preload object if desired */
+ if (flags & 2)
+ {
+ (void)mcmlck(mctx, (mcmon)obj);
+ mcmunlck(mctx, (mcmon)obj);
+ }
+ break;
+
+ case TOKSTEXTERN:
+ if (!vctx->voccxrun->runcxext)
+ errsig(ec, ERR_UNXEXT);
+ ex = &vctx->voccxrun->runcxext[obj];
+
+ memcpy(ex->runxnam, p + 4, (size_t)p[3]);
+ ex->runxnam[p[3]] = '\0';
+ bsiz -= p[3] + 4;
+ p += p[3] + 4;
+ break;
+
+ default:
+ errsig(ec, ERR_UNKOTYP);
+ }
+ }
+
+ /* done with temporary block; free it */
+ osfree(bufp);
+ osfseek(fp, endpos + startofs, OSFSK_SET);
+ }
+ else
+ {
+ while (curpos != endpos)
+ {
+ if (osfrb(fp, buf, 3)) errsig(ec, ERR_RDGAM);
+ obj = osrp2(buf + 1);
+ switch(buf[0])
+ {
+ case TOKSTFUNC:
+ case TOKSTOBJ:
+ if (osfrb(fp, buf + 3, 8)) errsig(ec, ERR_RDGAM);
+ mcmrsrv(mctx, (ushort)osrp2(buf + 3), (mcmon)obj,
+ (mclhd)osrp4(buf + 7));
+ curpos += 11;
+
+ /* preload object if desired */
+ if (flags & 2)
+ {
+ (void)mcmlck(mctx, (mcmon)obj);
+ mcmunlck(mctx, (mcmon)obj);
+ osfseek(fp, curpos + startofs, OSFSK_SET);
+ }
+ break;
+
+ case TOKSTEXTERN:
+ if (!vctx->voccxrun->runcxext)
+ errsig(ec, ERR_UNXEXT);
+ ex = &vctx->voccxrun->runcxext[obj];
+
+ if (osfrb(fp, buf + 3, 1)
+ || osfrb(fp, ex->runxnam, (int)buf[3]))
+ errsig(ec, ERR_RDGAM);
+ ex->runxnam[buf[3]] = '\0';
+ curpos += buf[3] + 4;
+ break;
+
+ default:
+ errsig(ec, ERR_UNKOTYP);
+ }
+ }
+ }
+
+ /* if we can preload xfcn's, do so now */
+ if (xfcn_pos)
+ {
+ eof_reset = endpos; /* remember to return here when done */
+ osfseek(fp, xfcn_pos, OSFSK_SET); /* go to xfcn's */
+ }
+ }
+ else if (fioisrsc(buf, "XFCN"))
+ {
+ if (!vctx->voccxrun->runcxext) errsig(ec, ERR_UNXEXT);
+
+ /* read length and name of resource */
+ if (osfrb(fp, buf, 3) || osfrb(fp, buf + 3, (int)buf[2]))
+ errsig(ec, ERR_RDGAM);
+ siz = osrp2(buf);
+
+#if 0
+/*
+ * external functions are now obsolete - do not load
+ */
+
+ /* look for an external function with the same name */
+ for (i = vctx->voccxrun->runcxexc, ex = vctx->voccxrun->runcxext
+ ; i ; ++ex, --i)
+ {
+ j = strlen(ex->runxnam);
+ if (j == buf[2] && !memcmp(buf + 3, ex->runxnam, (size_t)j))
+ break;
+ }
+
+ /* if we found an external function of this name, load it */
+ if (i && !xfcns_done)
+ {
+ /* load the function */
+ ex->runxptr = os_exfld(fp, (unsigned)siz);
+ }
+ else
+ {
+ /* this XFCN isn't used; don't bother loading it */
+ osfseek(fp, endpos + startofs, OSFSK_SET);
+ }
+#else
+ /* external functions are obsolete; simply skip the data */
+ osfseek(fp, endpos + startofs, OSFSK_SET);
+#endif
+ }
+ else if (fioisrsc(buf, "HTMLRES"))
+ {
+ /* read the resources */
+ fiordhtml(ec, fp, appctx, 0, fname);
+
+ /*
+ * skip the resources - they're entirely for the host
+ * application's use
+ */
+ osfseek(fp, endpos + startofs, OSFSK_SET);
+ }
+ else if (fioisrsc(buf, "INH"))
+ {
+ uchar *p;
+ uchar *bufp;
+ ulong bsiz;
+
+ /* do it in a single file read, if we can, for speed */
+ curpos = osfpos(fp) - startofs;
+ bsiz = endpos - curpos;
+ if (bsiz && bsiz < OSMALMAX
+ && (bufp = p = (uchar *)osmalloc((size_t)bsiz)) != 0)
+ {
+ uchar *p1;
+ ulong siz2;
+ uint sizcur;
+
+ for (p1 = p, siz2 = bsiz ; siz2 ; siz2 -= sizcur, p1 += sizcur)
+ {
+ sizcur = (siz2 > (uint)0xffff ? (uint)0xffff : siz2);
+ if (osfrb(fp, p1, sizcur)) errsig(ec, ERR_RDGAM);
+ }
+
+ while (bsiz)
+ {
+ i = osrp2(p + 7);
+ obj = osrp2(p + 1);
+
+ vociadd(vctx, (objnum)obj, (objnum)osrp2(p+3), i,
+ (objnum *)(p + 9), p[0] | VOCIFXLAT);
+ vocinh(vctx, obj)->vociilc = osrp2(p + 5);
+
+ p += 9 + (2 * i);
+ bsiz -= 9 + (2 * i);
+ }
+
+ /* done with temporary block; free it */
+ osfree(bufp);
+ }
+ else
+ {
+ while (curpos != endpos)
+ {
+ if (osfrb(fp, buf, 9)) errsig(ec, ERR_RDGAM);
+ i = osrp2(buf + 7); /* get number of superclasses */
+ obj = osrp2(buf + 1); /* get object number */
+ if (i && osfrb(fp, buf + 9, 2 * i)) errsig(ec, ERR_RDGAM);
+
+ vociadd(vctx, (objnum)obj, (objnum)osrp2(buf+3),
+ i, (objnum *)(buf + 9), buf[0] | VOCIFXLAT);
+ vocinh(vctx, obj)->vociilc = osrp2(buf + 5);
+
+ curpos += 9 + (2 * i);
+ }
+ }
+ }
+ else if (fioisrsc(buf, "REQ"))
+ {
+ curpos = osfpos(fp) - startofs;
+ siz = endpos - curpos;
+
+ if (osfrb(fp, buf, (uint)siz)) errsig(ec, ERR_RDGAM);
+ vctx->voccxme = vctx->voccxme_init = osrp2(buf);
+ vctx->voccxvtk = osrp2(buf+2);
+ vctx->voccxstr = osrp2(buf+4);
+ vctx->voccxnum = osrp2(buf+6);
+ vctx->voccxprd = osrp2(buf+8);
+ vctx->voccxvag = osrp2(buf+10);
+ vctx->voccxini = osrp2(buf+12);
+ vctx->voccxpre = osrp2(buf+14);
+ vctx->voccxper = osrp2(buf+16);
+
+ /* if we have a cmdPrompt function, read it */
+ if (siz >= 20)
+ vctx->voccxprom = osrp2(buf + 18);
+ else
+ vctx->voccxprom = MCMONINV;
+
+ /* if we have the NLS functions, read them */
+ if (siz >= 26)
+ {
+ vctx->voccxpdis = osrp2(buf + 20);
+ vctx->voccxper2 = osrp2(buf + 22);
+ vctx->voccxpdef = osrp2(buf + 24);
+ }
+ else
+ {
+ /* the new NLS functions aren't defined in this file */
+ vctx->voccxpdis = MCMONINV;
+ vctx->voccxper2 = MCMONINV;
+ vctx->voccxpdef = MCMONINV;
+ }
+
+ /* test for parseAskobj separately, as it was added later */
+ if (siz >= 28)
+ vctx->voccxpask = osrp2(buf + 26);
+ else
+ vctx->voccxpask = MCMONINV;
+
+ /* test for preparseCmd separately - it's another late comer */
+ if (siz >= 30)
+ vctx->voccxppc = osrp2(buf + 28);
+ else
+ vctx->voccxppc = MCMONINV;
+
+ /* check for parseAskobjActor separately - another late comer */
+ if (siz >= 32)
+ vctx->voccxpask2 = osrp2(buf + 30);
+ else
+ vctx->voccxpask2 = MCMONINV;
+
+ /* if we have parseErrorParam, read it as well */
+ if (siz >= 34)
+ {
+ vctx->voccxperp = osrp2(buf + 32);
+ }
+ else
+ {
+ /* parseErrorParam isn't defined in this file */
+ vctx->voccxperp = MCMONINV;
+ }
+
+ /*
+ * if we have commandAfterRead and initRestore, read them as
+ * well
+ */
+ if (siz >= 38)
+ {
+ vctx->voccxpostprom = osrp2(buf + 34);
+ vctx->voccxinitrestore = osrp2(buf + 36);
+ }
+ else
+ {
+ /* these new functions aren't defined in this game */
+ vctx->voccxpostprom = MCMONINV;
+ vctx->voccxinitrestore = MCMONINV;
+ }
+
+ /* check for and read parseUnknownVerb, parseNounPhrase */
+ if (siz >= 42)
+ {
+ vctx->voccxpuv = osrp2(buf + 38);
+ vctx->voccxpnp = osrp2(buf + 40);
+ }
+ else
+ {
+ vctx->voccxpuv = MCMONINV;
+ vctx->voccxpnp = MCMONINV;
+ }
+
+ /* check for postAction, endCommand */
+ if (siz >= 48)
+ {
+ vctx->voccxpostact = osrp2(buf + 42);
+ vctx->voccxendcmd = osrp2(buf + 44);
+ vctx->voccxprecmd = osrp2(buf + 46);
+ }
+ else
+ {
+ vctx->voccxpostact = MCMONINV;
+ vctx->voccxendcmd = MCMONINV;
+ vctx->voccxprecmd = MCMONINV;
+ }
+
+ /* check for parseAskobjIndirect */
+ if (siz >= 50)
+ vctx->voccxpask3 = osrp2(buf + 48);
+ else
+ vctx->voccxpask3 = MCMONINV;
+
+ /* check for preparseExt and parseDefaultExt */
+ if (siz >= 54)
+ {
+ vctx->voccxpre2 = osrp2(buf + 50);
+ vctx->voccxpdef2 = osrp2(buf + 52);
+ }
+ else
+ {
+ vctx->voccxpre2 = MCMONINV;
+ vctx->voccxpdef2 = MCMONINV;
+ }
+ }
+ else if (fioisrsc(buf, "VOC"))
+ {
+ uchar *p;
+ uchar *bufp;
+ ulong bsiz;
+ int len1;
+ int len2;
+
+ /* do it in a single file read, if we can, for speed */
+ curpos = osfpos(fp) - startofs;
+ bsiz = endpos - curpos;
+ if (bsiz && bsiz < OSMALMAX
+ && (bufp = p = (uchar *)osmalloc((size_t)bsiz)) != 0)
+ {
+ uchar *p1;
+ ulong siz2;
+ uint sizcur;
+
+ for (p1 = p, siz2 = bsiz ; siz2 ; siz2 -= sizcur, p1 += sizcur)
+ {
+ sizcur = (siz2 > (uint)0xffff ? (uint)0xffff : siz2);
+ if (osfrb(fp, p1, sizcur)) errsig(ec, ERR_RDGAM);
+ }
+
+ while (bsiz)
+ {
+ len1 = osrp2(p);
+ len2 = osrp2(p + 2);
+ if (*flagp & FIOFCRYPT)
+ fioxor(p + 10, (uint)(len1 + len2),
+ xor_seed, xor_inc);
+ vocadd2(vctx, (prpnum)osrp2(p+4), (objnum)osrp2(p+6),
+ osrp2(p+8), p + 10, len1,
+ (len2 ? p + 10 + len1 : (uchar*)0), len2);
+
+ p += 10 + len1 + len2;
+ bsiz -= 10 + len1 + len2;
+ }
+
+ /* done with the temporary block; free it up */
+ osfree(bufp);
+ }
+ else
+ {
+ /* can't do it in one file read; do it the slow way */
+ while (curpos != endpos)
+ {
+ if (osfrb(fp, buf, 10)
+ || osfrb(fp, buf + 10,
+ (len1 = osrp2(buf)) + (len2 = osrp2(buf + 2))))
+ errsig(ec, ERR_RDGAM);
+
+ if (*flagp & FIOFCRYPT)
+ fioxor(buf + 10, (uint)(len1 + len2),
+ xor_seed, xor_inc);
+ vocadd2(vctx, (prpnum)osrp2(buf+4), (objnum)osrp2(buf+6),
+ osrp2(buf+8), buf + 10, len1,
+ (len2 ? buf + 10 + len1 : (uchar*)0), len2);
+ curpos += 10 + len1 + len2;
+ }
+ }
+ }
+ else if (fioisrsc(buf, "FMTSTR"))
+ {
+ uchar *fmts;
+ uint fmtl;
+
+ if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM);
+ fmtl = osrp2(buf);
+ fmts = mchalo(vctx->voccxerr, fmtl, "fiord1");
+ if (osfrb(fp, fmts, fmtl)) errsig(ec, ERR_RDGAM);
+ if (*flagp & FIOFCRYPT) fioxor(fmts, fmtl, xor_seed, xor_inc);
+ tiosetfmt(vctx->voccxtio, vctx->voccxrun, fmts, fmtl);
+
+ if (fmtsp) *fmtsp = fmts;
+ if (fmtlp) *fmtlp = fmtl;
+ }
+ else if (fioisrsc(buf, "CMPD"))
+ {
+ if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM);
+ vctx->voccxcpl = osrp2(buf);
+ vctx->voccxcpp = (char *)mchalo(vctx->voccxerr,
+ vctx->voccxcpl, "fiord1");
+ if (osfrb(fp, vctx->voccxcpp, (uint)vctx->voccxcpl))
+ errsig(ec, ERR_RDGAM);
+ if (*flagp & FIOFCRYPT)
+ fioxor((uchar *)vctx->voccxcpp, (uint)vctx->voccxcpl,
+ xor_seed, xor_inc);
+ }
+ else if (fioisrsc(buf, "SPECWORD"))
+ {
+ if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM);
+ vctx->voccxspl = osrp2(buf);
+ vctx->voccxspp = (char *)mchalo(vctx->voccxerr,
+ vctx->voccxspl, "fiord1");
+ if (osfrb(fp, vctx->voccxspp, (uint)vctx->voccxspl))
+ errsig(ec, ERR_RDGAM);
+ if (*flagp & FIOFCRYPT)
+ fioxor((uchar *)vctx->voccxspp, (uint)vctx->voccxspl,
+ xor_seed, xor_inc);
+ }
+ else if (fioisrsc(buf, "SYMTAB"))
+ {
+ tokthdef *symtab;
+
+ /* if there's no debugger context, don't bother with this */
+ if (!vctx->voccxrun->runcxdbg)
+ {
+ osfseek(fp, endpos + startofs, OSFSK_SET);
+ continue;
+ }
+
+ if (!(symtab = vctx->voccxrun->runcxdbg->dbgcxtab))
+ {
+ symtab = (tokthdef *)mchalo(ec, sizeof(tokthdef),
+ "fiord:symtab");
+ tokthini(ec, mctx, (toktdef *)symtab);
+ vctx->voccxrun->runcxdbg->dbgcxtab = symtab;
+ }
+
+ /* read symbols until we find a zero-length symbol */
+ for (;;)
+ {
+ int hash;
+
+ if (osfrb(fp, buf, 4)) errsig(ec, ERR_RDGAM);
+ if (buf[0] == 0) break;
+ if (osfrb(fp, buf + 4, (int)buf[0])) errsig(ec, ERR_RDGAM);
+ buf[4 + buf[0]] = '\0';
+ hash = tokhsh((char *)buf + 4);
+
+ (*symtab->tokthsc.toktfadd)((toktdef *)symtab,
+ (char *)buf + 4,
+ (int)buf[0], (int)buf[1],
+ osrp2(buf + 2), hash);
+ }
+ }
+ else if (fioisrsc(buf, "SRC"))
+ {
+ /* skip source file id's if there's no debugger context */
+ if (vctx->voccxrun->runcxdbg == 0)
+ {
+ osfseek(fp, endpos + startofs, OSFSK_SET);
+ continue;
+ }
+
+ while ((osfpos(fp) - startofs) != endpos)
+ {
+ /* the only thing we know how to read is linfdef's */
+ if (linfload(fp, vctx->voccxrun->runcxdbg, ec, path))
+ errsig(ec, ERR_RDGAM);
+ }
+ }
+ else if (fioisrsc(buf, "SRC2"))
+ {
+ /*
+ * this is simply a marker indicating that we have new-style
+ * (line-number-based) source debugging information in the
+ * file -- set the new-style debug info flag
+ */
+ if (vctx->voccxrun->runcxdbg != 0)
+ vctx->voccxrun->runcxdbg->dbgcxflg |= DBGCXFLIN2;
+
+ /* the contents are empty - skip the block */
+ osfseek(fp, endpos + startofs, OSFSK_SET);
+ }
+ else if (fioisrsc(buf, "PREINIT"))
+ {
+ if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM);
+ *preinit = osrp2(buf);
+ }
+ else if (fioisrsc(buf, "ERRMSG"))
+ {
+ errini(ec, fp);
+ osfseek(fp, endpos + startofs, OSFSK_SET);
+ }
+ else if (fioisrsc(buf, "EXTCNT"))
+ {
+ uchar *p;
+ ushort len;
+ ulong bsiz;
+
+ curpos = osfpos(fp) - startofs;
+ bsiz = endpos - curpos;
+ if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM);
+ i = osrp2(buf);
+
+ len = i * sizeof(runxdef);
+ p = mchalo(ec, len, "fiord:runxdef");
+ memset(p, 0, (size_t)len);
+
+ vctx->voccxrun->runcxext = (runxdef *)p;
+ vctx->voccxrun->runcxexc = i;
+
+ /* see if start-of-XFCN information is present */
+ if (bsiz >= 6)
+ {
+ /* get location of first XFCN, and seek there */
+ if (osfrb(fp, buf, 4)) errsig(ec, ERR_RDGAM);
+ xfcn_pos = osrp4(buf);
+ }
+
+ /* seek past this resource */
+ osfseek(fp, endpos + startofs, OSFSK_SET);
+ }
+ else if (fioisrsc(buf, "PRPCNT"))
+ {
+ if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM);
+ if (pcntptr) *pcntptr = osrp2(buf);
+ }
+ else if (fioisrsc(buf, "TADSPP") && tctx != 0)
+ {
+ tok_read_defines(tctx, fp, ec);
+ }
+ else if (fioisrsc(buf, "XSI"))
+ {
+ if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM);
+ setupctx->fiolcxseed = xor_seed = buf[0];
+ setupctx->fiolcxinc = xor_inc = buf[1];
+ osfseek(fp, endpos + startofs, OSFSK_SET);
+ }
+ else if (fioisrsc(buf, "CHRSET"))
+ {
+ size_t len;
+
+ /* read the character set ID and LDESC */
+ if (osfrb(fp, buf, 6)
+ || (len = osrp2(buf+4)) > CMAP_LDESC_MAX_LEN
+ || osfrb(fp, buf+6, len))
+ errsig(ec, ERR_RDGAM);
+
+ /* establish this character set mapping */
+ buf[4] = '\0';
+ cmap_set_game_charset(ec, (char *)buf, (char *)buf + 6, argv0);
+ }
+ else if (fioisrsc(buf, "$EOF"))
+ {
+ if (eof_reset)
+ {
+ osfseek(fp, eof_reset, OSFSK_SET); /* back after EXTCNT */
+ eof_reset = 0; /* really done at next EOF */
+#if 0 // XFCNs are obsolete
+ xfcns_done = TRUE; /* don't do XFCNs again */
+#endif
+ }
+ else
+ break;
+ }
+ else
+ errsig(ec, ERR_UNKRSC);
+ }
+}
+
+/* read binary file */
+void fiord(mcmcxdef *mctx, voccxdef *vctx, tokcxdef *tctx, char *fname,
+ char *exename, fiolcxdef *setupctx, objnum *preinit, uint *flagp,
+ tokpdef *path, uchar **fmtsp, uint *fmtlp, uint *pcntptr,
+ int flags, struct appctxdef *appctx, char *argv0)
+{
+ osfildef *fp;
+ ulong startofs;
+ char *display_fname;
+
+ /* presume there will be no need to run preinit */
+ *preinit = MCMONINV;
+
+ /*
+ * get the display filename - use the real filename if one is
+ * provided, otherwise use the name of the executable file itself
+ */
+ display_fname = (fname != 0 ? fname : exename);
+
+ /* save the filename in G_os_gamename */
+ if (display_fname != 0)
+ {
+ size_t copylen;
+
+ /* limit the copy to the buffer size */
+ if ((copylen = strlen(display_fname)) > sizeof(G_os_gamename) - 1)
+ copylen = sizeof(G_os_gamename) - 1;
+
+ /* save it */
+ memcpy(G_os_gamename, display_fname, copylen);
+ G_os_gamename[copylen] = '\0';
+ }
+ else
+ G_os_gamename[0] = '\0';
+
+ /* open the file and read and check file header */
+ fp = (fname != 0 ? osfoprb(fname, OSFTGAME)
+ : os_exeseek(exename, "TGAM"));
+ if (fp == 0)
+ errsig(vctx->voccxerr, ERR_OPRGAM);
+
+ /*
+ * we've identified the .GAM file source - tell the host system
+ * about it, if it's interested
+ */
+ if (appctx != 0 && appctx->set_game_name != 0)
+ (*appctx->set_game_name)(appctx->set_game_name_ctx, display_fname);
+
+ /* remember starting location in file */
+ startofs = osfpos(fp);
+
+ ERRBEGIN(vctx->voccxerr)
+
+ /*
+ * Read the game file. Note that the .GAM file always has resource
+ * file number zero.
+ */
+ fiord1(mctx, vctx, tctx, fp, display_fname,
+ setupctx, startofs, preinit, flagp, path,
+ fmtsp, fmtlp, pcntptr, flags, appctx, argv0);
+
+ /*
+ * If the host system accepts additional resource files, look for
+ * additional resource files. These are files in the same directory
+ * as the .GAM file, with the .GAM suffix replaced by suffixes from
+ *. RS0 to .RS9.
+ */
+ if (appctx != 0 && appctx->add_resfile != 0)
+ {
+ char suffix_lc[4];
+ char suffix_uc[4];
+ int i;
+ char *base_name;
+
+ /* use the game or executable filename, as appropriate */
+ base_name = display_fname;
+
+ /* build the initial suffixes - try both upper- and lower-case */
+ suffix_uc[0] = 'R';
+ suffix_uc[1] = 'S';
+ suffix_uc[3] = '\0';
+ suffix_lc[0] = 'r';
+ suffix_lc[1] = 's';
+ suffix_lc[3] = '\0';
+
+ /* loop through each possible suffix (.RS0 through .RS9) */
+ for (i = 0 ; i < 9 ; ++i)
+ {
+ char resname[OSFNMAX];
+ osfildef *fpres;
+ int resfileno;
+
+ /*
+ * Build the next resource filename. If there's an explicit
+ * resource path, use it, otherwise use the same directory
+ * that contains the .GAM file.
+ */
+ if (appctx->ext_res_path != 0)
+ {
+ /*
+ * There's an explicit resource path - append the root
+ * (filename-only, minus path) portion of the .GAM file
+ * name to the resource path.
+ */
+ os_build_full_path(resname, sizeof(resname),
+ appctx->ext_res_path,
+ os_get_root_name(base_name));
+ }
+ else
+ {
+ /*
+ * there's no resource path - use the entire .GAM
+ * filename, including directory, so that we look in the
+ * same directory that contains the .GAM file
+ */
+ if (base_name != 0)
+ strcpy(resname, base_name);
+ else
+ resname[0] = '\0';
+ }
+
+ /* add the current extension (replacing any current extension) */
+ os_remext(resname);
+ suffix_lc[2] = suffix_uc[2] = '0' + i;
+ os_addext(resname, suffix_lc);
+
+ /* try opening the file */
+ fpres = osfoprb(resname, OSFTGAME);
+
+ /* if that didn't work, try the upper-case name */
+ if (fpres == 0)
+ {
+ /* replace the suffix with the upper-case version */
+ os_remext(resname);
+ os_addext(resname, suffix_uc);
+
+ /* try again with the new name */
+ fpres = osfoprb(resname, OSFTGAME);
+ }
+
+ /* if we opened it successfully, read it */
+ if (fpres != 0)
+ {
+ /* tell the host system about it */
+ resfileno = (*appctx->add_resfile)
+ (appctx->add_resfile_ctx, resname);
+
+ /* read the file */
+ fiordrscext(vctx->voccxerr, fpres, appctx,
+ resfileno, resname);
+
+ /* we're done with the file, so close it */
+ osfcls(fpres);
+ }
+ }
+ }
+
+ ERRCLEAN(vctx->voccxerr)
+ /* if an error occurs during read, clean up by closing the file */
+ osfcls(fp);
+ ERRENDCLN(vctx->voccxerr);
+}
+
+/* save game header */
+#define FIOSAVHDR "TADS2 save\012\015\032"
+
+/* save game header prefix - .GAM file information */
+#define FIOSAVHDR_PREFIX "TADS2 save/g\012\015\032"
+
+/*
+ * Saved game format version string - note that the length of the
+ * version string must be fixed, so when this is updated, it must be
+ * updated to another string of the same length. This should be updated
+ * whenever a change is made to the format that can't be otherwise
+ * detected from the data stream in the saved game file.
+ */
+#define FIOSAVVSN "v2.2.1"
+
+/* old saved game format version strings */
+#define FIOSAVVSN1 "v2.2.0"
+
+/* read fuse/daemon/alarm record */
+static int fiorfda(osfildef *fp, vocddef *p, uint cnt)
+{
+ vocddef *q;
+ uint i;
+ uchar buf[14];
+
+ /* start by clearing out entire record */
+ for (i = 0, q = p ; i < cnt ; ++q, ++i)
+ q->vocdfn = MCMONINV;
+
+ /* now restore all the records from the file */
+ for (;;)
+ {
+ /* read a record, and quit if it's the last one */
+ if (osfrb(fp, buf, 13)) return(TRUE);
+ if ((i = osrp2(buf)) == 0xffff) return(FALSE);
+
+ /* restore this record */
+ q = p + i;
+ q->vocdfn = osrp2(buf+2);
+ q->vocdarg.runstyp = buf[4];
+ switch(buf[4])
+ {
+ case DAT_NUMBER:
+ q->vocdarg.runsv.runsvnum = osrp4s(buf+5);
+ break;
+ case DAT_OBJECT:
+ case DAT_FNADDR:
+ q->vocdarg.runsv.runsvobj = osrp2(buf+5);
+ break;
+ case DAT_PROPNUM:
+ q->vocdarg.runsv.runsvprp = osrp2(buf+5);
+ break;
+ }
+ q->vocdprp = osrp2(buf+9);
+ q->vocdtim = osrp2(buf+11);
+ }
+}
+
+/*
+ * 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 *fnamebuf, size_t buflen)
+{
+ osfildef *fp;
+ uint namelen;
+ char buf[sizeof(FIOSAVHDR_PREFIX) + 2];
+
+ /* open the input file */
+ if (!(fp = osfoprb(saved_file, OSFTSAVE)))
+ return FALSE;
+
+ /* read the prefix header and check */
+ if (osfrb(fp, buf, (int)(sizeof(FIOSAVHDR_PREFIX) + 2))
+ || memcmp(buf, FIOSAVHDR_PREFIX, sizeof(FIOSAVHDR_PREFIX)) != 0)
+ {
+ /*
+ * there's no game file information - close the file and
+ * indicate that we have no information
+ */
+ osfcls(fp);
+ return FALSE;
+ }
+
+ /* get the length of the filename */
+ namelen = osrp2(buf + sizeof(FIOSAVHDR_PREFIX));
+ if (namelen > buflen - 1)
+ namelen = buflen - 1;
+
+ /* read the filename */
+ if (osfrb(fp, fnamebuf, namelen))
+ {
+ osfcls(fp);
+ return FALSE;
+ }
+
+ /* null-terminate the string */
+ fnamebuf[namelen] = '\0';
+
+ /* done with the file */
+ osfcls(fp);
+
+ /* indicate that we found the information */
+ return TRUE;
+}
+
+/* restore game: returns TRUE on failure */
+int fiorso(voccxdef *vctx, char *fname)
+{
+ osfildef *fp;
+ objnum obj;
+ uchar *p;
+ uchar *mut;
+ uint mutsiz;
+ uint oldmutsiz;
+ int propcnt;
+ mcmcxdef *mctx = vctx->voccxmem;
+ uchar buf[sizeof(FIOSAVHDR) + sizeof(FIOSAVVSN)];
+ ushort newsiz;
+ int err = FALSE;
+ char timestamp[26];
+ int version = 0; /* version ID - 0 = current version */
+ int result;
+
+ /* presume success */
+ result = FIORSO_SUCCESS;
+
+ /* open the input file */
+ if (!(fp = osfoprb(fname, OSFTSAVE)))
+ return FIORSO_FILE_NOT_FOUND;
+
+ /* check for a prefix header - if it's there, skip it */
+ if (!osfrb(fp, buf, (int)(sizeof(FIOSAVHDR_PREFIX) + 2))
+ && memcmp(buf, FIOSAVHDR_PREFIX, sizeof(FIOSAVHDR_PREFIX)) == 0)
+ {
+ ulong skip_len;
+
+ /*
+ * The prefix header is present - skip it. The 2-byte value
+ * following the header is the length of the prefix data block
+ * (not including the header), so simply skip the additional
+ * number of bytes specified.
+ */
+ skip_len = (ulong)osrp2(buf + sizeof(FIOSAVHDR_PREFIX));
+ osfseek(fp, skip_len, OSFSK_CUR);
+ }
+ else
+ {
+ /*
+ * there's no prefix header - seek back to the start of the file
+ * and read the standard header information
+ */
+ osfseek(fp, 0, OSFSK_SET);
+ }
+
+
+ /* read headers and check */
+ if (osfrb(fp, buf, (int)(sizeof(FIOSAVHDR) + sizeof(FIOSAVVSN)))
+ || memcmp(buf, FIOSAVHDR, (size_t)sizeof(FIOSAVHDR)))
+ {
+ /* it's not a saved game file */
+ result = FIORSO_NOT_SAVE_FILE;
+ goto ret_error;
+ }
+
+ /* check the version string */
+ if (memcmp(buf + sizeof(FIOSAVHDR), FIOSAVVSN,
+ (size_t)sizeof(FIOSAVVSN)) == 0)
+ {
+ /* it's the current version */
+ version = 0;
+ }
+ else if (memcmp(buf + sizeof(FIOSAVHDR), FIOSAVVSN1,
+ (size_t)sizeof(FIOSAVVSN1)) == 0)
+ {
+ /* it's old version #1 */
+ version = 1;
+ }
+ else
+ {
+ /*
+ * this isn't a recognized version - the file must have been
+ * saved by a newer version of the system, so we can't assume we
+ * will be able to parse the format
+ */
+ result = FIORSO_BAD_FMT_VSN;
+ goto ret_error;
+ }
+
+ /*
+ * Read timestamp and check - the game must have been saved by the
+ * same .GAM file that we are now running, because the .SAV file is
+ * written entirely in terms of the contents of the .GAM file; any
+ * change in the .GAM file invalidates the .SAV file.
+ */
+ if (osfrb(fp, timestamp, 26)
+ || memcmp(timestamp, vctx->voccxtim, (size_t)26))
+ {
+ result = FIORSO_BAD_GAME_VSN;
+ goto ret_error;
+ }
+
+ /* first revert every object to original (post-compilation) state */
+ vocrevert(vctx);
+
+ /*
+ * the most common error from here on is simply a file read error,
+ * so presume that this is what will happen; if we are successful or
+ * encounter a different error, we'll change the status at that
+ * point
+ */
+ result = FIORSO_READ_ERROR;
+
+ /* go through file and load changed objects */
+ for (;;)
+ {
+ /* get the header */
+ if (osfrb(fp, buf, 7))
+ goto ret_error;
+
+ /* get the object number from the header, and stop if we're done */
+ obj = osrp2(buf+1);
+ if (obj == MCMONINV)
+ break;
+
+ /* if the object was dynamically allocated, recreate it */
+ if (buf[0] == 1)
+ {
+ int sccnt;
+ objnum sc;
+
+ /* create the object */
+ mutsiz = osrp2(buf + 3);
+ p = mcmalonum(mctx, (ushort)mutsiz, (mcmon)obj);
+
+ /* read the object's contents */
+ if (osfrb(fp, p, mutsiz))
+ goto ret_error;
+
+ /* get the superclass data (at most one superclass) */
+ sccnt = objnsc(p);
+ if (sccnt) sc = osrp2(objsc(p));
+
+ /* create inheritance records for the object */
+ vociadd(vctx, obj, MCMONINV, sccnt, &sc, VOCIFNEW | VOCIFVOC);
+
+#if 0
+ {
+ int wrdcnt;
+
+ /* read the object's vocabulary and add it back */
+ if (osfrb(fp, buf, 2))
+ goto ret_error;
+ wrdcnt = osrp2(buf);
+ while (wrdcnt--)
+ {
+ int len1;
+ int len2;
+ char wrd[80];
+
+ /* read the header */
+ if (osfrb(fp, buf, 6))
+ goto ret_error;
+ len1 = osrp2(buf+2);
+ len2 = osrp2(buf+4);
+
+ /* read the word text */
+ if (osfrb(fp, wrd, len1 + len2))
+ goto ret_error;
+
+ /* add the word */
+ vocadd2(vctx, buf[0], obj, buf[1], wrd, len1,
+ wrd + len1, len2);
+ }
+ }
+#endif
+
+ }
+ else
+ {
+ /* get the remaining data from the header */
+ propcnt = osrp2(buf + 3);
+ mutsiz = osrp2(buf + 5);
+
+ /* expand object if it's not big enough for mutsiz */
+ p = mcmlck(mctx, (mcmon)obj);
+ oldmutsiz = mcmobjsiz(mctx, (mcmon)obj) - objrst(p);
+ if (oldmutsiz < mutsiz)
+ {
+ newsiz = mutsiz - oldmutsiz;
+ p = (uchar *)objexp(mctx, obj, &newsiz);
+ }
+
+ /* reset statistics, and read mutable part from file */
+ mut = p + objrst(p);
+ objsnp(p, propcnt);
+ objsfree(p, mutsiz + objrst(p));
+ if (osfrb(fp, mut, mutsiz))
+ err = TRUE;
+
+ /* reset ignore flags as needed */
+ objsetign(mctx, obj);
+ }
+
+ /* touch and unlock the object */
+ mcmtch(mctx, (mcmon)obj);
+ mcmunlck(mctx, (mcmon)obj);
+ if (err)
+ goto ret_error;
+ }
+
+ /* read fuses/daemons/alarms */
+ if (fiorfda(fp, vctx->voccxdmn, vctx->voccxdmc)
+ || fiorfda(fp, vctx->voccxfus, vctx->voccxfuc)
+ || fiorfda(fp, vctx->voccxalm, vctx->voccxalc))
+ goto ret_error;
+
+ /* read the dynamically added and deleted vocabulary */
+ for (;;)
+ {
+ int len1;
+ int len2;
+ char wrd[80];
+ int flags;
+ int typ;
+
+ /* read the header */
+ if (osfrb(fp, buf, 8))
+ goto ret_error;
+
+ typ = buf[0];
+ flags = buf[1];
+ len1 = osrp2(buf+2);
+ len2 = osrp2(buf+4);
+ obj = osrp2(buf+6);
+
+ /* check to see if this is the end marker */
+ if (obj == MCMONINV) break;
+
+ /* read the word text */
+ if (osfrb(fp, wrd+2, len1))
+ goto ret_error;
+ if (len2)
+ {
+ wrd[len1 + 2] = ' ';
+ if (osfrb(fp, &wrd[len1 + 3], len2))
+ goto ret_error;
+ oswp2(wrd, len1 + len2 + 3);
+ }
+ else
+ oswp2(wrd, len1 + 2);
+
+ /* add or delete the word as appropriate */
+ if (flags & VOCFDEL)
+ vocdel1(vctx, obj, (char *)wrd, (prpnum)typ, FALSE, FALSE, FALSE);
+ else
+ vocadd2(vctx, buf[0], obj, buf[1], (uchar *)wrd+2, len1,
+ (uchar *)wrd+len1, len2);
+ }
+
+ /*
+ * the following was added in save format version "v2.2.1", so skip
+ * it if the save version is older than that
+ */
+ if (version != 1)
+ {
+ /* read the current "Me" object */
+ if (osfrb(fp, buf, 2))
+ goto ret_error;
+ vctx->voccxme = osrp2(buf);
+ }
+
+ /* done - close file and return success indication */
+ osfcls(fp);
+ return FIORSO_SUCCESS;
+
+ /* come here on failure - close file and return error indication */
+ret_error:
+ osfcls(fp);
+ return result;
+}
+
+/* write fuse/daemon/alarm block */
+static int fiowfda(osfildef *fp, vocddef *p, uint cnt)
+{
+ uchar buf[14];
+ uint i;
+
+ for (i = 0 ; i < cnt ; ++i, ++p)
+ {
+ if (p->vocdfn == MCMONINV) continue; /* not set - ignore */
+
+ oswp2(buf, i); /* element in array to be set */
+ oswp2(buf+2, p->vocdfn); /* object number for function/target */
+ buf[4] = p->vocdarg.runstyp; /* type of argument */
+ switch(buf[4])
+ {
+ case DAT_NUMBER:
+ oswp4s(buf+5, p->vocdarg.runsv.runsvnum);
+ break;
+ case DAT_OBJECT:
+ case DAT_FNADDR:
+ oswp2(buf+5, p->vocdarg.runsv.runsvobj);
+ break;
+ case DAT_PROPNUM:
+ oswp2(buf+5, p->vocdarg.runsv.runsvprp);
+ break;
+ }
+ oswp2(buf+9, p->vocdprp);
+ oswp2(buf+11, p->vocdtim);
+
+ /* write this record to file */
+ if (osfwb(fp, buf, 13)) return(TRUE);
+ }
+
+ /* write end record - -1 for array element number */
+ oswp2(buf, 0xffff);
+ return(osfwb(fp, buf, 13));
+}
+
+/* context for vocabulary saver callback function */
+struct fiosav_cb_ctx
+{
+ int err;
+ osfildef *fp;
+};
+
+#ifdef NEVER
+/*
+ * callback for vocabulary saver - called by voc_iterate for each word
+ * defined for a particular object, allowing us to write all the words
+ * attached to a dynamically allocated object to the save file
+ */
+static void fiosav_cb(struct fiosav_cb_ctx *ctx,
+ vocdef *voc, vocwdef *vocw)
+{
+ char buf[10];
+
+ /* write the part of speech, flags, and word lengths */
+ buf[0] = vocw->vocwtyp;
+ buf[1] = vocw->vocwflg;
+ oswp2(buf+2, voc->voclen);
+ oswp2(buf+4, voc->vocln2);
+ if (osfwb(ctx->fp, buf, 6)) ctx->err = TRUE;
+
+ /* write the words */
+ if (osfwb(ctx->fp, voc->voctxt, voc->voclen + voc->vocln2))
+ ctx->err = TRUE;
+}
+#endif
+
+/*
+ * Callback for vocabulary saver - called by voc_iterate for every
+ * word. We'll write the word if it was dynamically added or deleted,
+ * so that we can restore that status when the game is restored.
+ */
+static void fiosav_voc_cb(void *ctx0, vocdef *voc, vocwdef *vocw)
+{
+ struct fiosav_cb_ctx *ctx = (struct fiosav_cb_ctx *)ctx0;
+ char buf[10];
+
+ /* if the word was dynamically allocated or deleted, save it */
+ if ((vocw->vocwflg & VOCFNEW) || (vocw->vocwflg & VOCFDEL))
+ {
+ /* write the header information */
+ buf[0] = vocw->vocwtyp;
+ buf[1] = vocw->vocwflg;
+ oswp2(buf+2, voc->voclen);
+ oswp2(buf+4, voc->vocln2);
+ oswp2(buf+6, vocw->vocwobj);
+ if (osfwb(ctx->fp, buf, 8)) ctx->err = TRUE;
+
+ /* write the words */
+ if (osfwb(ctx->fp, voc->voctxt, voc->voclen + voc->vocln2))
+ ctx->err = TRUE;
+ }
+}
+
+
+/* save game; returns TRUE on failure */
+int fiosav(voccxdef *vctx, char *fname, char *game_fname)
+{
+ osfildef *fp;
+ vocidef ***vpg;
+ vocidef **v;
+ int i;
+ int j;
+ objnum obj;
+ uchar *p;
+ uchar *mut;
+ uint mutsiz;
+ int propcnt;
+ mcmcxdef *mctx = vctx->voccxmem;
+ uchar buf[8];
+ int err = FALSE;
+ struct fiosav_cb_ctx fnctx;
+
+ /* open the output file */
+ if ((fp = osfopwb(fname, OSFTSAVE)) == 0)
+ return TRUE;
+
+ /*
+ * If we have game file information, save the game file information
+ * with the saved game file. This lets the player start the
+ * run-time and restore the game by specifying only the saved game
+ * file.
+ */
+ if (game_fname != 0)
+ {
+ size_t len;
+
+ /* write the prefix header */
+ len = strlen(game_fname);
+ oswp2(buf, len);
+ if (osfwb(fp, FIOSAVHDR_PREFIX, (int)sizeof(FIOSAVHDR_PREFIX))
+ || osfwb(fp, buf, 2)
+ || osfwb(fp, game_fname, (int)len))
+ goto ret_error;
+ }
+
+ /* write save game header and timestamp */
+ if (osfwb(fp, FIOSAVHDR, (int)sizeof(FIOSAVHDR))
+ || osfwb(fp, FIOSAVVSN, (int)sizeof(FIOSAVVSN))
+ || osfwb(fp, vctx->voccxtim, 26))
+ goto ret_error;
+
+ /* go through each object, and write if it's been changed */
+ for (vpg = vctx->voccxinh, i = 0 ; i < VOCINHMAX ; ++vpg, ++i)
+ {
+ if (!*vpg) continue;
+ for (v = *vpg, obj = (i << 8), j = 0 ; j < 256 ; ++v, ++obj, ++j)
+ {
+ if (*v != 0)
+ {
+ /* write object if it's dirty */
+ if (mcmobjdirty(mctx, (mcmon)obj))
+ {
+ p = mcmlck(mctx, (mcmon)obj);
+ mut = p + objrst(p);
+ propcnt = objnprop(p);
+ mutsiz = objfree(p) - objrst(p);
+ if ((objflg(p) & OBJFINDEX) != 0)
+ mutsiz += propcnt * 4;
+
+ /*
+ * If the object was dynamically allocated, write
+ * the whole object. Otherwise, write just the
+ * mutable part.
+ */
+ if ((*v)->vociflg & VOCIFNEW)
+ {
+ /* indicate that the object is dynamic */
+ buf[0] = 1;
+ oswp2(buf + 1, obj);
+
+ /* write the entire object */
+ mutsiz = objfree(p);
+ oswp2(buf + 3, mutsiz);
+ if (osfwb(fp, buf, 7)
+ || osfwb(fp, p, mutsiz))
+ err = TRUE;
+
+#ifdef NEVER
+ {
+ int wrdcnt;
+
+ /* count the words, and write the count */
+ voc_count(vctx, obj, 0, &wrdcnt, (int *)0);
+ oswp2(buf, wrdcnt);
+ if (osfwb(fp, buf, 2))
+ err = TRUE;
+
+ /* write the words */
+ fnctx.err = 0;
+ fnctx.fp = fp;
+ voc_iterate(vctx, obj, fiosav_cb, &fnctx);
+ if (fnctx.err != 0)
+ err = TRUE;
+ }
+#endif
+ }
+ else if (mutsiz)
+ {
+ /* write number of properties, size of mut, and mut */
+ buf[0] = 0; /* indicate that the object is static */
+ oswp2(buf + 1, obj);
+ oswp2(buf + 3, propcnt);
+ oswp2(buf + 5, mutsiz);
+ if (osfwb(fp, buf, 7)
+ || osfwb(fp, mut, mutsiz))
+ err = TRUE;
+ }
+
+ mcmunlck(mctx, (mcmon)obj);
+ if (err != 0)
+ goto ret_error;
+ }
+ }
+ }
+ }
+
+ /* write end-of-objects indication */
+ buf[0] = 0;
+ oswp2(buf + 1, MCMONINV);
+ oswp4(buf + 3, 0);
+ if (osfwb(fp, buf, 7))
+ goto ret_error;
+
+ /* write fuses/daemons/alarms */
+ if (fiowfda(fp, vctx->voccxdmn, vctx->voccxdmc)
+ || fiowfda(fp, vctx->voccxfus, vctx->voccxfuc)
+ || fiowfda(fp, vctx->voccxalm, vctx->voccxalc))
+ goto ret_error;
+
+ /* write run-time vocabulary additions and deletions */
+ fnctx.fp = fp;
+ fnctx.err = 0;
+ voc_iterate(vctx, MCMONINV, fiosav_voc_cb, &fnctx);
+ if (fnctx.err)
+ goto ret_error;
+
+ /* write end marker for vocabulary additions and deletions */
+ oswp2(buf+6, MCMONINV);
+ if (osfwb(fp, buf, 8))
+ goto ret_error;
+
+ /* write the current "Me" object */
+ oswp2(buf, vctx->voccxme);
+ if (osfwb(fp, buf, 2))
+ goto ret_error;
+
+ /* done - close file and return success indication */
+ osfcls(fp);
+ os_settype(fname, OSFTSAVE);
+ return FALSE;
+
+ /* come here on failure - close file and return error indication */
+ret_error:
+ osfcls(fp);
+ return TRUE;
+}
+
} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/line_source_file.cpp b/engines/glk/tads/tads2/line_source_file.cpp
index 26536c0..b5da3d1 100644
--- a/engines/glk/tads/tads2/line_source_file.cpp
+++ b/engines/glk/tads/tads2/line_source_file.cpp
@@ -21,12 +21,1049 @@
*/
#include "glk/tads/tads2/line_source_file.h"
+#include "glk/tads/tads2/character_map.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/memory_cache_heap.h"
+#include "glk/tads/tads2/tokenizer.h"
+#include "glk/tads/os_glk.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
+/* initialize a pre-allocated linfdef, skipping debugger page setup */
+void linfini2(mcmcxdef *mctx, linfdef *linf,
+ char *filename, int flen, osfildef *fp, int new_line_records)
+{
+ /* set up method pointers */
+ linf->linflin.lingetp = linfget;
+ linf->linflin.linclsp = linfcls;
+ linf->linflin.linppos = linfppos;
+ linf->linflin.linglop = (new_line_records ? linfglop2 : linfglop);
+ linf->linflin.linwrtp = linfwrt;
+ linf->linflin.lincmpp = linfcmp;
+ linf->linflin.linactp = linfact;
+ linf->linflin.lindisp = linfdis;
+ linf->linflin.lintellp = linftell;
+ linf->linflin.linseekp = linfseek;
+ linf->linflin.linreadp = linfread;
+ linf->linflin.linpaddp = linfpadd;
+ linf->linflin.linqtopp = linfqtop;
+ linf->linflin.lingetsp = linfgets;
+ linf->linflin.linnamp = linfnam;
+ linf->linflin.linlnump = linflnum;
+ linf->linflin.linfindp = linffind;
+ linf->linflin.lingotop = linfgoto;
+ linf->linflin.linofsp = linfofs;
+ linf->linflin.linrenp = linfren;
+ linf->linflin.lindelp = linfdelnum;
+
+ /* set up instance data */
+ linf->linflin.linbuf = linf->linfbuf;
+ linf->linflin.linflg = 0;
+ memcpy(linf->linfnam, filename, (size_t)flen);
+ linf->linfnam[flen] = '\0';
+ linf->linfbuf[0] = '\0';
+ linf->linfbufnxt = 0;
+ linf->linfnxtlen = 0;
+ linf->linffp = fp;
+ linf->linfnum = 0;
+ linf->linflin.linlln = 4; /* OPCLINE operand is seek offset in file */
+ linf->linfmem = mctx; /* save memory manager context */
+ linf->linfcrec = 0; /* no debugger records written yet */
+}
+
+/*
+ * Initialize a file line source object. If must_find_file is true,
+ * we'll fail if we can't find the file. Otherwise, we'll create the
+ * linfdef even if we can't find the file, reserving the maximum space
+ * for its path name to be filled in later.
+ */
+linfdef *linfini(mcmcxdef *mctx, errcxdef *ec, char *filename,
+ int flen, tokpdef *path, int must_find_file,
+ int new_line_records)
+{
+ int i;
+ objnum *objp;
+ linfdef *linf;
+ osfildef *fp;
+ char fbuf[OSFNMAX + 1];
+ tokpdef fakepath;
+ int len;
+
+ if (!path)
+ {
+ path = &fakepath;
+ fakepath.tokpnxt = (tokpdef *)0;
+ fakepath.tokplen = 0;
+ }
+
+ /* search through the path list */
+ for ( ; path ; path = path->tokpnxt)
+ {
+ char last;
+
+ /* prefix the current path */
+ if ((len = path->tokplen) != 0)
+ {
+ memcpy(fbuf, path->tokpdir, (size_t)len);
+ last = fbuf[len - 1];
+ if (last == OSPATHCHAR ||
+ (OSPATHALT && strchr(OSPATHALT, last)))
+ /* do nothing */ ;
+ else
+ {
+ /* append path separator character */
+ fbuf[len++] = OSPATHCHAR;
+ }
+ }
+
+ /* add the filename and null-terminate */
+ memcpy(fbuf + len, filename, (size_t)flen);
+ fbuf[len + flen] = '\0';
+
+ /* attempt to open this file */
+ if ((fp = osfoprs(fbuf, OSFTTEXT)) != 0)
+ break;
+ }
+
+ /*
+ * If no file opened yet, search tads path; if that doesn't work,
+ * let the debugger UI try to find the file. If nothing works, give
+ * up and return failure.
+ */
+ if (fp == 0
+ && (!os_locate(filename, flen, (char *)0, fbuf, sizeof(fbuf))
+ || (fp = osfoprs(fbuf, OSFTTEXT)) == 0))
+ {
+ /*
+ * Ask the debugger UI for advice. If the debugger isn't
+ * present, we'll get a failure code from this routine.
+ */
+ if (!dbgu_find_src(filename, flen, fbuf, sizeof(fbuf),
+ must_find_file))
+ return 0;
+
+ /* try opening the file */
+ if (fbuf[0] == '\0')
+ {
+ /*
+ * we didn't get a filename - the UI wants to defer finding
+ * the file until later
+ */
+ fp = 0;
+ }
+ else
+ {
+ /* we got a filename from the UI - try opening it */
+ fp = osfoprs(fbuf, OSFTTEXT);
+ }
+
+ /*
+ * if the file isn't present, and we're required to find it,
+ * return failure
+ */
+ if (fp == 0 && must_find_file)
+ return 0;
+ }
+
+ /* figure out how much space we need for the file's full name */
+ if (fp == 0)
+ {
+ /*
+ * we didn't find the file, so we don't yet know its name - use
+ * the maximum possible filename length for the buffer size, so
+ * that we can store the final filename if we should figure out
+ * where the file is later on
+ */
+ fbuf[0] = '\0';
+ len = sizeof(fbuf);
+ }
+ else
+ {
+ /*
+ * we found the file, so we have its final name - allocate space
+ * for the known name
+ */
+ len = (int)strlen(fbuf);
+ }
+
+ /* allocate the linfdef */
+ linf = (linfdef *)mchalo(ec, (ushort)(sizeof(linfdef) + flen
+ + len + 1), "linfini");
+
+ /* do the basic initialization */
+ linfini2(mctx, linf, filename, flen, fp, new_line_records);
+
+ memcpy(linf->linfnam + flen + 1, fbuf, (size_t)len);
+ linf->linfnam[flen + 1 + len] = '\0';
+
+ /* set all debugger pages to not-yet-allocated */
+ for (i = LINFPGMAX, objp = linf->linfpg ; i ; ++objp, --i)
+ *objp = MCMONINV;
+
+ /* return the new line source object */
+ return linf;
+}
+
+int linfget(lindef *lin)
+{
+# define linf ((linfdef *)lin)
+ char *p;
+ size_t rdlen;
+ int nl_len;
+
+ /* remember seek position of start of current line */
+ linf->linfseek = osfpos(linf->linffp);
+
+ /*
+ * if we have data left in the buffer after the end of this line,
+ * move it to the start of the buffer
+ */
+ if (linf->linfnxtlen != 0)
+ {
+ /* move the data down */
+ memmove(linf->linfbuf, linf->linfbuf + linf->linfbufnxt,
+ linf->linfnxtlen);
+
+ /*
+ * adjust the seek position to account for the fact that we've
+ * read ahead in the file
+ */
+ linf->linfseek -= linf->linfnxtlen;
+
+ /*
+ * Fill up the rest of the buffer. Leave one byte for a null
+ * terminator and one byte for a possible extra newline pair
+ * character (see below), hence fill to sizeof(buf)-2.
+ */
+ rdlen = osfrbc(linf->linffp, linf->linfbuf + linf->linfnxtlen,
+ sizeof(linf->linfbuf) - linf->linfnxtlen - 2);
+
+ /*
+ * the total space is the amount we had left over plus the
+ * amount we just read
+ */
+ rdlen += linf->linfnxtlen;
+ }
+ else
+ {
+ /*
+ * We have nothing in the buffer - fill it up. Fill to
+ * sizeof(buf)-2 to leave room for a null terminator plus a
+ * possible extra newline pair character (see below).
+ */
+ rdlen = osfrbc(linf->linffp, linf->linfbuf,
+ sizeof(linf->linfbuf) - 2);
+ }
+
+ /*
+ * if there's nothing in the buffer at this point, we've reached the
+ * end of the file
+ */
+ if (rdlen == 0)
+ return TRUE;
+
+ /*
+ * if the last line was not a continuation line, increment the line
+ * counter for the start of a new line
+ */
+ if (!(lin->linflg & LINFMORE))
+ ++(linf->linfnum);
+
+ /* null-terminate the buffer contents */
+ linf->linfbuf[rdlen] = '\0';
+
+ /* perform character mapping on th new part only */
+ for (p = linf->linfbuf + linf->linfnxtlen ; *p != '\0' ; ++p)
+ *p = cmap_n2i(*p);
+
+ /*
+ * scan the for the first newline in the buffer, allowing newline
+ * conventions that involve either CR or LF
+ */
+ for (p = linf->linfbuf ; *p != '\n' && *p != '\r' && *p != '\0' ; ++p) ;
+
+ /*
+ * Check to see if this character is followed by its newline pair
+ * complement, to allow for either CR-LF or LF-CR sequences, as well
+ * as plain single-byte newline (CR or LF) sequences.
+ *
+ * First, though, one weird special case: if this character is at
+ * the read limit in the buffer, the complementary character might
+ * be lurking in the next byte that we haven't read. In this case,
+ * use that one-byte reserve we have left (we filled the buffer only
+ * to length-2 so far) and read the next byte.
+ */
+ if (*p != '\0' && p + 1 == linf->linfbuf + sizeof(linf->linfbuf) - 2)
+ {
+ /*
+ * we've filled the buffer to but not including the reserve for
+ * just this case - fetch the extra character
+ */
+ if (osfrbc(linf->linffp, p + 1, 1) == 1)
+ {
+ /* increase the total read length for the extra byte */
+ ++rdlen;
+ *(p+2) = '\0';
+ }
+ }
+
+ /*
+ * now we can check for the newline type, since we have definitely
+ * read the full paired sequence
+ */
+ if (*p == '\0')
+ {
+ /* there's no newline in the buffer - we'll return a partial line */
+ nl_len = 0;
+
+ /* set the partial line flag */
+ lin->linflg |= LINFMORE;
+
+ /* return the entire buffer */
+ lin->linlen = rdlen;
+
+ /* there's nothing left for the next time through */
+ linf->linfnxtlen = 0;
+ }
+ else
+ {
+ /* check for a complementary pair */
+ if ((*p == '\n' && *(p+1) == '\r') || (*p == '\r' && *(p+1) == '\n'))
+ {
+ /* we have a paired newline */
+ nl_len = 2;
+ }
+ else
+ {
+ /* we have but a single-character newline sequence */
+ nl_len = 1;
+ }
+
+ /* this is the end of a line */
+ lin->linflg &= ~LINFMORE;
+
+ /*
+ * return only the part of the buffer up to, but not including,
+ * the newline
+ */
+ lin->linlen = (p - linf->linfbuf);
+
+ /* null-terminate the buffer at the newline */
+ *p = '\0';
+
+ /*
+ * anything remaining after the newline sequence is available
+ * for reading the next time through
+ */
+ linf->linfbufnxt = ((p + nl_len) - linf->linfbuf);
+ linf->linfnxtlen = rdlen - linf->linfbufnxt;
+ }
+
+ /* make sure buffer pointer is correct */
+ lin->linbuf = linf->linfbuf;
+
+ LINFDEBUG(printf("%s\n", linf->linfbuf));
+
+ /* success */
+ return FALSE;
+
+# undef linf
+}
+
+/* make printable string from position in file (for error reporting) */
+void linfppos(lindef *lin, char *buf, uint buflen)
+{
+ VARUSED(buflen);
+
+ sprintf(buf, "%s(%lu): ", ((linfdef *)lin)->linfnam,
+ ((linfdef *)lin)->linfnum);
+}
+
+/* close line source */
+void linfcls(lindef *lin)
+{
+ osfcls(((linfdef *)lin)->linffp);
+}
+
+/* generate operand of OPCLINE (source-line debug) instruction */
+void linfglop(lindef *lin, uchar *buf)
+{
+ oswp4(buf, ((linfdef *)lin)->linfseek); /* save seek position of line */
+}
+
+/* generate new-style operand of OPCLINE instruction */
+void linfglop2(lindef *lin, uchar *buf)
+{
+ oswp4(buf, ((linfdef *)lin)->linfnum); /* save seek position of line */
+}
+
+/* save line source information to binary (.gam) file; TRUE ==> error */
+int linfwrt(lindef *lin, osfildef *fp)
+{
+# define linf ((linfdef *)lin)
+ uchar buf[UCHAR_MAX + 6];
+ size_t len;
+ uint pgcnt;
+ uchar *objp;
+ mcmon *objn;
+
+ buf[0] = lin->linid;
+ len = strlen(linf->linfnam);
+ if (len > UCHAR_MAX)
+ return FALSE;
+ buf[1] = (uchar)len;
+ oswp4(buf + 2, linf->linfcrec);
+ memcpy(buf + 6, linf->linfnam, (size_t)buf[1]);
+ if (osfwb(fp, buf, (int)(buf[1] + 6))) return(TRUE);
+
+ /* write the debug source pages */
+ if (!linf->linfcrec) return(FALSE); /* no debug records at all */
+ pgcnt = 1 + ((linf->linfcrec - 1) >> 10); /* figure number of pages */
+
+ for (objn = linf->linfpg ; pgcnt ; ++objn, --pgcnt)
+ {
+ objp = mcmlck(linf->linfmem, *objn);
+ if (osfwb(fp, objp, (1024 * DBGLINFSIZ))) return(TRUE);
+ mcmunlck(linf->linfmem, *objn);
+ }
+
+ return(FALSE);
+
+# undef linf
+}
+
+/* load a file-line-source from binary (.gam) file */
+int linfload(osfildef *fp, dbgcxdef *dbgctx, errcxdef *ec, tokpdef *path)
+{
+ linfdef *linf;
+ uchar buf[UCHAR_MAX + 6];
+ uint pgcnt;
+ uchar *objp;
+ mcmon *objn;
+
+ /* read the source's description from the file */
+ if (osfrb(fp, buf, 6)
+ || osfrb(fp, buf + 6, (int)buf[1]))
+ return TRUE;
+
+ /* initialize the linfdef */
+ if (!(linf = linfini(dbgctx->dbgcxmem, ec, (char *)buf + 6,
+ (int)buf[1], path, FALSE, FALSE)))
+ {
+ errlog1(ec, ERR_NOSOURC, ERRTSTR,
+ errstr(ec, (char *)buf+6, (int)buf[1]));
+ return TRUE;
+ }
+
+ /* if we opened the file, close it - don't hold all files open */
+ if (linf->linffp != 0)
+ {
+ osfcls(linf->linffp);
+ linf->linffp = 0;
+ }
+
+ /* link into debug line source chain */
+ linf->linflin.linnxt = dbgctx->dbgcxlin;
+ dbgctx->dbgcxlin = &linf->linflin;
+ linf->linflin.linid = buf[0];
+ linf->linfcrec = osrp4(buf + 2);
+
+ /* make sure the max line id is set above current line */
+ if (buf[0] >= dbgctx->dbgcxfid)
+ dbgctx->dbgcxfid = buf[0] + 1;
+
+ /* make sure we have some debug records */
+ if (!linf->linfcrec)
+ return FALSE;
+
+ /* figure number of pages */
+ pgcnt = 1 + ((linf->linfcrec - 1) >> 10);
+
+ /* allocate and read the debug source pages */
+ for (objn = linf->linfpg ; pgcnt ; ++objn, --pgcnt)
+ {
+ objp = mcmalo(linf->linfmem, (ushort)(1024 * DBGLINFSIZ), objn);
+ if (osfrb(fp, objp, (1024 * DBGLINFSIZ))) return(TRUE);
+ mcmunlck(linf->linfmem, *objn);
+ }
+
+ /* success */
+ return FALSE;
+}
+
+/* add a debugger line record for the current line being compiled */
+void linfcmp(lindef *lin, uchar *buf)
+{
+ uint pg;
+ uchar *objptr;
+# define linf ((linfdef *)lin)
+
+ /* figure out which page to use, and lock it */
+ pg = linf->linfcrec >> 10; /* 2^10 records per page */
+ if (pg >= LINFPGMAX)
+ errsig(linf->linfmem->mcmcxgl->mcmcxerr, ERR_MANYDBG);
+ if (linf->linfpg[pg] == MCMONINV)
+ objptr = mcmalo(linf->linfmem, (ushort)(1024 * DBGLINFSIZ),
+ &linf->linfpg[pg]);
+ else
+ objptr = mcmlck(linf->linfmem, linf->linfpg[pg]);
+
+ /* write the record to the appropriate offset within the page */
+ memcpy(objptr + (linf->linfcrec & 1023) * DBGLINFSIZ, buf,
+ (size_t)DBGLINFSIZ);
+
+ /* increment counter of line records so far */
+ ++(linf->linfcrec);
+
+ /* done with page - touch it and unlock it */
+ mcmtch(linf->linfmem, linf->linfpg[pg]);
+ mcmunlck(linf->linfmem, linf->linfpg[pg]);
+
+# undef linf
+}
+
+/*
+ * Renumber an existing object. Searches through all line records for
+ * any with the given object number, and changes the number to the new
+ * number if found.
+ */
+void linfren(lindef *lin, objnum oldnum, objnum newnum)
+{
+# define linf ((linfdef *)lin)
+ uint pgcnt;
+ uchar *objp;
+ mcmon *pgobjn;
+ int i;
+ int pgtot;
+ int tot;
+
+ /* figure the number of pages - if no lines, stop now */
+ tot = linf->linfcrec;
+ if (tot == 0)
+ return;
+
+ /* calculate the number of pages to check */
+ pgcnt = 1 + ((tot - 1) >> 10);
+
+ /* scan each page */
+ for (pgobjn = linf->linfpg ; pgcnt ; ++pgobjn, --pgcnt, tot -= 1024)
+ {
+ /* lock the page */
+ objp = mcmlck(linf->linfmem, *pgobjn);
+
+ /* figure the number on this page */
+ pgtot = (tot > 1024 ? 1024 : tot);
+
+ /* scan each record on this page */
+ for (i = 0 ; i < pgtot ; ++i, objp += DBGLINFSIZ)
+ {
+ /* check this one */
+ if (osrp2(objp) == oldnum)
+ {
+ /* it matches - renumber it */
+ oswp2(objp, newnum);
+ }
+ }
+
+ /* done with the page - touch it and unlock it */
+ mcmtch(linf->linfmem, *pgobjn);
+ mcmunlck(linf->linfmem, *pgobjn);
+ }
+
+# undef linf
+}
+
+/*
+ * Delete an existing object. Searches through all line records for any
+ * with the given object number, and removes line records for the object
+ * number if found.
+ */
+void linfdelnum(lindef *lin, objnum objn)
+{
+# define linf ((linfdef *)lin)
+ uint pgcnt;
+ uchar *objp;
+ uchar *objp_orig;
+ mcmon *pgobjn;
+ int i;
+ int pgtot;
+ int tot;
+
+ /* figure the number of pages - if no lines, stop now */
+ tot = linf->linfcrec;
+ if (tot == 0)
+ return;
+
+ /* calculate the number of pages to check */
+ pgcnt = 1 + ((tot - 1) >> 10);
+
+ /* scan each page */
+ for (pgobjn = linf->linfpg ; pgcnt ; ++pgobjn, --pgcnt, tot -= 1024)
+ {
+ /* lock the page */
+ objp = objp_orig = mcmlck(linf->linfmem, *pgobjn);
+
+ /* figure the number on this page */
+ pgtot = (tot > 1024 ? 1024 : tot);
+
+ /* scan each record on this page */
+ for (i = 0 ; i < pgtot ; ++i, objp += DBGLINFSIZ)
+ {
+ int j;
+
+ /* check this one */
+ if (osrp2(objp) == objn)
+ {
+ uchar *nxtp;
+ uint pg;
+ int delcnt;
+ int totrem;
+
+ /*
+ * it matches - delete it, along with any subsequent
+ * contiguous entries that also match it
+ */
+ for (delcnt = 1, j = i + 1 ; j < pgtot ; ++j, ++delcnt)
+ {
+ /*
+ * if this one doesn't match, we've found the end of
+ * the contiguous records for this object
+ */
+ if (osrp2(objp + (j - i)*DBGLINFSIZ) != objn)
+ break;
+ }
+
+ /* close up the gap on this page */
+ if (j < pgtot)
+ memmove(objp, objp + delcnt*DBGLINFSIZ,
+ (pgtot - j)*DBGLINFSIZ);
+
+ /*
+ * if this isn't the last page, copy the bottom of the
+ * next page to the gap at the top of this page
+ */
+ if (pgcnt > 1)
+ {
+ /* lock the next page */
+ nxtp = mcmlck(linf->linfmem, *(pgobjn + 1));
+
+ /*
+ * copy from the beginning of the next page to the
+ * end of this page
+ */
+ memcpy(objp_orig + (pgtot - delcnt)*DBGLINFSIZ,
+ nxtp, delcnt*DBGLINFSIZ);
+
+ /* done with the page */
+ mcmunlck(linf->linfmem, *(pgobjn + 1));
+ }
+ else
+ {
+ /*
+ * this is the last page, so there's no next page to
+ * copy items from - reduce the count of items on
+ * this page accordingly
+ */
+ pgtot -= delcnt;
+ }
+
+ /*
+ * Now rearrange all subsequent pages to accommodate the
+ * gap we just created
+ */
+ for (totrem = tot, pg = 1 ; pg < pgcnt ;
+ totrem -= 1024, ++pg)
+ {
+ uchar *curp;
+ int curtot;
+
+ /* figure how many we have on this page */
+ curtot = (totrem > 1024 ? 1024 : totrem);
+
+ /* lock this page */
+ curp = mcmlck(linf->linfmem, *(pgobjn + pg));
+
+ /* delete from the start of this page */
+ memmove(curp, curp + delcnt*DBGLINFSIZ,
+ (curtot - delcnt)*DBGLINFSIZ);
+
+ /* if there's another page, copy from it */
+ if (pg + 1 < pgcnt)
+ {
+ /* lock the next page */
+ nxtp = mcmlck(linf->linfmem, *(pgobjn + pg + 1));
+
+ /*
+ * copy from the start of the next page to the
+ * end of this page
+ */
+ memcpy(curp + (curtot - delcnt)*DBGLINFSIZ,
+ nxtp, delcnt*DBGLINFSIZ);
+
+ /* unlock it */
+ mcmunlck(linf->linfmem, *(pgobjn + pg + 1));
+ }
+
+ /* done with the page - touch it and unlock it */
+ mcmtch(linf->linfmem, *(pgobjn + pg));
+ mcmunlck(linf->linfmem, *(pgobjn + pg));
+ }
+
+ /* deduct the removed records from the total */
+ linf->linfcrec -= delcnt;
+ }
+ }
+
+ /* done with the page - touch it and unlock it */
+ mcmtch(linf->linfmem, *pgobjn);
+ mcmunlck(linf->linfmem, *pgobjn);
+ }
+
+# undef linf
+}
+
+
+/* find the nearest line record to a file seek location */
+void linffind(lindef *lin, char *buf, objnum *objp, uint *ofsp)
+{
+# define linf ((linfdef *)lin)
+ uint pg;
+ uchar *objptr;
+ uchar *bufptr;
+ long first;
+ long last;
+ long cur;
+ ulong seekpos;
+ ulong curpos = 0;
+ objnum objn;
+ uint ofs;
+
+ /* get desired seek position out of buffer */
+ seekpos = osrp4(buf);
+
+ /* we haven't traversed any records yet */
+ objn = MCMONINV;
+ ofs = 0;
+
+ /* run a binary search for the indicated line record */
+ first = 0;
+ last = linf->linfcrec - 1;
+ for (;;)
+ {
+ /* make sure we're not out of records entirely */
+ if (first > last)
+ {
+ /* return the most recent record found - it's closest */
+ *objp = objn;
+ *ofsp = ofs;
+
+ /* set the position to that of the line we actually found */
+ oswp4(buf, curpos);
+ return;
+ }
+
+ /* split the difference */
+ cur = first + (last - first)/2;
+
+ /* calculate the page containing this item */
+ pg = cur >> 10;
+
+ /* get object + offset corresponding to current source line */
+ objptr = mcmlck(linf->linfmem, linf->linfpg[pg]);
+ bufptr = objptr + ((cur & 1023) * DBGLINFSIZ);
+ objn = osrp2(bufptr);
+ ofs = osrp2(bufptr + 2);
+ mcmunlck(linf->linfmem, linf->linfpg[pg]);
+
+ /* read user data out of the object's OPCLINE record */
+ objptr = mcmlck(linf->linfmem, (mcmon)objn);
+ bufptr = objptr + ofs + 5;
+ curpos = osrp4(bufptr);
+ mcmunlck(linf->linfmem, (mcmon)objn);
+
+ /* see what we have */
+ if (curpos == seekpos)
+ {
+ *objp = objn;
+ *ofsp = ofs;
+ return;
+ }
+ else if (curpos < seekpos)
+ first = (cur == first ? first + 1 : cur);
+ else
+ last = (cur == last ? last - 1 : cur);
+ }
+
+# undef linf
+}
+
+/*
+ * copy line records to an array of linfinfo structures
+ */
+void linf_copy_linerecs(linfdef *linf, struct linfinfo *info)
+{
+ uint pg;
+ uint prvpg;
+ uchar *objptr;
+ uchar *bufptr;
+ long last;
+ long cur;
+
+ /* note the last element */
+ last = linf->linfcrec;
+
+ /* if there are no records, there's nothing to do */
+ if (last == 0)
+ return;
+
+ /* load the first page of records */
+ prvpg = 0;
+ pg = 0;
+ objptr = mcmlck(linf->linfmem, linf->linfpg[0]);
+
+ /* scan the records */
+ for (cur = 0 ; cur < last ; ++cur, ++info)
+ {
+ uchar *codeptr;
+
+ /* calculate the page containing this item */
+ pg = cur >> 10;
+
+ /* if it's different than the last page, load the next page */
+ if (pg != prvpg)
+ {
+ /* unlock the previous page */
+ mcmunlck(linf->linfmem, linf->linfpg[prvpg]);
+
+ /* load the next page */
+ objptr = mcmlck(linf->linfmem, linf->linfpg[pg]);
+
+ /* this is now the previous page */
+ prvpg = pg;
+ }
+
+ /* get object + offset corresponding to current source line */
+ bufptr = objptr + ((cur & 1023) * DBGLINFSIZ);
+ info->objn = osrp2(bufptr);
+ info->ofs = osrp2(bufptr + 2);
+
+ /* read source location data out of the object's OPCLINE record */
+ codeptr = mcmlck(linf->linfmem, (mcmon)info->objn);
+ bufptr = codeptr + info->ofs + 5;
+ info->fpos = osrp4(bufptr);
+ mcmunlck(linf->linfmem, (mcmon)info->objn);
+ }
+
+ /* unlock the last page */
+ mcmunlck(linf->linfmem, linf->linfpg[prvpg]);
+}
+
+/* disactivate line source under debugger - close file */
+void linfdis(lindef *lin)
+{
+# define linf ((linfdef *)lin)
+
+ if (linf->linffp)
+ {
+ osfcls(linf->linffp);
+ linf->linffp = (osfildef *)0;
+ }
+
+# undef linf
+}
+
+/* activate line source under debugger - open file */
+void linfact(lindef *lin)
+{
+ char *fname;
+# define linf ((linfdef *)lin)
+
+ /* get the name buffer, and advance to the full path name portion */
+ fname = linf->linfnam;
+ fname += strlen(fname) + 1;
+
+ /*
+ * If the full path name is empty, it means that the UI told us to
+ * defer searching for the file until we actually need the file. At
+ * this point, we actually need the file. Ask the UI again to find
+ * the file.
+ */
+ if (fname[0] != '\0'
+ || dbgu_find_src(linf->linfnam, strlen(linf->linfnam),
+ fname, OSFNMAX, TRUE))
+ {
+ /* open the file */
+ linf->linffp = osfoprs(fname, OSFTTEXT);
+ }
+ else
+ {
+ /* there's no file to open */
+ linf->linffp = 0;
+ }
+
+# undef linf
+}
+
+/* get current seek position */
+void linftell(lindef *lin, uchar *pos)
+{
+# define linf ((linfdef *)lin)
+ long seekpos;
+
+ seekpos = osfpos(linf->linffp);
+ oswp4(pos, seekpos);
+
+# undef linf
+}
+
+/* seek to a new position */
+void linfseek(lindef *lin, uchar *pos)
+{
+# define linf ((linfdef *)lin)
+ long seekpos;
+
+ seekpos = osrp4(pos);
+ osfseek(linf->linffp, seekpos, OSFSK_SET);
+
+# undef linf
+}
+
+/* read bytes - fread-style interface */
+int linfread(lindef *lin, uchar *buf, uint siz)
+{
+# define linf ((linfdef *)lin)
+
+ return osfrbc(linf->linffp, buf, siz);
+
+# undef linf
+}
+
+/* add a signed delta to a seek position */
+void linfpadd(lindef *lin, uchar *pos, long delta)
+{
+# define linf ((linfdef *)lin)
+ long seekpos;
+
+ seekpos = osrp4(pos);
+ seekpos += delta;
+ if (seekpos < 0) seekpos = 0;
+ oswp4(pos, seekpos);
+
+# undef linf
+}
+
+/* query whether we're at top of file */
+int linfqtop(lindef *lin, uchar *pos)
+{
+# define linf ((linfdef *)lin)
+
+ return(osrp4(pos) == 0);
+
+# undef linf
+}
+
+/* read one line at current position - fgets-style interface */
+int linfgets(lindef *lin, uchar *buf, uint siz)
+{
+ int ret;
+ long startpos;
+ uchar *p;
+# define linf ((linfdef *)lin)
+
+ /* note the seek offset at the start of the line */
+ startpos = osfpos(linf->linffp);
+
+ /* read the next line */
+ ret = (osfgets((char *)buf, siz, linf->linffp) != 0);
+ if (!ret)
+ return ret;
+
+ /* scan for non-standard line endings */
+ for (p = buf ; *p != '\0' && *p != '\r' && *p != '\n' ; ++p) ;
+ if (*p != '\0')
+ {
+ uchar *nxt;
+
+ /*
+ * Scan for non-line-ending characters after this line-ending
+ * character. If we find any, we must have non-standard newline
+ * conventions in this file. To be tolerant of these, seek back
+ * to the start of the next line in these cases and read the
+ * next line from the new location.
+ */
+ for (nxt = p + 1 ; *nxt == '\r' || *nxt == '\n' ; ++nxt) ;
+ if (*nxt == '\0')
+ {
+ /*
+ * we had only line-ending characters after the first
+ * line-ending character -- simply end the line after the
+ * first line-ending character
+ */
+ *(p+1) = '\0';
+ }
+ else
+ {
+ /*
+ * We had a line-ending character in the middle of other
+ * text, so we must have a file that doesn't conform to
+ * local newline conventions. Seek back to the next
+ * character following the last line-ending character so
+ * that we start the next line here, and end the current
+ * line after the first line-ending character.
+ */
+ *(p+1) = '\0';
+ osfseek(linf->linffp, startpos + (nxt - buf), OSFSK_SET);
+ }
+ }
+
+ /* return the result */
+ return ret;
+
+# undef linf
+}
+
+/* get name of line source */
+void linfnam(lindef *lin, char *buf)
+{
+# define linf ((linfdef *)lin)
+
+ strcpy(buf, linf->linfnam);
+
+# undef linf
+}
+
+/* get the current line number */
+ulong linflnum(lindef *lin)
+{
+# define linf ((linfdef *)lin)
+
+ return linf->linfnum;
+
+# undef linf
+}
+
+/* go to top/bottom of line source */
+void linfgoto(lindef *lin, int where)
+{
+# define linf ((linfdef *)lin)
+
+ osfseek(linf->linffp, 0L, where);
+
+# undef linf
+}
+
+/* return current seek offset within source */
+long linfofs(lindef *lin)
+{
+# define linf ((linfdef *)lin)
+
+ return(osfpos(linf->linffp));
+
+# undef linf
+}
} // End of namespace TADS2
} // End of namespace TADS
diff --git a/engines/glk/tads/tads2/memory_cache.cpp b/engines/glk/tads/tads2/memory_cache.cpp
index 3bab3b0..359f1b3 100644
--- a/engines/glk/tads/tads2/memory_cache.cpp
+++ b/engines/glk/tads/tads2/memory_cache.cpp
@@ -21,11 +21,1167 @@
*/
#include "glk/tads/tads2/memory_cache.h"
+#include "glk/tads/tads2/memory_cache_heap.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/os_glk.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
+/* get an unused object cache entry, allocating a new page if needed */
+static mcmodef *mcmoal(mcmcx1def *ctx, mcmon *objnum);
+
+/* split a (previously free) block into two pieces */
+static void mcmsplt(mcmcx1def *ctx, mcmon n, ushort siz);
+
+/* unlink an object from a doubly-linked list */
+static void mcmunl(mcmcx1def *ctx, mcmon n, mcmon *lst);
+
+/* initialize a cache, return cache context */
+/* find free block: find a block from the free pool to satisfy a request */
+static mcmodef *mcmffb(mcmcx1def *ctx, ushort siz, mcmon *nump);
+
+/* add page pagenum, initializing entries after firstunu to unused */
+static void mcmadpg(mcmcx1def *ctx, uint pagenum, mcmon firstunu);
+
+/* link an object into a doubly-linked list at the head of the list */
+static void mcmlnkhd(mcmcx1def *ctx, mcmon *lst, mcmon n);
+
+/* try to allocate a new chunk from the heap */
+static uchar *mcmhalo(mcmcx1def *ctx);
+
+/* relocate blocks in a heap */
+static uchar *mcmreloc(mcmcx1def *ctx, uchar *start, uchar *end);
+
+/* find next free heap block */
+static uchar *mcmffh(mcmcx1def *ctx, uchar *p);
+
+#ifdef NEVER
+/* update entry to account for a block relocation */
+static void mcmmove(mcmcx1def *ctx, mcmodef *obj, uchar *newaddr);
+#else /* NEVER */
+#define mcmmove(ctx, o, new) ((o)->mcmoptr = (new))
+#endif /* NEVER */
+
+/* consolidate two contiguous free blocks into a single block */
+static void mcmconsol(mcmcx1def *ctx, uchar *p);
+
+/* collect garbage in all heaps */
+static void mcmgarb(mcmcx1def *ctx);
+
+/* make some room by swapping or discarding objects */
+static int mcmswap(mcmcx1def *ctx, ushort siz);
+
+/* toss out an object; returns TRUE if successful */
+static int mcmtoss(mcmcx1def *ctx, mcmon objnum);
+
+/* next heap block, given a heap block (points to header) */
+/* uchar *mcmhnxt(mcmcx1def *ctx, uchar *p) */
+
+#define mcmnxh(ctx, p) \
+ ((p) + osrndsz(sizeof(mcmon)) + mcmgobje(ctx, *(mcmon*)(p))->mcmosiz)
+
+#ifdef DEBUG
+# define MCMCLICTX(ctx) assert(*(((ulong *)ctx) - 1) == 0x02020202)
+# define MCMGLBCTX(ctx) assert(*(((ulong *)ctx) - 1) == 0x01010101)
+#else /* DEBUG */
+# define MCMCLICTX(ctx)
+# define MCMGLBCTX(ctx)
+#endif /* DEBUG */
+
+/* initialize a new client context */
+mcmcxdef *mcmcini(mcmcx1def *globalctx, uint pages,
+ void (*loadfn)(void *, mclhd, uchar *, ushort),
+ void *loadctx,
+ void (*revertfn)(void *, mcmon), void *revertctx)
+{
+ mcmcxdef *ret;
+ ushort siz;
+
+ siz = sizeof(mcmcxdef) + sizeof(mcmon *) * (pages - 1);
+ IF_DEBUG(siz += sizeof(ulong));
+
+ ret = (mcmcxdef *)mchalo(globalctx->mcmcxerr, siz, "mcm client context");
+ IF_DEBUG((*(ulong *)ret = 0x02020202,
+ ret = (mcmcxdef *)((uchar *)ret + sizeof(ulong))));
+
+ ret->mcmcxmsz = pages;
+ ret->mcmcxgl = globalctx;
+ ret->mcmcxldf = loadfn;
+ ret->mcmcxldc = loadctx;
+ ret->mcmcxrvf = revertfn;
+ ret->mcmcxrvc = revertctx;
+ ret->mcmcxflg = 0;
+ memset(ret->mcmcxmtb, 0, (size_t)(pages * sizeof(mcmon *)));
+ return(ret);
+}
+
+/* uninitialize a client context */
+void mcmcterm(mcmcxdef *ctx)
+{
+ /* delete the context memory */
+ mchfre(ctx);
+}
+
+/* initialize a new global context */
+mcmcx1def *mcmini(ulong max, uint pages, ulong swapsize,
+ osfildef *swapfp, char *swapfilename, errcxdef *errctx)
+{
+ mcmcx1def *ctx; /* newly-allocated cache manager context */
+ uchar *noreg chunk;/* 1st chunk of memory managed by this cache mgr */
+ mcmodef *obj; /* pointer to a cache object entry */
+ ushort siz; /* size of current thing being allocated */
+ ushort rem; /* bytes remaining in chunk */
+ int err;
+
+ NOREG((&chunk))
+
+ /* make sure 'max' is big enough - must be at least one chunk */
+ if (max < (ulong)MCMCHUNK) max = (ulong)MCMCHUNK;
+
+ /* allocate space for control structures from low-level heap */
+ rem = MCMCHUNK;
+
+ IF_DEBUG(rem += sizeof(long));
+ chunk = mchalo(errctx, rem, "mcmini");
+ IF_DEBUG((*(ulong *)chunk = 0x01010101, chunk += sizeof(ulong),
+ rem -= sizeof(ulong)));
+
+ ctx = (mcmcx1def *)chunk; /* put context at start of chunk */
+
+ /* initialize swapper; clean up if it fails */
+ ERRBEGIN(errctx)
+ mcsini(&ctx->mcmcxswc, ctx, swapsize, swapfp, swapfilename, errctx);
+ ERRCATCH(errctx, err)
+ mcsclose(&ctx->mcmcxswc);
+ mchfre(chunk);
+ errsig(errctx, err);
+ ERREND(errctx)
+
+ chunk += sizeof(mcmcx1def); /* rest of chunk is after context */
+ rem -= sizeof(mcmcx1def); /* remove from remaining size counter */
+
+ /* allocate the page table (an array of pointers to pages) */
+ ctx->mcmcxtab = (mcmodef **)chunk; /* put at bottom of chunk */
+ siz = pages * sizeof(mcmodef *); /* calcuate size of table */
+
+ memset(ctx->mcmcxtab, 0, (size_t)siz); /* clear entire table */
+ chunk += siz; /* reflect size of table */
+ rem -= siz; /* take it out of the remaining count */
+
+ /* here we begin normal heap marking with object references */
+ ctx->mcmcxhpch = (mcmhdef *)chunk; /* set start of heap chain */
+ chunk += sizeof(mcmhdef);
+ rem -= sizeof(mcmhdef);
+ ctx->mcmcxhpch->mcmhnxt = (mcmhdef *)0; /* no next heap in chain yet */
+
+ /* allocate the first page */
+ *(mcmon *)chunk = 0; /* set object number header in chunk */
+ chunk += osrndsz(sizeof(mcmon));
+ rem -= osrndsz(sizeof(mcmon));
+
+ ctx->mcmcxtab[0] = (mcmodef *)chunk; /* put at bottom of chunk */
+ memset(ctx->mcmcxtab[0], 0, (size_t)MCMPAGESIZE);
+ chunk += MCMPAGESIZE; /* reflect size of page */
+ rem -= MCMPAGESIZE; /* take it out of the remainder */
+
+ /* set up the first page with an entry for itself */
+ obj = mcmgobje(ctx, (mcmon)0); /* point to first page entry */
+ obj->mcmoflg = MCMOFPRES | MCMOFNODISC | MCMOFPAGE | MCMOFNOSWAP;
+ obj->mcmoptr = (uchar *)ctx->mcmcxtab[0];
+ obj->mcmosiz = MCMPAGESIZE;
+
+ /* set up the rest of the context */
+ ctx->mcmcxlru = ctx->mcmcxmru = MCMONINV; /* no mru/lru list yet */
+ ctx->mcmcxmax = max - (ulong)MCMCHUNK;
+ ctx->mcmcxpage = 1; /* next page slot to be allocated will be #1 */
+ ctx->mcmcxpgmx = pages; /* max number of pages we can allocate */
+ ctx->mcmcxerr = errctx;
+ ctx->mcmcxcsw = mcmcswf;
+
+ /* set up the free list with the remainder of the chunk */
+ ctx->mcmcxfre = 1; /* we've allocated object 0; obj 1 is free space */
+ obj = mcmgobje(ctx, ctx->mcmcxfre); /* point to free object entry */
+ obj->mcmonxt = obj->mcmoprv = MCMONINV; /* end of free list */
+ obj->mcmoflg = MCMOFFREE; /* mark the free block as such */
+ *(mcmon *)chunk = ctx->mcmcxfre; /* set free list header */
+
+ chunk += osrndsz(sizeof(mcmon));
+ rem -= osrndsz(sizeof(mcmon));
+ obj->mcmoptr = chunk; /* rest of chunk */
+
+ obj->mcmosiz = rem - osrndsz(sizeof(mcmon)); /* remaining size in chunk */
+
+ /* set flag for end of chunk (invalid object header) */
+ *((mcmon *)(chunk + rem - osrndsz(sizeof(mcmon)))) = MCMONINV;
+
+ /* set up the unused entry list with the remaining headers in the page */
+ mcmadpg(ctx, 0, 2);
+
+ return(ctx);
+}
+
+/*
+ * Uninitialize the cache manager. Frees the memory allocated for the
+ * cache, including the context structure itself.
+ */
+void mcmterm(mcmcx1def *ctx)
+{
+ mcmhdef *cur, *nxt;
+
+ /*
+ * Free each chunk in the cache block list, *except* the last one. The
+ * last one is special: it's actually the first chunk allocated, since
+ * we build the list in reverse order, and the first chunk pointer
+ * points into the middle of the actual allocation block, since we
+ * sub-allocated the context structure itself and the page table out of
+ * that memory.
+ */
+ for (cur = ctx->mcmcxhpch ; cur != 0 && cur->mcmhnxt != 0 ; cur = nxt)
+ {
+ /* remember the next chunk, and delete this one */
+ nxt = cur->mcmhnxt;
+ mchfre(cur);
+ }
+
+ /*
+ * As described above, the last chunk in the list is the first
+ * allocated, and it points into the middle of the actual allocated
+ * memory block. Luckily, we do have a handy pointer to the start of
+ * the memory block, namely the context pointer - it's the first thing
+ * allocated out of the block, so it's the same as the block pointer.
+ * Freeing the context frees this last/first chunk.
+ */
+ mchfre(ctx);
+}
+
+/*
+ * Allocate a new object, returning a pointer to its memory. The new
+ * object is locked upon return. The object number for the new object
+ * is returned at *nump.
+ */
+static uchar *mcmalo1(mcmcx1def *ctx, ushort siz, mcmon *nump)
+{
+ mcmon n;
+ mcmodef *o;
+ uchar *chunk;
+
+ MCMGLBCTX(ctx);
+
+ /* round size to appropriate multiple */
+ siz = osrndsz(siz);
+
+ /* if it's bigger than the chunk size, we can't allocate it */
+ if (siz > MCMCHUNK)
+ errsig(ctx->mcmcxerr, ERR_BIGOBJ);
+
+startover:
+ /* look in the free block chain for a fit to the request */
+ o = mcmffb(ctx, siz, &n);
+ if (n != MCMONINV)
+ {
+ mcmsplt(ctx, n, siz); /* split the block if necessary */
+ mcmgobje(ctx, n)->mcmoflg = MCMOFNODISC | MCMOFLOCK | MCMOFPRES;
+ mcmgobje(ctx, n)->mcmolcnt = 1; /* one locker so far */
+ *nump = n;
+ return(o->mcmoptr);
+ }
+
+ /* nothing found; we must get space out of the heap if possible */
+ chunk = mcmhalo(ctx); /* get space from heap */
+ if (!chunk) goto error; /* can't get any more space from heap */
+ o = mcmoal(ctx, &n); /* set up cache entry for free space */
+ if (n == MCMONINV)
+ {
+ mcmhdef *chunk_hdr = ((mcmhdef *)chunk) - 1;
+ ctx->mcmcxhpch = chunk_hdr->mcmhnxt;
+ mchfre(chunk_hdr);
+ goto error; /* any error means we can't allocate the memory */
+ }
+
+ *(mcmon *)chunk = n; /* set object header */
+ chunk += osrndsz(sizeof(mcmon));
+ o->mcmoptr = chunk;
+ o->mcmosiz = MCMCHUNK - osrndsz(sizeof(mcmon));
+ o->mcmoflg = MCMOFFREE;
+ mcmlnkhd(ctx, &ctx->mcmcxfre, n);
+ goto startover; /* try again, now that we have some memory */
+
+error:
+ *nump = MCMONINV;
+ return((uchar *)0);
+}
+
+static void mcmcliexp(mcmcxdef *cctx, mcmon clinum)
+{
+ /* add global number to client mapping table at client number */
+ if (cctx->mcmcxmtb[clinum >> 8] == (mcmon *)0)
+ {
+ mcmcx1def *ctx = cctx->mcmcxgl;
+ int i;
+ mcmon *p;
+
+ /* this page is not allocated - allocate it */
+ p = (mcmon *)mchalo(ctx->mcmcxerr, (256 * sizeof(mcmon)),
+ "client mapping page");
+ cctx->mcmcxmtb[clinum >> 8] = p;
+ for (i = 0 ; i < 256 ; ++i) *p++ = MCMONINV;
+ }
+}
+
+/* high-level allocate: try, collect garbage, then try again */
+uchar *mcmalo0(mcmcxdef *cctx, ushort siz, mcmon *nump,
+ mcmon clinum, int noclitrans)
+{
+ uchar *ret;
+ mcmcx1def *ctx = cctx->mcmcxgl; /* global context */
+ mcmon glb; /* global object number allocated */
+
+ MCMCLICTX(cctx);
+ MCMGLBCTX(ctx);
+
+ /* try once */
+ if ((ret = mcmalo1(ctx, siz, &glb)) != 0)
+ goto done;
+
+ /* collect some garbage */
+ mcmgarb(ctx);
+
+ /* try swapping until we get the memory or have nothing left to swap */
+ for ( ;; )
+ {
+ /* try again */
+ if ((ret = mcmalo1(ctx, siz, &glb)) != 0)
+ goto done;
+
+ /* nothing left to swap? */
+ if (!mcmswap(ctx, siz))
+ break;
+
+ /* try yet again */
+ if ((ret = mcmalo1(ctx, siz, &glb)) != 0)
+ goto done;
+
+ /* collect garbage once again */
+ mcmgarb(ctx);
+ }
+
+ /* try again */
+ if ((ret = mcmalo1(ctx, siz, &glb)) != 0)
+ goto done;
+
+ /* we have no other way of getting more memory, so signal an error */
+ errsig(ctx->mcmcxerr, ERR_NOMEM1);
+ NOTREACHEDV(uchar *);
+
+done:
+ if (noclitrans)
+ {
+ *nump = glb;
+ return(ret);
+ }
+
+ /* we have an object - generate client number */
+ if (clinum == MCMONINV)
+ {
+ /* find a free number */
+ mcmon **p;
+ uint i;
+ mcmon j = 0;
+ mcmon *q;
+ int found = FALSE;
+ int unused = -1;
+
+ for (i = 0, p = cctx->mcmcxmtb ; i < cctx->mcmcxmsz ; ++i, ++p)
+ {
+ if (*p)
+ {
+ for (j = 0, q = *p ; j < 256 ; ++j, ++q)
+ {
+ if (*q == MCMONINV)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+ }
+ else if (unused == -1)
+ unused = i; /* note an unused page mapping table */
+
+ if (found) break;
+ }
+
+ if (found)
+ clinum = (i << 8) + j;
+ else if (unused != -1)
+ clinum = (unused << 8);
+ else
+ errsig(ctx->mcmcxerr, ERR_CLIFULL);
+ }
+
+ /* expand client mapping table if necessary */
+ mcmcliexp(cctx, clinum);
+
+ /* make sure the entry isn't already in use */
+ if (mcmc2g(cctx, clinum) != MCMONINV)
+ errsig(ctx->mcmcxerr, ERR_CLIUSE);
+
+ cctx->mcmcxmtb[clinum >> 8][clinum & 255] = glb;
+ if (nump) *nump = clinum;
+ return(ret);
+}
+
+/* reserve space for an object at a client object number */
+void mcmrsrv(mcmcxdef *cctx, ushort siz, mcmon clinum, mclhd loadhd)
+{
+ mcmcx1def *ctx = cctx->mcmcxgl; /* global context */
+ mcmon glb; /* global object number allocated */
+ mcmodef *o;
+
+ MCMCLICTX(cctx);
+ MCMGLBCTX(ctx);
+
+ o = mcmoal(ctx, &glb); /* get a new object header */
+ if (!o) errsig(ctx->mcmcxerr, ERR_NOHDR); /* can't get a new header */
+
+ o->mcmoldh = loadhd;
+ o->mcmoflg = 0;
+ o->mcmosiz = siz;
+
+ mcmcliexp(cctx, clinum);
+ if (mcmc2g(cctx, clinum) != MCMONINV)
+ errsig(ctx->mcmcxerr, ERR_CLIUSE);
+
+ cctx->mcmcxmtb[clinum >> 8][clinum & 255] = glb;
+}
+
+/* resize an existing object */
+uchar *mcmrealo(mcmcxdef *cctx, mcmon cliobj, ushort newsize)
+{
+ mcmcx1def *ctx = cctx->mcmcxgl; /* global context */
+ mcmon obj = mcmc2g(cctx, cliobj);
+ mcmodef *o = mcmgobje(ctx, obj);
+ mcmon nxt;
+ mcmodef *nxto;
+ uchar *p;
+ int local_lock;
+
+ MCMCLICTX(cctx);
+ MCMGLBCTX(ctx);
+
+ newsize = osrndsz(newsize);
+
+ /* make sure the object is locked, and note if we locked it */
+ if ((local_lock = !(o->mcmoflg & MCMOFLOCK)) != 0)
+ (void)mcmlck(cctx, cliobj);
+
+ ERRBEGIN(ctx->mcmcxerr)
+
+ if (newsize < o->mcmosiz)
+ mcmsplt(ctx, obj, newsize); /* smaller; just split block */
+ else
+ {
+ /* see if there's a free block after this block */
+ p = o->mcmoptr;
+ nxt = *(mcmon *)(p + o->mcmosiz);
+ nxto = (nxt == MCMONINV) ? (mcmodef *)0 : mcmgobje(ctx, nxt);
+
+ if (nxto && ((nxto->mcmoflg & MCMOFFREE)
+ && nxto->mcmosiz >= newsize - o->mcmosiz))
+ {
+ /* sanity check - make sure heap and page table agree */
+ assert(nxto->mcmoptr == p + o->mcmosiz + osrndsz(sizeof(mcmon)));
+ /* annex the free block */
+ o->mcmosiz += nxto->mcmosiz + osrndsz(sizeof(mcmon));
+ /* move the free block to the unused list */
+ mcmunl(ctx, nxt, &ctx->mcmcxfre);
+ nxto->mcmonxt = ctx->mcmcxunu;
+ ctx->mcmcxunu = nxt;
+ nxto->mcmoflg = 0;
+
+ /* split the newly grown block if necessary */
+ mcmsplt(ctx, obj, newsize);
+ }
+ else
+ {
+ /* can't annex; allocate new memory and copy */
+
+ if (o->mcmolcnt != 1) /* if anyone else has a lock... */
+ errsig(ctx->mcmcxerr, ERR_REALCK); /* we can't move it */
+
+ p = mcmalo0(cctx, newsize, &nxt, MCMONINV, TRUE);
+ if (nxt == MCMONINV) errsig(ctx->mcmcxerr, ERR_NOMEM2);
+ memcpy(p, o->mcmoptr, (size_t)o->mcmosiz);
+
+ /* adjust the object entries */
+ nxto = mcmgobje(ctx, nxt); /* get pointer to new entry */
+ newsize = nxto->mcmosiz; /* get actual size of new block */
+ nxto->mcmoptr = o->mcmoptr; /* copy current block info to new */
+ nxto->mcmosiz = o->mcmosiz;
+ o->mcmoptr = p; /* copy new block info to original entry */
+ o->mcmosiz = newsize;
+
+ /* now fix up the heap pointers, and free the temp object */
+ *(mcmon *)(p - osrndsz(sizeof(mcmon))) = obj;
+ *(mcmon *)(nxto->mcmoptr - osrndsz(sizeof(mcmon))) = nxt;
+ mcmgunlck(ctx, nxt);
+ mcmgfre(ctx, nxt);
+ }
+ }
+
+ ERRCLEAN(ctx->mcmcxerr)
+ /* release our lock, if we had to obtain one */
+ if (local_lock) mcmunlck(cctx, cliobj);
+ ERRENDCLN(ctx->mcmcxerr)
+
+ /* return the address of the object */
+ return(o->mcmoptr);
+}
+
+/*
+ * Free an object by GLOBAL number: move object to free list.
+ */
+void mcmgfre(mcmcx1def *ctx, mcmon obj)
+{
+ mcmodef *o = mcmgobje(ctx, obj);
+
+ MCMGLBCTX(ctx);
+
+ /* signal an error if the object is locked */
+ if (o->mcmolcnt) errsig(ctx->mcmcxerr, ERR_LCKFRE);
+
+ /* take out of LRU chain if it's in the chain */
+ if (o->mcmoflg & MCMOFLRU) mcmunl(ctx, obj, &ctx->mcmcxlru);
+
+ /* put it in the free list */
+ mcmlnkhd(ctx, &ctx->mcmcxfre, obj);
+ o->mcmoflg = MCMOFFREE;
+}
+
+/*
+ * load and lock an object that has been swapped out or discarded
+ */
+uchar *mcmload(mcmcxdef *cctx, mcmon cnum)
+{
+ mcmcx1def *ctx = cctx->mcmcxgl;
+ mcmodef *o = mcmobje(cctx, cnum);
+ mcmodef *newdef;
+ mcmon newn;
+ mcmon num = mcmc2g(cctx, cnum);
+
+ MCMCLICTX(cctx);
+ MCMGLBCTX(ctx);
+
+ /* we first need to obtain some memory for this object */
+ (void)mcmalo0(cctx, o->mcmosiz, &newn, MCMONINV, TRUE);
+ newdef = mcmgobje(ctx, newn);
+
+ /* use memory block from our new object */
+ o->mcmoptr = newdef->mcmoptr;
+ o->mcmosiz = newdef->mcmosiz;
+
+ /* load or swap the object in */
+ ERRBEGIN(ctx->mcmcxerr)
+ if (o->mcmoflg & (MCMOFNODISC | MCMOFDIRTY))
+ mcsin(&ctx->mcmcxswc, o->mcmoswh, o->mcmoptr, o->mcmosiz);
+ else if (cctx->mcmcxldf)
+ (*cctx->mcmcxldf)(cctx->mcmcxldc, o->mcmoldh, o->mcmoptr,
+ o->mcmosiz);
+ else
+ errsig(ctx->mcmcxerr, ERR_NOLOAD);
+ ERRCLEAN(ctx->mcmcxerr)
+ mcmgunlck(ctx, newn); /* unlock the object */
+ mcmgfre(ctx, newn); /* don't need new memory after all */
+ ERRENDCLN(ctx->mcmcxerr)
+
+ /* unuse the new cache entry we obtained (we just wanted the memory) */
+/* @@@ */
+ *(mcmon *)(o->mcmoptr - osrndsz(sizeof(mcmon))) = num; /* set obj# */
+ newdef->mcmoflg = 0; /* mark new block as unused */
+ newdef->mcmonxt = ctx->mcmcxunu; /* link to unused chain */
+ ctx->mcmcxunu = newn;
+
+ /* set flags in the newly loaded object and return */
+ o->mcmoflg |= MCMOFPRES | MCMOFLOCK; /* object is now present in memory */
+ o->mcmoflg &= ~MCMOFDIRTY; /* not written since last swapped in */
+ o->mcmoflg |= MCMOFNODISC; /* don't discard once it's been to swap file */
+ o->mcmolcnt = 1; /* one locker so far */
+
+ /* if the object is to be reverted upon loading, revert it now */
+ if (o->mcmoflg & MCMOFREVRT)
+ {
+ (*cctx->mcmcxrvf)(cctx->mcmcxrvc, cnum);
+ o->mcmoflg &= ~MCMOFREVRT;
+ }
+
+ return(o->mcmoptr);
+}
+
+/*
+ * Allocate a new object header. This doesn't allocate an object, just
+ * the header for one.
+ */
+static mcmodef *mcmoal(mcmcx1def *ctx, mcmon *nump)
+{
+ mcmodef *ret;
+ uint pagenum;
+
+ MCMGLBCTX(ctx);
+
+ /* look first in list of unused headers */
+startover:
+ if (ctx->mcmcxunu != MCMONINV)
+ {
+ /* we have something in the unused list; return it */
+ *nump = ctx->mcmcxunu;
+ ret = mcmgobje(ctx, *nump);
+ ctx->mcmcxunu = ret->mcmonxt;
+ ret->mcmoswh = MCSSEGINV;
+ return(ret);
+ }
+
+ /*
+ * No unused entries: we must create a new page. To do so, we
+ * simply allocate memory for a new page. Allocate the memory
+ * ourselves, to avoid deadlocking with the allocator (which can
+ * try to get a new entry to satisfy our request for memory).
+ */
+ if (ctx->mcmcxpage == ctx->mcmcxpgmx) goto error; /* no more pages */
+ pagenum = ctx->mcmcxpage++; /* get a new page slot */
+
+ ctx->mcmcxtab[pagenum] =
+ (mcmodef *)mchalo(ctx->mcmcxerr, MCMPAGESIZE, "mcmoal");
+ mcmadpg(ctx, pagenum, MCMONINV);
+ goto startover;
+
+error:
+ *nump = MCMONINV;
+ return((mcmodef *)0);
+}
+
+/* find free block: find a block from the free pool to satisfy allocation */
+static mcmodef *mcmffb(mcmcx1def *ctx, ushort siz, mcmon *nump)
+{
+ mcmon n;
+ mcmodef *o;
+ mcmon minn;
+ mcmodef *mino;
+ ushort min = 0;
+
+ MCMGLBCTX(ctx);
+
+ for (minn = MCMONINV, mino = 0, n = ctx->mcmcxfre ; n != MCMONINV ;
+ n = o->mcmonxt)
+ {
+ o = mcmgobje(ctx, n);
+ if (o->mcmosiz == siz)
+ {
+ /* found exact match - use it immediately */
+ minn = n;
+ min = siz;
+ mino = o;
+ break;
+ }
+ else if (o->mcmosiz > siz)
+ {
+ /* found something at least as big; is it smallest yet? */
+ if (minn == MCMONINV || o->mcmosiz < min)
+ {
+ /* yes, best fit so far, use it; but keep looking */
+ minn = n;
+ mino = o;
+ min = o->mcmosiz;
+ }
+ }
+ }
+
+ /* if we found something, remove from the free list */
+ if (minn != MCMONINV)
+ {
+ mcmunl(ctx, minn, &ctx->mcmcxfre);
+ mino->mcmoflg &= ~MCMOFFREE;
+ mino->mcmoswh = MCSSEGINV;
+ }
+
+ *nump = minn;
+ return mino;
+}
+
+/*
+ * unlink an object header from one of the doubly-linked lists
+ */
+static void mcmunl(mcmcx1def *ctx, mcmon n, mcmon *lst)
+{
+ mcmodef *o = mcmgobje(ctx, n);
+ mcmodef *nxt;
+ mcmodef *prv;
+
+ MCMGLBCTX(ctx);
+
+ /* see if this is LRU chain - must deal with MRU pointer if so */
+ if (lst == &ctx->mcmcxlru)
+ {
+ /* if it's at MRU, set MRU pointer to previous object in list */
+ if (ctx->mcmcxmru == n)
+ {
+ ctx->mcmcxmru = o->mcmoprv; /* set MRU to previous in chain */
+ if (ctx->mcmcxmru != MCMONINV) /* set nxt for new MRU */
+ mcmgobje(ctx, ctx->mcmcxmru)->mcmonxt = MCMONINV;
+ else
+ ctx->mcmcxlru = MCMONINV; /* nothing in list; clear LRU */
+ }
+ o->mcmoflg &= ~MCMOFLRU;
+ }
+
+ nxt = o->mcmonxt == MCMONINV ? (mcmodef *)0 : mcmgobje(ctx, o->mcmonxt);
+ prv = o->mcmoprv == MCMONINV ? (mcmodef *)0 : mcmgobje(ctx, o->mcmoprv);
+
+ /* set back link for next object, if there is a next object */
+ if (nxt) nxt->mcmoprv = o->mcmoprv;
+
+ /* set forward link for previous object, or head if no previous object */
+ if (prv) prv->mcmonxt = o->mcmonxt;
+ else *lst = o->mcmonxt;
+
+ o->mcmonxt = o->mcmoprv = MCMONINV;
+}
+
+/* link an item to the head of a doubly-linked list */
+static void mcmlnkhd(mcmcx1def *ctx, mcmon *lst, mcmon n)
+{
+ MCMGLBCTX(ctx);
+
+ if (*lst != MCMONINV) mcmgobje(ctx, *lst)->mcmoprv = n;
+ mcmgobje(ctx, n)->mcmonxt = *lst; /* next is previous head of list */
+ *lst = n; /* make object new head of list */
+ mcmgobje(ctx, n)->mcmoprv = MCMONINV; /* there is no previous entry */
+}
+
+/* add page pagenum, initializing entries after firstunu to unused */
+static void mcmadpg(mcmcx1def *ctx, uint pagenum, mcmon firstunu)
+{
+ mcmon unu;
+ mcmodef *obj;
+ mcmon lastunu;
+
+ MCMGLBCTX(ctx);
+
+ unu = (firstunu == MCMONINV ? pagenum * MCMPAGECNT : firstunu);
+ ctx->mcmcxunu = unu;
+ lastunu = (pagenum * MCMPAGECNT) + MCMPAGECNT - 1;
+ for (obj = mcmgobje(ctx, unu) ; unu < lastunu ; ++obj)
+ obj->mcmonxt = ++unu;
+ obj->mcmonxt = MCMONINV;
+}
+
+/*
+ * split a previously-free block into two chunks, adding the remainder
+ * back into the free list, if there's enough left over
+ */
+static void mcmsplt(mcmcx1def *ctx, mcmon n, ushort siz)
+{
+ mcmodef *o = mcmgobje(ctx, n);
+ mcmon newn;
+ mcmodef *newp;
+
+ MCMGLBCTX(ctx);
+
+ if (o->mcmosiz < siz + MCMSPLIT) return; /* don't split; we're done */
+
+ newp = mcmoal(ctx, &newn);
+ if (newn == MCMONINV) return; /* ignore error - just skip split */
+
+ /* set up the new entry, and link into free list */
+ *(mcmon *)(o->mcmoptr + siz) = newn;
+ newp->mcmoptr = o->mcmoptr + siz + osrndsz(sizeof(mcmon));
+ newp->mcmosiz = o->mcmosiz - siz - osrndsz(sizeof(mcmon));
+ newp->mcmoflg = MCMOFFREE;
+ mcmlnkhd(ctx, &ctx->mcmcxfre, newn);
+
+ o->mcmosiz = siz; /* size of new object is now exactly as request */
+}
+
+/* allocate a new chunk from the heap if possible */
+static uchar *mcmhalo(mcmcx1def *ctx)
+{
+ uchar *chunk;
+ int err;
+# define size (MCMCHUNK + sizeof(mcmhdef) + 2*osrndsz(sizeof(mcmon)))
+
+ VARUSED(err);
+
+ MCMGLBCTX(ctx);
+
+ if (ctx->mcmcxmax < MCMCHUNK) return((uchar *)0);
+
+ ERRBEGIN(ctx->mcmcxerr)
+ chunk = mchalo(ctx->mcmcxerr, size, "mcmhalo");
+ ERRCATCH(ctx->mcmcxerr, err)
+ ctx->mcmcxmax = 0; /* remember we can't allocate anything more */
+ return((uchar *)0); /* return no memory */
+ ERREND(ctx->mcmcxerr)
+
+ ctx->mcmcxmax -= MCMCHUNK;
+
+ /* link into heap chain */
+ ((mcmhdef *)chunk)->mcmhnxt = ctx->mcmcxhpch;
+ ctx->mcmcxhpch = (mcmhdef *)chunk;
+/*@@@@*/
+ *(mcmon *)(chunk + osrndsz(sizeof(mcmhdef) + MCMCHUNK)) = MCMONINV;
+ return(chunk + sizeof(mcmhdef));
+
+# undef size
+}
+
+/* "use" an object - move to most-recent position in LRU chain */
+void mcmuse(mcmcx1def *ctx, mcmon obj)
+{
+ mcmodef *o = mcmgobje(ctx, obj);
+
+ MCMGLBCTX(ctx);
+
+ if (ctx->mcmcxmru == obj) return; /* already MRU; nothing to do */
+
+ /* remove from LRU chain if it's in it */
+ if (o->mcmoflg & MCMOFLRU) mcmunl(ctx, obj, &ctx->mcmcxlru);
+
+ /* set forward pointer of last block, if there is one */
+ if (ctx->mcmcxmru != MCMONINV)
+ mcmgobje(ctx, ctx->mcmcxmru)->mcmonxt = obj;
+
+ o->mcmoprv = ctx->mcmcxmru; /* point back to previous MRU */
+ o->mcmonxt = MCMONINV; /* nothing in list after this one */
+ ctx->mcmcxmru = obj; /* point MRU to new block */
+
+ /* if there's nothing in the chain at all, set LRU to this block, too */
+ if (ctx->mcmcxlru == MCMONINV) ctx->mcmcxlru = obj;
+
+ /* note that object is in LRU chain */
+ o->mcmoflg |= MCMOFLRU;
+}
+
+/* find next free block in a heap, starting with pointer */
+static uchar *mcmffh(mcmcx1def *ctx, uchar *p)
+{
+ mcmodef *o;
+
+ MCMGLBCTX(ctx);
+
+ while (*(mcmon *)p != MCMONINV)
+ {
+ o = mcmgobje(ctx, *(mcmon *)p);
+ assert(o->mcmoptr == p + osrndsz(sizeof(mcmon)));
+ if (o->mcmoflg & MCMOFFREE) return(p);
+ p += osrndsz(sizeof(mcmon)) + o->mcmosiz; /* move on to next chunk */
+ }
+ return((uchar *)0); /* no more free blocks in heap */
+}
+
+#ifdef NEVER
+static void mcmmove(mcmcx1def *ctx, mcmodef *o, uchar *newpage)
+{
+ mcmodef **page;
+
+ MCMGLBCTX(ctx);
+
+ /* see if we need to update page table (we do if moving a page) */
+ if (o->mcmoflg & MCMOFPAGE)
+ {
+ for (page = ctx->mcmcxtab ; *page ; ++page)
+ {
+ if (*page == (mcmodef *)(o->mcmoptr))
+ {
+ *page = (mcmodef *)newpag;
+ break;
+ }
+ }
+ if (!*page) printf("\n*** internal error - relocating page\n");
+ }
+ o->mcmoptr = newpage;
+}
+#endif /* NEVER */
+
+/* relocate blocks from p to (but not including) q */
+static uchar *mcmreloc(mcmcx1def *ctx, uchar *p, uchar *q)
+{
+ mcmodef *o;
+ ushort dist;
+ mcmon objnum;
+
+ MCMGLBCTX(ctx);
+
+ objnum = *(mcmon *)p; /* get number of free block being bubbled up */
+ o = mcmgobje(ctx, objnum); /* get pointer to free object */
+ assert(o->mcmoptr == p + osrndsz(sizeof(mcmon)));
+ dist = osrndsz(sizeof(mcmon)) + o->mcmosiz; /* compute distance to move */
+ mcmmove(ctx, o, q - dist + osrndsz(sizeof(mcmon))); /* move obj to top */
+
+ memmove(p, p+dist, (size_t)(q - p - o->mcmosiz)); /* move memory */
+
+ /* update cache entries for the blocks we moved */
+ while (p != q - dist)
+ {
+ mcmmove(ctx, mcmgobje(ctx, *(mcmon *)p), p + osrndsz(sizeof(mcmon)));
+ p = mcmnxh(ctx, p);
+ }
+
+ *(mcmon *)(q - dist) = objnum; /* set bubbled num */
+ return(q - dist); /* return new location of bubbled block */
+}
+
+/* consolidate the two (free) blocks starting at p into one block */
+static void mcmconsol(mcmcx1def *ctx, uchar *p)
+{
+ uchar *q;
+ mcmodef *obj1, *obj2;
+
+ MCMGLBCTX(ctx);
+
+ q = mcmnxh(ctx, p);
+ obj1 = mcmgobje(ctx, *(mcmon *)p);
+ obj2 = mcmgobje(ctx, *(mcmon *)q);
+
+ assert(obj1->mcmoptr == p + osrndsz(sizeof(mcmon)));
+ assert(obj2->mcmoptr == q + osrndsz(sizeof(mcmon)));
+
+ obj1->mcmosiz += osrndsz(sizeof(mcmon)) + obj2->mcmosiz;
+ mcmunl(ctx, *(mcmon *)q, &ctx->mcmcxfre);
+
+ /* add second object entry to unused list */
+ obj2->mcmonxt = ctx->mcmcxunu;
+ ctx->mcmcxunu = *(mcmon *)q;
+ obj2->mcmoflg = 0;
+}
+
+/* attempt to compact all heaps by consolidating free space */
+static void mcmgarb(mcmcx1def *ctx)
+{
+ mcmhdef *h;
+ uchar *p;
+ uchar *q;
+ uchar *nxt;
+ ushort flags;
+
+ MCMGLBCTX(ctx);
+
+ for (h = ctx->mcmcxhpch ; h ; h = h->mcmhnxt)
+ {
+ p = (uchar *)(h+1); /* get pointer to actual heap */
+ p = mcmffh(ctx, p); /* get first free block in heap */
+ if (!p) continue; /* can't do anything - no free blocks */
+ nxt = mcmnxh(ctx, p); /* remember immediate next block */
+
+ for (q=p ;; )
+ {
+ q = mcmnxh(ctx, q); /* find next chunk in heap */
+ if (*(mcmon *)q == MCMONINV) break; /* reached end of heap */
+ assert(mcmgobje(ctx, *(mcmon *)q)->mcmoptr
+ == q + osrndsz(sizeof(mcmon)));
+ flags = mcmgobje(ctx, *(mcmon *)q)->mcmoflg; /* get flags */
+
+ /* if the block is locked, p can't be relocated */
+ if (flags & MCMOFLOCK)
+ {
+ p = mcmffh(ctx, q); /* find next free block after p */
+ q = p;
+ if (p) continue; /* try again; start with next free block */
+ else break; /* no more free blocks - done with heap */
+ }
+
+ /* if the block is free, we can relocate between p and q */
+ if (flags & MCMOFFREE)
+ {
+ if (q != nxt) p = mcmreloc(ctx, p, q); /* relocate */
+ mcmconsol(ctx, p); /* consolidate two free blocks */
+
+ /* resume looking, starting with consolidated block */
+ nxt = mcmnxh(ctx, p);
+ q = p;
+ continue;
+ }
+ }
+ }
+}
+
+/* toss out a particular object */
+static int mcmtoss(mcmcx1def *ctx, mcmon n)
+{
+ mcmodef *o = mcmgobje(ctx, n);
+ mcmodef *newp;
+ mcmon newn;
+
+ MCMGLBCTX(ctx);
+
+ /* make a new block for the free space */
+ newp = mcmoal(ctx, &newn);
+ if (newn == MCMONINV)
+ return(FALSE); /* ignore the error, but can't toss it out */
+
+ /* write object to swap file if not discardable */
+ if (o->mcmoflg & (MCMOFNODISC | MCMOFDIRTY))
+ {
+ mcsseg old_swap_seg;
+
+ /*
+ * If this object was last loaded out of the load file, rather
+ * than the swap file, don't attempt to find it in the swap file
+ * -- so note by setting the old swap segment parameter to null.
+ */
+ if (!(o->mcmoflg & MCMOFNODISC))
+ old_swap_seg = o->mcmoswh;
+ else
+ old_swap_seg = MCSSEGINV;
+
+ o->mcmoswh = mcsout(&ctx->mcmcxswc, (uint)n, o->mcmoptr, o->mcmosiz,
+ old_swap_seg, o->mcmoflg & MCMOFDIRTY);
+ }
+
+ /* give the object's space to the newly created block */
+ newp->mcmoptr = o->mcmoptr;
+ newp->mcmosiz = o->mcmosiz;
+ newp->mcmoflg = MCMOFFREE;
+/*@@@*/
+ *(mcmon *)(o->mcmoptr - osrndsz(sizeof(mcmon))) = newn;
+ mcmlnkhd(ctx, &ctx->mcmcxfre, newn);
+
+ o->mcmoflg &= ~MCMOFPRES; /* object is no longer in memory */
+ mcmunl(ctx, n, &ctx->mcmcxlru); /* remove from LRU list */
+ return(TRUE); /* successful, so return TRUE */
+}
+
+/* swap or discard to make room for siz; return 0 if nothing swapped */
+static int mcmswap(mcmcx1def *ctx, ushort siz)
+{
+ mcmon n;
+ mcmodef *o;
+ mcmon nxt;
+ int pass; /* pass 1: swap one piece big enough */
+ /* pass 2: swap enough pieces to add up to right size */
+ ushort tot;
+
+ MCMGLBCTX(ctx);
+
+ for (pass = 1, tot = 0 ; pass < 3 && tot < siz ; ++pass)
+ {
+ for (n = ctx->mcmcxlru ; n != MCMONINV && tot < siz ; n = nxt)
+ {
+ o = mcmgobje(ctx, n);
+ nxt = o->mcmonxt; /* get next now, as we may unlink */
+ if (!(o->mcmoflg & (MCMOFLOCK | MCMOFNOSWAP | MCMOFPAGE))
+ && (pass == 2 || o->mcmosiz >= siz))
+ {
+ /* toss out, and add into size if successful */
+ if (mcmtoss(ctx, n)) tot += o->mcmosiz;
+ }
+ }
+ }
+
+ /* if we managed to remove anything, return TRUE, otherwise FALSE */
+ return(tot != 0);
+}
+
+/* compute size of cache */
+ulong mcmcsiz(mcmcxdef *cctx)
+{
+ mcmcx1def *ctx = cctx->mcmcxgl;
+ mcmhdef *p;
+ ulong tot;
+
+ MCMCLICTX(cctx);
+ MCMGLBCTX(ctx);
+
+ /* count number of heaps, adding in chunk size for each */
+ for (tot = 0, p = ctx->mcmcxhpch ; p ; p = p->mcmhnxt)
+ tot += MCMCHUNK;
+
+ return(tot);
+}
+
+#ifdef MCM_NO_MACRO
+/* routines that can be either macros or functions */
+
+uchar *mcmlck(mcmcxdef *ctx, mcmon objnum)
+{
+ mcmodef *o = mcmobje(ctx, objnum);
+
+ if ((o->mcmoflg & MCMOFFREE) != 0 || mcmc2g(ctx, objnum) == MCMONINV)
+ {
+ errsig(ctx->mcmcxgl->mcmcxerr, ERR_INVOBJ);
+ return 0;
+ }
+ else if (o->mcmoflg & MCMOFPRES)
+ {
+ o->mcmoflg |= MCMOFLOCK;
+ ++(o->mcmolcnt);
+ return(o->mcmoptr);
+ }
+ else
+ return(mcmload(ctx, objnum));
+}
+
+void mcmunlck(mcmcxdef *ctx, mcmon obj)
+{
+ mcmodef *o = mcmobje(ctx, obj);
+
+ if (o->mcmoflg & MCMOFLOCK)
+ {
+ if (!(--(o->mcmolcnt)))
+ {
+ o->mcmoflg &= ~MCMOFLOCK;
+ mcmuse(ctx->mcmcxgl, mcmc2g(ctx, obj));
+ }
+ }
+}
+
+void mcmgunlck(mcmcx1def *ctx, mcmon obj)
+{
+ mcmodef *o = mcmgobje(ctx, obj);
+
+ if (o->mcmoflg & MCMOFLOCK)
+ {
+ if (!(--(o->mcmolcnt)))
+ {
+ o->mcmoflg &= ~MCMOFLOCK;
+ mcmuse(ctx, obj);
+ }
+ }
+}
+
+#endif /* MCM_NO_MACRO */
+
+/*
+ * Change an object's swap file handle. This routine will only be
+ * called for an object that is either present or swapped out (i.e., an
+ * object with a valid mcsseg number in its swap state).
+ */
+void mcmcswf(mcmcx1def *ctx, mcmon objn, mcsseg swapn, mcsseg oldswapn)
+{
+ mcmodef *o = mcmgobje(ctx, objn);
+
+ MCMGLBCTX(ctx);
+
+ /*
+ * Reset the swap number only if the object is swapped out and its
+ * swap file number matches the old one, or the object is currently
+ * present (in which case the swap file number is irrelevant and can
+ * be replaced).
+ */
+ if (((o->mcmoflg & (MCMOFDIRTY | MCMOFNODISC)) && o->mcmoswh == oldswapn)
+ || (o->mcmoflg & MCMOFPRES))
+ o->mcmoswh = swapn;
+}
+
+
+void mcmfre(mcmcxdef *ctx, mcmon obj)
+{
+ /* free the actual object */
+ mcmgfre(ctx->mcmcxgl, mcmc2g(ctx, obj));
+
+ /* unmap the client object number */
+ mcmc2g(ctx, obj) = MCMONINV;
+}
+
} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/memory_cache_loader.cpp b/engines/glk/tads/tads2/memory_cache_loader.cpp
deleted file mode 100644
index f403299..0000000
--- a/engines/glk/tads/tads2/memory_cache_loader.cpp
+++ /dev/null
@@ -1,31 +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/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_swap.cpp b/engines/glk/tads/tads2/memory_cache_swap.cpp
index 114a725..8d78892 100644
--- a/engines/glk/tads/tads2/memory_cache_swap.cpp
+++ b/engines/glk/tads/tads2/memory_cache_swap.cpp
@@ -21,11 +21,311 @@
*/
#include "glk/tads/tads2/memory_cache_swap.h"
+#include "glk/tads/tads2/memory_cache.h"
+#include "glk/tads/tads2/memory_cache_heap.h"
+#include "glk/tads/tads2/error.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
+
+/* initialize swapper: allocate memory for swap page table */
+void mcsini(mcscxdef *ctx, mcmcx1def *gmemctx, ulong maxsiz,
+ osfildef *fp, char *swapfilename, errcxdef *errctx)
+{
+ uchar *p;
+
+ ctx->mcscxtab = (mcsdsdef **)0; /* anticipate failure */
+
+ /* allocate space from the low-level heap for page table and one page */
+ p = mchalo(errctx, ((MCSPAGETAB * sizeof(mcsdsdef *))
+ + (MCSPAGECNT * sizeof(mcsdsdef))), "mcsini");
+
+ /* set up the context with pointers to this chunk */
+ ctx->mcscxtab = (mcsdsdef **)p;
+ memset(p, 0, (size_t)(MCSPAGETAB * sizeof(mcsdsdef *)));
+ p += MCSPAGETAB * sizeof(mcsdsdef *);
+ ctx->mcscxtab[0] = (mcsdsdef *)p;
+
+ /* set up the rest of the context */
+ ctx->mcscxtop = (ulong)0;
+ ctx->mcscxmax = maxsiz;
+ ctx->mcscxmsg = 0;
+ ctx->mcscxfp = fp;
+ ctx->mcscxerr = errctx;
+ ctx->mcscxmem = gmemctx;
+
+ /*
+ * store the swap filename - make a copy so that the caller doesn't
+ * have to retain the original copy (in case it's on the stack)
+ */
+ if (swapfilename != 0)
+ {
+ ctx->mcscxfname = (char *)mchalo(errctx,
+ (strlen(swapfilename)+1),
+ "mcsini");
+ strcpy(ctx->mcscxfname, swapfilename);
+ }
+ else
+ ctx->mcscxfname = 0;
+}
+
+/* close the swapper */
+void mcsclose(mcscxdef *ctx)
+{
+ if (ctx->mcscxtab) mchfre(ctx->mcscxtab);
+}
+
+/*
+ * Attempt to compact the swap file when it grows too big. The segment
+ * descriptors are always allocated in increasing seek location within
+ * the swap file. To compress the file, make each descriptor's
+ * allocated size equal its used size for each in-use segment, and leave
+ * free segments at their allocated sizes.
+ */
+static void mcscompact(mcscxdef *ctx)
+{
+ char buf[512];
+ ulong max;
+ mcsseg cur_in;
+ mcsseg cur_out;
+ mcsdsdef *desc_in;
+ mcsdsdef *desc_out;
+ uint siz;
+ uint rdsiz;
+ ulong ptr_in;
+ ulong ptr_out;
+
+ max = 0; /* start at offset zero within file */
+ for (cur_in = cur_out = 0 ; cur_in < ctx->mcscxmsg ; ++cur_in)
+ {
+ desc_in = mcsdsc(ctx, cur_in);
+
+ /*
+ * If the present descriptor's address is wrong, and the swap
+ * segment is in use, move the swap segment. If it's not in
+ * use, we don't need to move it, because we're going to throw
+ * away the segment entirely.
+ */
+ if (desc_in->mcsdsptr != max
+ && (desc_in->mcsdsflg & MCSDSFINUSE))
+ {
+ /* ptr_in is the old location, ptr_out is the new location */
+ ptr_in = desc_in->mcsdsptr;
+ ptr_out = max;
+
+ /* copy through our buffer */
+ for (siz = desc_in->mcsdsosz ; siz ; siz -= rdsiz)
+ {
+ /* size is whole buffer, or last piece if smaller */
+ rdsiz = (siz > sizeof(buf) ? sizeof(buf) : siz);
+
+ /* seek to old location and get the piece */
+ osfseek(ctx->mcscxfp, ptr_in, OSFSK_SET);
+ (void)osfrb(ctx->mcscxfp, buf, (size_t)rdsiz);
+
+ /* seek to new location and write the piece */
+ osfseek(ctx->mcscxfp, ptr_out, OSFSK_SET);
+ (void)osfwb(ctx->mcscxfp, buf, (size_t)rdsiz);
+
+ /* adjust the pointers by the size copied */
+ ptr_in += rdsiz;
+ ptr_out += rdsiz;
+ }
+ }
+
+ /* adjust object descriptor to reflect new location */
+ desc_in->mcsdsptr = max;
+
+ /*
+ * Make current object's size exact if it's in use. If it's
+ * not in use, delete the segment altogether.
+ */
+ if (desc_in->mcsdsflg & MCSDSFINUSE)
+ {
+ desc_in->mcsdssiz = desc_in->mcsdsosz;
+ max += desc_in->mcsdssiz;
+
+ /* copy descriptor to correct position to close any holes */
+ if (cur_out != cur_in)
+ {
+ desc_out = mcsdsc(ctx, cur_out);
+ OSCPYSTRUCT(*desc_out, *desc_in);
+
+ /* we need to renumber the corresponding object as well */
+ mcmcsw(ctx->mcscxmem, (mcmon)desc_in->mcsdsobj,
+ cur_out, cur_in);
+ }
+
+ /* we actually wrote this one, so move output pointer */
+ ++cur_out;
+ }
+ else
+ {
+ /*
+ * We need to renumber the corresponding object so that it
+ * knows there is no swap segment for it any more.
+ */
+ mcmcsw(ctx->mcscxmem, (mcmon)desc_in->mcsdsobj,
+ MCSSEGINV, cur_in);
+ }
+ }
+
+ /*
+ * Adjust the top of the file for our new size, and add the savings
+ * into the available space counter. Also, adjust the total handle
+ * count to reflect any descriptors that we've deleted.
+ */
+ ctx->mcscxmax += (ctx->mcscxtop - max);
+ ctx->mcscxtop = max;
+ ctx->mcscxmsg = cur_out;
+}
+
+/* swap an object out to the swap file */
+mcsseg mcsout(mcscxdef *ctx, uint objid, uchar *ptr, ushort siz,
+ mcsseg oldseg, int dirty)
+{
+ mcsdsdef *desc;
+ mcsdsdef **pagep;
+ uint i;
+ uint j;
+ mcsseg min;
+ mcsseg cur;
+ ushort minsiz = 0;
+
+ IF_DEBUG(printf("<< mcsout: objid=%d, ptr=%lx, siz=%u, oldseg=%u >>\n",
+ objid, (unsigned long)ptr, siz, oldseg));
+
+ /* see if old segment can be reused */
+ if (oldseg != MCSSEGINV)
+ {
+ desc = mcsdsc(ctx, oldseg);
+ if (!(desc->mcsdsflg & MCSDSFINUSE) /* if old seg is not in use */
+ && desc->mcsdsobj == objid /* and it has same object */
+ && desc->mcsdssiz >= siz /* and it's still big enough */
+ && !dirty) /* and the object in memory hasn't been changed */
+ {
+ /* we can reuse the old segment without rewriting it */
+ desc->mcsdsflg |= MCSDSFINUSE; /* mark segment as in use */
+ return(oldseg);
+ }
+ }
+
+ /* look for the smallest unused segment big enough for this object */
+ for (cur = 0, min = MCSSEGINV, i = 0, pagep = ctx->mcscxtab
+ ; cur < ctx->mcscxmsg && i < MCSPAGETAB && *pagep ; ++pagep, ++i)
+ {
+ for (j = 0, desc = *pagep ; cur < ctx->mcscxmsg && j < MCSPAGECNT
+ ; ++desc, ++j, ++cur)
+ {
+ if (!(desc->mcsdsflg & MCSDSFINUSE)
+ && desc->mcsdssiz >= siz
+ && (min == MCSSEGINV || desc->mcsdssiz < minsiz))
+ {
+ min = cur;
+ minsiz = desc->mcsdssiz;
+ if (minsiz == siz) break; /* exact match - we're done */
+ }
+ }
+ /* quit if we found an exact match */
+ if (min != MCSSEGINV && minsiz == siz) break;
+ }
+
+ /* if we found nothing, allocate a new segment if possible */
+ if (min == MCSSEGINV)
+ {
+ if (siz > ctx->mcscxmax)
+ {
+ /* swap file is too big; compact it and try again */
+ mcscompact(ctx);
+ if (siz > ctx->mcscxmax)
+ errsig(ctx->mcscxerr, ERR_SWAPBIG);
+ }
+
+ min = ctx->mcscxmsg;
+ if ((min >> 8) >= MCSPAGETAB) /* exceeded pages in page table? */
+ errsig(ctx->mcscxerr, ERR_SWAPPG);
+
+ if (!ctx->mcscxtab[min >> 8]) /* haven't allocate page yet? */
+ {
+ ctx->mcscxtab[min >> 8] =
+ (mcsdsdef *)mchalo(ctx->mcscxerr,
+ (MCSPAGECNT * sizeof(mcsdsdef)),
+ "mcsout");
+ }
+
+ /* set up new descriptor */
+ desc = mcsdsc(ctx, min);
+ desc->mcsdsptr = ctx->mcscxtop;
+ desc->mcsdssiz = siz;
+ desc->mcsdsobj = objid;
+
+ /* write out the segment */
+ mcswrt(ctx, desc, ptr, siz);
+ desc->mcsdsflg = MCSDSFINUSE;
+
+ /* update context information to account for new segment */
+ ctx->mcscxtop += siz; /* add to top seek offset in file */
+ ctx->mcscxmax -= siz; /* take size out of quota */
+ ctx->mcscxmsg++; /* increment last segment allocated */
+
+ return(min);
+ }
+ else
+ {
+ desc = mcsdsc(ctx, min);
+ desc->mcsdsobj = objid;
+ mcswrt(ctx, desc, ptr, siz);
+ desc->mcsdsflg |= MCSDSFINUSE;
+
+ return(min);
+ }
+}
+
+void mcsin(mcscxdef *ctx, mcsseg seg, uchar *ptr, ushort siz)
+{
+ mcsdsdef *desc = mcsdsc(ctx, seg);
+
+ IF_DEBUG(printf("<< mcsin: seg=%u, ptr=%lx, siz=%d, objid=%u >>\n",
+ seg, (unsigned long)ptr, siz, desc->mcsdsobj));
+
+ assert(seg < ctx->mcscxmsg);
+
+ /* can only swap in as much as we wrote */
+ if (desc->mcsdsosz < siz) siz = desc->mcsdsosz;
+
+ /* seek to and read the segment */
+ if (osfseek(ctx->mcscxfp, desc->mcsdsptr, OSFSK_SET))
+ errsig(ctx->mcscxerr, ERR_FSEEK);
+ if (osfrb(ctx->mcscxfp, ptr, (size_t)siz))
+ errsig(ctx->mcscxerr, ERR_FREAD);
+
+ desc->mcsdsflg &= ~MCSDSFINUSE; /* segment no longer in use */
+}
+
+void mcswrt(mcscxdef *ctx, mcsdsdef *desc, uchar *buf, ushort bufl)
+{
+ int tries;
+
+ desc->mcsdsosz = bufl;
+
+ for (tries = 0 ; tries < 2 ; ++tries)
+ {
+ /* attempt to write the object to the swap file */
+ if (osfseek(ctx->mcscxfp, desc->mcsdsptr, OSFSK_SET))
+ errsig(ctx->mcscxerr, ERR_FSEEK);
+ if (!osfwb(ctx->mcscxfp, buf, (size_t)bufl))
+ return;
+
+ /* couldn't write it; compact the swap file */
+ mcscompact(ctx);
+ }
+
+ /* couldn't write to swap file, even after compacting it */
+ errsig(ctx->mcscxerr, ERR_FWRITE);
+}
+
} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/object.cpp b/engines/glk/tads/tads2/object.cpp
index 2ba1161..4360f5f 100644
--- a/engines/glk/tads/tads2/object.cpp
+++ b/engines/glk/tads/tads2/object.cpp
@@ -21,15 +21,1056 @@
*/
#include "glk/tads/tads2/object.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/memory_cache_heap.h"
+#include "glk/tads/os_glk.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
+/*
+ * Get a property WITHOUT INHERITANCE. The offset of the property's
+ * prpdef is returned. An offset of zero means the property wasn't
+ * found.
+ */
+uint objgetp(mcmcxdef *mctx, objnum objn, prpnum prop, dattyp *typptr)
+{
+ objdef *objptr;
+ prpdef *p;
+ int cnt;
+ uint retval; /* property offset, if we find it */
+ uint ignprop; /* ignored property - use if real property isn't found */
+ uchar pbuf[2]; /* property number in portable format */
+ uchar *indp;
+ uchar *indbase;
+ int last;
+ int first;
+ int cur;
+
+ oswp2(pbuf, prop); /* get property number in portable foramt */
+ objptr = (objdef *)mcmlck(mctx, objn); /* get a lock on the object */
+ ignprop = 0; /* assume we won't find ignored property */
+ cnt = objnprop(objptr); /* get number of properties defined */
+ retval = 0; /* presume failure */
+
+ if (objflg(objptr) & OBJFINDEX)
+ {
+ /* there's an index -> do a binary search through the index */
+ indbase = (uchar *)objpfre(objptr); /* find index */
+ first = 0;
+ last = cnt - 1;
+ for (;;)
+ {
+ if (first > last) break; /* crossed over -> not found */
+ cur = first + (last - first)/2; /* split the difference */
+ indp = indbase + cur*4; /* get pointer to this entry */
+ if (indp[0] == pbuf[0] && indp[1] == pbuf[1])
+ {
+ retval = osrp2(indp + 2);
+ break;
+ }
+ else if (indp[0] < pbuf[0]
+ || (indp[0] == pbuf[0] && indp[1] < pbuf[1]))
+ first = (cur == first ? first + 1 : cur);
+ else
+ last = (cur == last ? last - 1 : cur);
+ }
+
+ /* ignore ignored and deleted properties if possible */
+ while (retval
+ && ((prpflg(objptr + retval) & PRPFIGN) != 0
+ || ((prpflg(objptr + retval) & PRPFDEL) != 0
+ && (mctx->mcmcxflg & MCMCXF_NO_PRP_DEL) == 0))
+ && cur < cnt && indp[0] == indp[4] && indp[1] == indp[5])
+ {
+ indp += 4;
+ retval = osrp2(indp + 2);
+ }
+ if (retval && osrp2(objptr + retval) != prop)
+ assert(FALSE);
+ }
+ else
+ {
+ /* there's no index -> do sequential search through properties */
+ for (p = objprp(objptr) ; cnt ; p = objpnxt(p), --cnt)
+ {
+ /* if this is the property, and it's not being ignored, use it */
+ if (*(uchar *)p == pbuf[0] && *(((uchar *)p) + 1) == pbuf[1])
+ {
+ if (prpflg(p) & PRPFIGN) /* this is ignored */
+ ignprop = objpofs(objptr, p); /* ... make a note of it */
+ else if ((prpflg(p) & PRPFDEL) != 0 /* it's deleted */
+ && (mctx->mcmcxflg & MCMCXF_NO_PRP_DEL) == 0)
+ /* simply skip it */ ;
+ else
+ {
+ retval = objpofs(objptr, p); /* this is the one */
+ break; /* we're done */
+ }
+ }
+ }
+ }
+
+ if (!retval) retval = ignprop; /* use ignored value if nothing else */
+ if (retval && typptr) *typptr = prptype(objofsp(objptr, retval));
+
+ mcmunlck(mctx, objn); /* done with object, so unlock it */
+ return(retval);
+}
+
+/* get the offset of the end of a property in an object */
+uint objgetp_end(mcmcxdef *ctx, objnum objn, prpnum prop)
+{
+ objdef *objp;
+ prpdef *propptr;
+ uint ofs;
+ uint valsiz;
+
+ /* get the start of the object */
+ ofs = objgetp(ctx, objn, prop, 0);
+ if (ofs == 0)
+ return 0;
+
+ /* get the object */
+ objp = mcmlck(ctx, (mcmon)objn);
+
+ /* get the property */
+ propptr = objofsp(objp, ofs);
+
+ /* get the data size */
+ valsiz = prpsize(propptr);
+
+ /* done with the object */
+ mcmunlck(ctx, (mcmon)objn);
+
+ /*
+ * return the ending offset - it's the starting offset plus the
+ * property header size plus the size of the property data
+ */
+ return ofs + PRPHDRSIZ + valsiz;
+}
+
+/* determine whether an object is a descendant of another object */
+static int objisd(mcmcxdef *ctx, objdef *objptr, objnum parentnum)
+{
+ uchar *sc;
+ int cnt;
+
+ for (sc = objsc(objptr), cnt = objnsc(objptr) ; cnt ;
+ sc += 2, --cnt)
+ {
+ int cursc = osrp2(sc);
+ int ret;
+ objdef *curptr;
+
+ if (cursc == parentnum) return(TRUE);
+
+ curptr = (objdef *)mcmlck(ctx, (mcmon)cursc);
+ ret = objisd(ctx, curptr, parentnum);
+ mcmunlck(ctx, (mcmon)cursc);
+ if (ret) return(TRUE);
+ }
+ return(FALSE);
+}
+
+/*
+ * 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
+ * (in other words, all object locking status is the same as it was on
+ * entry). If the offset is zero, the property was not found.
+ *
+ * This is an internal helper routine - it's not meant to be called
+ * except by objgetap().
+ */
+static uint objgetap0(mcmcxdef *ctx, noreg objnum obj, prpnum prop,
+ objnum *orn, int inh, dattyp *ortyp)
+{
+ uchar *sc;
+ ushort sccnt;
+ ushort psav;
+ dattyp typsav = DAT_NIL;
+ objnum osavn = MCMONINV;
+ uchar *o1;
+ objnum o1n;
+ ushort poff;
+ int found;
+ uint retval;
+ dattyp typ;
+ uchar sclist[100]; /* up to 50 superclasses */
+ objdef *objptr;
+
+ NOREG((&obj))
+
+ /* see if the property is in the current object first */
+ if (!inh && (retval = objgetp(ctx, obj, prop, &typ)) != 0)
+ {
+ /*
+ * tell the caller which object this came from, if the caller
+ * wants to know
+ */
+ if (orn != 0)
+ *orn = obj;
+
+ /* if the caller wants to know the type, return it */
+ if (ortyp != 0)
+ *ortyp = typ;
+
+ /* return the property offset */
+ return retval;
+ }
+
+ /* lock the object, cache its superclass list, and unlock it */
+ objptr = (objdef *)mcmlck(ctx, (mcmon)obj);
+ sccnt = objnsc(objptr);
+ memcpy(sclist, objsc(objptr), (size_t)(sccnt << 1));
+ sc = sclist;
+ mcmunlck(ctx, (mcmon)obj);
+
+ /* try to inherit the property */
+ for (found = FALSE ; sccnt != 0 ; sc += 2, --sccnt)
+ {
+ /* recursively look up the property in this superclass */
+ poff = objgetap0(ctx, (objnum)osrp2(sc), prop, &o1n, FALSE, &typ);
+
+ /* if we found the property, remember it */
+ if (poff != 0)
+ {
+ int isdesc;
+
+ /* if we have a previous object, determine lineage */
+ if (found)
+ {
+ o1 = mcmlck(ctx, o1n);
+ isdesc = objisd(ctx, o1, osavn);
+ mcmunlck(ctx, o1n);
+ }
+
+ /*
+ * if we don't already have a property, or the new object
+ * is a descendant of the previously found object (meaning
+ * that the new object's property should override the
+ * previously found object's property), use this new
+ * property
+ */
+ if (!found || isdesc)
+ {
+ psav = poff;
+ osavn = o1n;
+ typsav = typ;
+ found = TRUE;
+ }
+ }
+ }
+
+ /* set return pointer and return the offset of what we found */
+ if (orn != 0)
+ *orn = osavn;
+
+ /* return the object type if the caller wanted it */
+ if (ortyp != 0)
+ *ortyp = typsav;
+
+ /* return the offset of the property if we found one, or zero if not */
+ return (found ? psav : 0);
+}
+
+/*
+ * 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
+ * (in other words, all object locking status is the same as it was on
+ * entry). If the offset is zero, the property was not found.
+ */
+uint objgetap(mcmcxdef *ctx, noreg objnum obj, prpnum prop,
+ objnum *ornp, int inh)
+{
+ uint retval;
+ dattyp typ;
+ objnum orn;
+
+ /*
+ * even if the caller doesn't care about the original object number,
+ * we do, so provide our own location to store it if necessary
+ */
+ if (ornp == 0)
+ ornp = &orn;
+
+ /* keep going until we've finished translating synonyms */
+ for (;;)
+ {
+ /* look up the property */
+ retval = objgetap0(ctx, obj, prop, ornp, inh, &typ);
+
+ /*
+ * If we found something (i.e., retval != 0), check to see if we
+ * have a synonym; if so, synonym translation is required
+ */
+ if (retval != 0 && typ == DAT_SYN)
+ {
+ prpnum prvprop;
+ objdef *objptr;
+ prpdef *p;
+
+ /*
+ * Translation is required - get new property and try again.
+ * First, remember the original property, so we can make
+ * sure we're not going to loop (at least, not in this one
+ * synonym definition).
+ */
+ prvprop = prop;
+
+ objptr = (objdef *)mcmlck(ctx, (mcmon)*ornp);
+ p = objofsp(objptr, retval);
+ prop = osrp2(prpvalp(p));
+ mcmunlck(ctx, (mcmon)*ornp);
+
+ /* check for direct circularity */
+ if (prop == prvprop)
+ errsig(ctx->mcmcxgl->mcmcxerr, ERR_CIRCSYN);
+
+ /* go back for another try with the new property */
+ continue;
+ }
+
+ /* we don't have to perform a translation; return the result */
+ return retval;
+ }
+}
+
+
+/*
+ * Expand an object by a requested size, and return a pointer to the
+ * object's location. The object will be unlocked and relocked by this
+ * call. The new size is written to the *siz argument.
+ */
+objdef *objexp(mcmcxdef *ctx, objnum obj, ushort *siz)
+{
+ ushort oldsiz;
+ uchar *p;
+
+ oldsiz = mcmobjsiz(ctx, (mcmon)obj);
+ p = mcmrealo(ctx, (mcmon)obj, (ushort)(oldsiz + *siz));
+ *siz = mcmobjsiz(ctx, (mcmon)obj) - oldsiz;
+ return((objdef *)p);
+}
+
+/*
+ * Delete a property in an object. Note that we never actually remove
+ * anything marked as an original property, but just mark it 'ignore'.
+ * This way, it's easy to restore the entire original state of the
+ * objects, simply by deleting everything not marked original and
+ * clearing the 'ignore' flag on the remaining properties. If
+ * 'mark_only' is true, we'll only mark the property as deleted without
+ * actually reclaiming the space; this is necessary when deleting a
+ * method when other methods may follow, since 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)
+{
+ objdef *objptr;
+ uint pofs;
+ prpdef *p;
+ prpdef *nxt;
+ size_t movsiz;
+
+ pofs = objgetp(mctx, objn, prop, (dattyp *)0); /* try to find property */
+ if (!pofs) return; /* not defined - nothing to delete */
+
+ objptr = (objdef *)mcmlck(mctx, objn); /* get lock on object */
+ p = objofsp(objptr, pofs); /* get actual prpdef pointer */
+ nxt = objpnxt(p); /* find next prpdef after this one */
+
+ /* if this is original, just mark 'ignore' */
+ if (prpflg(p) & PRPFORG)
+ {
+ prpflg(p) |= PRPFIGN; /* mark this as overridden */
+ }
+ else if (mark_only)
+ {
+ prpflg(p) |= PRPFDEL; /* mark as deleted without removing space */
+ }
+ else
+ {
+ /* move prpdef's after current one down over current one */
+ movsiz = (uchar *)objptr + objfree(objptr) - (uchar *)nxt;
+ memmove(p, nxt, movsiz);
+
+ objsnp(objptr, objnprop(objptr)-1);
+ objsfree(objptr, objfree(objptr) - (((uchar *)nxt) - ((uchar *)p)));
+ }
+
+ /* tell cache manager this object has been changed, and unlock it */
+ mcmtch(mctx, objn);
+ mcmunlck(mctx, objn);
+}
+
+/*
+ * Set a property of an object to a new value, overwriting the original
+ * value (if any); the object must be unlocked coming in. If an undo
+ * context is provided, an undo record is written; if the undo context
+ * pointer is null, no undo information is kept.
+ */
+void objsetp(mcmcxdef *ctx, objnum objn, prpnum prop, dattyp typ,
+ void *val, objucxdef *undoctx)
+{
+ objdef *objptr;
+ prpdef *p;
+ uint pofs;
+ uint siz;
+ ushort newsiz;
+ int indexed;
+ int prop_was_set;
+
+ /* get a lock on the object */
+ objptr = (objdef *)mcmlck(ctx, objn);
+ indexed = objflg(objptr) & OBJFINDEX;
+
+ /* catch any errors so we can unlock the object */
+ ERRBEGIN(ctx->mcmcxgl->mcmcxerr)
+ {
+ /* get the previous value of the property, if any */
+ pofs = objgetp(ctx, objn, prop, (dattyp *)0);
+ p = objofsp(objptr, pofs);
+ prop_was_set = (p != 0);
+
+ /* start the undo record if we are keeping undo information */
+ if (undoctx && objuok(undoctx))
+ {
+ uchar *up;
+ uchar cmd;
+
+ if (p)
+ {
+ if (prpflg(p) & PRPFORG)
+ {
+ cmd = OBJUOVR; /* override original */
+ p = (prpdef *)0; /* pretend it doesn't even exist */
+ }
+ else cmd = OBJUCHG; /* change property */
+ }
+ else cmd = OBJUADD; /* prop didn't exist - adding it */
+
+ /* write header, reserve space, and get a pointer to the space */
+ up = objures(undoctx, cmd,
+ (ushort)(sizeof(mcmon) + sizeof(prpnum)
+ + (p ? PRPHDRSIZ + prpsize(p) : 0)));
+
+ /* write the object and property numbers */
+ memcpy(up, &objn, (size_t)sizeof(objn));
+ up += sizeof(mcmon);
+ memcpy(up, &prop, (size_t)sizeof(prop));
+ up += sizeof(prop);
+
+ /* if there's existing data, write it */
+ if (p)
+ {
+ memcpy(up, p, (size_t)(PRPHDRSIZ + prpsize(p)));
+ up += PRPHDRSIZ + prpsize(p);
+ }
+
+ /* update the undo context's head offset for the new value */
+ undoctx->objucxhead = up - undoctx->objucxbuf;
+ }
+
+ /* get the size of the data */
+ siz = datsiz(typ, val);
+
+ /*
+ * If the property is already set, and the new data fits, use the
+ * existing slot. However, do not use existing slot if it's
+ * in the non-mutable portion of the object.
+ */
+ if (!p || (uint)prpsize(p) < siz || pofs < (uint)objrst(objptr))
+ {
+ uint avail;
+
+ /* delete any existing value */
+ if (prop_was_set)
+ objdelp(ctx, objn, prop, FALSE);
+
+ /* get the top of the property area */
+ p = objpfre(objptr);
+ /* make sure there's room at the top */
+ avail = mcmobjsiz(ctx, (mcmon)objn) - objfree(objptr);
+ if (avail < siz + PRPHDRSIZ)
+ {
+ newsiz = 64 + ((objfree(objptr) + siz + PRPHDRSIZ) -
+ mcmobjsiz(ctx, (mcmon)objn));
+ objptr = objexp(ctx, objn, &newsiz);
+ p = objpfre(objptr); /* reset pointer if object moved */
+ /* NOTE! Index (if present) is now invalid! */
+ }
+
+ prpsetsize(p, siz); /* set the new property size */
+ prpsetprop(p, prop); /* ... and property id */
+ prpflg(p) = 0; /* no property flags yet */
+ objsnp(objptr, objnprop(objptr) + 1); /* one more prop */
+ objsfree(objptr, objfree(objptr) + siz + PRPHDRSIZ);
+ }
+
+ /* copy the new data to top of object's free space */
+ prptype(p) = typ;
+ if (siz != 0) memcpy(prpvalp(p), val, (size_t)siz);
+ }
+ ERRCLEAN(ctx->mcmcxgl->mcmcxerr)
+ {
+ mcmunlck(ctx, objn); /* unlock the object */
+ }
+ ERRENDCLN(ctx->mcmcxgl->mcmcxerr)
+
+ /* dirty the object, and release lock on object before return */
+ mcmtch(ctx, objn); /* mark the object as changed */
+ mcmunlck(ctx, objn); /* unlock it */
+
+ /* if necessary, rebuild the property index */
+ if (indexed) objindx(ctx, objn);
+}
+
+/* set an undo savepoint */
+void objusav(objucxdef *undoctx)
+{
+ /* the only thing in this record is the OBJUSAV header */
+ objures(undoctx, OBJUSAV, (ushort)0);
+}
+
+/* reserve space in an undo buffer, and write header */
+uchar *objures(objucxdef *undoctx, uchar cmd, ushort siz)
+{
+ ushort prv;
+ uchar *p;
+
+ /* adjust size to include header information */
+ siz += 1 + sizeof(ushort);
+
+ /* make sure there's enough room overall for the record */
+ if (siz > undoctx->objucxsiz) errsig(undoctx->objucxerr, ERR_UNDOVF);
+
+ /* if there's no information, reset buffers */
+ if (undoctx->objucxhead == undoctx->objucxprv)
+ {
+ undoctx->objucxhead = undoctx->objucxprv = undoctx->objucxtail = 0;
+ undoctx->objucxtop = 0;
+ goto done;
+ }
+
+ /* if tail is below head, we can use to top of entire buffer */
+ if (undoctx->objucxtail < undoctx->objucxhead)
+ {
+ /* if there's enough space left after head, we're done */
+ if (undoctx->objucxsiz - undoctx->objucxhead >= siz)
+ goto done;
+
+ /* insufficient space: wrap head down to bottom of buffer */
+ undoctx->objucxtop = undoctx->objucxprv; /* last was top */
+ undoctx->objucxhead = 0;
+ }
+
+ /* head is below tail: delete records until we have enough room */
+ while (undoctx->objucxtail - undoctx->objucxhead < siz)
+ {
+ objutadv(undoctx);
+
+ /* if the tail wrapped, advancing won't do any more good */
+ if (undoctx->objucxtail <= undoctx->objucxhead)
+ {
+ /* if there's enough room at the top, we're done */
+ if (undoctx->objucxsiz - undoctx->objucxhead >= siz)
+ goto done;
+
+ /* still not enough room; wrap the head this time */
+ undoctx->objucxtop = undoctx->objucxprv; /* last was top */
+ undoctx->objucxhead = 0;
+ }
+ }
+
+done:
+ /* save back-link, and set objucxprv pointer to the new record */
+ prv = undoctx->objucxprv;
+ undoctx->objucxprv = undoctx->objucxhead;
+
+ /* write the header: command byte, back-link to previous record */
+ p = &undoctx->objucxbuf[undoctx->objucxhead];
+ *p++ = cmd;
+ memcpy(p, &prv, sizeof(prv));
+
+ /* advance the head pointer past the header */
+ undoctx->objucxhead += 1 + sizeof(prv);
+
+ /* set the high-water mark if we've exceeded the old one */
+ if (undoctx->objucxprv > undoctx->objucxtop)
+ undoctx->objucxtop = undoctx->objucxprv;
+
+ /* return the reserved space */
+ return &undoctx->objucxbuf[undoctx->objucxhead];
+}
+
+/* advance the undo tail pointer over the record it points to */
+void objutadv(objucxdef *undoctx)
+{
+ uchar *p;
+ ushort siz;
+ uchar pr[PRPHDRSIZ]; /* space for a property header */
+ uchar cmd;
+
+ /* if we're at the most recently written record, flush buffer */
+ if (undoctx->objucxtail == undoctx->objucxprv)
+ {
+ undoctx->objucxtail = 0;
+ undoctx->objucxprv = 0;
+ undoctx->objucxhead = 0;
+ undoctx->objucxtop = 0;
+ }
+
+ /* if we've reached high water mark, wrap back to bottom */
+ if (undoctx->objucxtail == undoctx->objucxtop)
+ {
+ undoctx->objucxtail = 0;
+ return;
+ }
+
+ /* determine size by inspecting current record */
+ p = undoctx->objucxbuf + undoctx->objucxtail;
+ siz = 1 + sizeof(ushort); /* basic header size */
+
+ cmd = *p++;
+ p += sizeof(ushort); /* skip the previous pointer */
+
+ switch(cmd)
+ {
+ case OBJUCHG:
+ /* change: property header (added below) plus data value */
+ memcpy(pr, p + sizeof(mcmon) + sizeof(prpnum), (size_t)PRPHDRSIZ);
+ siz += PRPHDRSIZ + prpsize(pr);
+ /* FALLTHROUGH */
+
+ case OBJUADD:
+ case OBJUOVR:
+ /* add/override: property header only */
+ siz += sizeof(mcmon) + sizeof(prpnum);
+ break;
+
+ case OBJUCLI:
+ siz += (*undoctx->objucxcsz)(undoctx->objucxccx, p);
+ break;
+
+ case OBJUSAV:
+ break;
+ }
+
+ undoctx->objucxtail += siz;
+}
+
+/* undo one undo record, and remove it from the undo list */
+void obj1undo(mcmcxdef *mctx, objucxdef *undoctx)
+{
+ uchar *p;
+ prpnum prop = 0;
+ objnum objn = 0;
+ uchar cmd;
+ uchar pr[PRPHDRSIZ]; /* space for property header */
+ ushort prv;
+ ushort pofs;
+ objdef *objptr;
+ int indexed = 0;
+
+ /* if there's no more undo, signal an error */
+ if (undoctx->objucxprv == undoctx->objucxhead)
+ errsig(undoctx->objucxerr, ERR_NOUNDO);
+
+ /* move back to previous record */
+ undoctx->objucxhead = undoctx->objucxprv;
+ p = &undoctx->objucxbuf[undoctx->objucxprv];
+
+ /* get command, and set undocxprv to previous record */
+ cmd = *p++;
+ memcpy(&prv, p, sizeof(prv));
+ p += sizeof(prv);
+
+ /* if we're at the tail, no more undo; otherwise, use back link */
+ if (undoctx->objucxprv == undoctx->objucxtail)
+ undoctx->objucxprv = undoctx->objucxhead;
+ else
+ undoctx->objucxprv = prv;
+
+ if (cmd == OBJUSAV) return; /* savepointer marker - nothing to do */
+
+ /* get object/property information for property-changing undo */
+ if (cmd != OBJUCLI)
+ {
+ memcpy(&objn, p, (size_t)sizeof(objn));
+ p += sizeof(objn);
+ memcpy(&prop, p, (size_t)sizeof(prop));
+ p += sizeof(prop);
+ objptr = mcmlck(mctx, objn);
+ indexed = (objflg(objptr) & OBJFINDEX);
+ mcmunlck(mctx, objn);
+ }
+
+ switch(cmd)
+ {
+ case OBJUADD:
+ objdelp(mctx, objn, prop, FALSE);
+ if (indexed) objindx(mctx, objn);
+ break;
+
+ case OBJUOVR:
+ objdelp(mctx, objn, prop, FALSE); /* delete the non-original value */
+ pofs = objgetp(mctx, objn, prop, (dattyp *)0); /* get ignored prop */
+ objptr = (objdef *)mcmlck(mctx, objn); /* lock the object */
+ prpflg(objofsp(objptr, pofs)) &= ~PRPFIGN; /* no longer ignored */
+ mcmunlck(mctx, objn); /* unlock the object */
+ break;
+
+ case OBJUCHG:
+ memcpy(pr, p, (size_t)PRPHDRSIZ);
+ p += PRPHDRSIZ;
+ objsetp(mctx, objn, prop, prptype(pr), (void *)p, (objucxdef *)0);
+ break;
+
+ case OBJUCLI:
+ (*undoctx->objucxcun)(undoctx->objucxccx, p);
+ break;
+ }
+}
+
+/*
+ * Determine if it's ok to add undo records - returns TRUE if a
+ * savepoint has been stored in the undo log, FALSE if not.
+ */
+int objuok(objucxdef *undoctx)
+{
+ ushort prv;
+
+ /* see if there's any more undo information */
+ if (undoctx->objucxprv == undoctx->objucxhead)
+ return(FALSE);
+
+ /* look for most recent savepoint marker */
+ for (prv = undoctx->objucxprv ;; )
+ {
+ if (undoctx->objucxbuf[prv] == OBJUSAV)
+ return(TRUE); /* found a savepoint - can add undo */
+
+ /* if we've reached the tail, there are no more undo records */
+ if (prv == undoctx->objucxtail)
+ return(FALSE); /* no savepoints - can't add undo */
+
+ /* get previous record */
+ memcpy(&prv, &undoctx->objucxbuf[prv+1], sizeof(prv));
+ }
+}
+
+/*
+ * 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)
+{
+ ushort prv;
+ ushort sav;
+
+ /* see if there's any more undo information */
+ if (undoctx->objucxprv == undoctx->objucxhead)
+ errsig(undoctx->objucxerr, ERR_NOUNDO);
+
+ /* look for most recent savepoint marker */
+ for (prv = undoctx->objucxprv ;; )
+ {
+ if (undoctx->objucxbuf[prv] == OBJUSAV)
+ {
+ sav = prv;
+ break;
+ }
+
+ /* if we've reached the tail, there are no more undo records */
+ if (prv == undoctx->objucxtail)
+ errsig(undoctx->objucxerr, ERR_ICUNDO);
+
+ /* get previous record */
+ memcpy(&prv, &undoctx->objucxbuf[prv+1], sizeof(prv));
+ }
+
+ /* now undo everything until we get to the savepoint */
+ do { obj1undo(mctx, undoctx); } while (undoctx->objucxhead != sav);
+}
+
+/* initialize undo context */
+objucxdef *objuini(mcmcxdef *ctx, ushort siz,
+ void (*undocb)(void *, uchar *),
+ ushort (*sizecb)(void *, uchar *),
+ void *callctx)
+{
+ objucxdef *ret;
+ long totsiz;
+
+ /* force size into valid range */
+ totsiz = (long)siz + sizeof(objucxdef) - 1;
+ if (totsiz > 0xff00)
+ siz = 0xff00 - sizeof(objucxdef) + 1;
+
+ ret = (objucxdef *)mchalo(ctx->mcmcxgl->mcmcxerr,
+ (sizeof(objucxdef) + siz - 1),
+ "objuini");
+
+ ret->objucxmem = ctx;
+ ret->objucxerr = ctx->mcmcxgl->mcmcxerr;
+ ret->objucxsiz = siz;
+ ret->objucxhead = ret->objucxprv = ret->objucxtail = ret->objucxtop = 0;
+
+ /* set client callback functions */
+ ret->objucxcun = undocb; /* callback to apply client undo */
+ ret->objucxcsz = sizecb; /* callback to get size of client undo */
+ ret->objucxccx = callctx; /* context for client callback functions */
+
+ return(ret);
+}
+
+/* discard all undo records */
+void objulose(objucxdef *ctx)
+{
+ if (ctx)
+ ctx->objucxhead =
+ ctx->objucxprv =
+ ctx->objucxtail =
+ ctx->objucxtop = 0;
+}
+
+/* uninitialize the undo context - release allocated memory */
+void objuterm(objucxdef *uctx)
+{
+ /* free the undo memory block */
+ mchfre(uctx);
+}
+
+/* revert object to original (post-compilation) values */
+void objrevert(void *ctx0, mcmon objn)
+{
+ mcmcxdef *mctx = (mcmcxdef *)ctx0;
+ uchar *p;
+ prpdef *pr;
+ int cnt;
+ int indexed;
+
+ p = mcmlck(mctx, objn);
+ pr = objprp(p);
+ indexed = objflg(p) & OBJFINDEX;
+
+ /* restore original settings */
+ objsfree(p, objrst(p));
+ objsnp(p, objstat(p));
+
+ /* go through original properties and remove 'ignore' flag if set */
+ for (cnt = objnprop(p) ; cnt ; pr = objpnxt(pr), --cnt)
+ prpflg(pr) &= ~PRPFIGN;
+
+ /* touch object and unlock it */
+ mcmtch(mctx, objn);
+ mcmunlck(mctx, objn);
+
+ /* if it's indexed, rebuild the index */
+ if (indexed) objindx(mctx, objn);
+}
+
+/* set 'ignore' flag for original properties set in mutable part */
+void objsetign(mcmcxdef *mctx, objnum objn)
+{
+ objdef *objptr;
+ prpdef *mut;
+ prpdef *p;
+ int statcnt;
+ int cnt;
+ int indexed;
+ prpdef *p1;
+
+ objptr = (objdef *)mcmlck(mctx, (mcmon)objn);
+ p1 = objprp(objptr);
+ indexed = objflg(objptr) & OBJFINDEX;
+
+ /* go through mutables, and set ignore on duplicates in non-mutables */
+ for (mut = (prpdef *)(objptr + objrst(objptr)),
+ cnt = objnprop(objptr) - objstat(objptr) ; cnt ;
+ mut = objpnxt(mut), --cnt)
+ {
+ for (p = p1, statcnt = objstat(objptr) ; statcnt ;
+ p = objpnxt(p), --statcnt)
+ {
+ /* if this static prop matches a mutable prop, ignore it */
+ if (prpprop(p) == prpprop(mut))
+ {
+ prpflg(p) |= PRPFIGN;
+ break;
+ }
+ }
+ }
+
+ mcmtch(mctx, (mcmon)objn);
+ mcmunlck(mctx, (mcmon)objn);
+ if (indexed) objindx(mctx, objn);
+}
+
+/*
+ * Build or rebuild a property index for an object.
+ */
+void objindx(mcmcxdef *mctx, objnum objn)
+{
+ uint newsiz;
+ uint avail;
+ objdef *objptr;
+ uint cnt;
+ prpdef *p;
+ uchar *indp = nullptr;
+ uchar *indbase;
+ uint icnt;
+ uint first;
+ uint last;
+ uint cur = 0;
+
+ objptr = (objdef *)mcmlck(mctx, objn); /* get object pointer */
+ cnt = objnprop(objptr); /* get number of properties */
+ p = objprp(objptr); /* get pointer to properties (or old index) */
+ newsiz = 2 + 4*cnt; /* figure size needed for the index */
+
+ avail = mcmobjsiz(mctx, objn) - objfree(objptr);
+
+ /* insert space for the index; expand the object if necessary */
+ if (avail < newsiz)
+ {
+ ushort need;
+
+ newsiz += 10*4; /* add some extra space for later */
+ need = newsiz - avail; /* compute amount of space needed */
+ objptr = objexp(mctx, objn, &need);
+ p = objprp(objptr);
+ }
+
+ /* now build the index */
+ indbase = objpfre(objptr);
+ for (icnt = 0 ; cnt ; p = objpnxt(p), --cnt, ++icnt)
+ {
+ uint ofs = (uchar *)p - (uchar *)objptr;
+
+ if (icnt)
+ {
+ /* figure out where to insert this property */
+ first = 0;
+ last = icnt - 1;
+ for (;;)
+ {
+ if (first > last) break;
+ cur = first + (last - first)/2;
+ indp = indbase + cur*4;
+ if (indp[0] == p[0] && indp[1] == p[1])
+ break;
+ else if (indp[0] < p[0]
+ || (indp[0] == p[0] && indp[1] < p[1]))
+ first = (cur == first ? first + 1 : cur);
+ else
+ last = (cur == last ? last - 1 : cur);
+ }
+
+ /* make sure we're positioned just before insertion point */
+ while (cur < icnt
+ && (indp[0] <= p[0]
+ || (indp[0] == p[0] && indp[1] <= p[1])))
+ {
+ indp += 4;
+ ++cur;
+ }
+
+ /* move elements above if any */
+ if (cur < icnt)
+ memmove(indp + 4, indp, (size_t)((icnt - cur) * 4));
+ }
+ else
+ indp = indbase;
+
+ /* insert property into index */
+ indp[0] = p[0];
+ indp[1] = p[1];
+ oswp2(indp+2, ofs);
+ }
+
+ /* set the index flag, and dirty and free the object */
+ objsflg(objptr, objflg(objptr) | OBJFINDEX);
+ mcmtch(mctx, (mcmon)objn);
+ mcmunlck(mctx, (mcmon)objn);
+}
+
+/* allocate and initialize an object */
+objdef *objnew(mcmcxdef *mctx, int sccnt, ushort propspace,
+ objnum *objnptr, int classflg)
+{
+ objdef *o;
+ mcmon objn;
+
+ /* allocate cache object */
+ o = (objdef *)mcmalo(mctx, (ushort)(OBJDEFSIZ + sccnt * 2 + propspace),
+ &objn);
+
+ /* set up object descriptor for the new object */
+ objini(mctx, sccnt, (objnum)objn, classflg);
+
+ *objnptr = (objnum)objn;
+ return(o);
+}
+
+/* initialize an already allocated object */
+void objini(mcmcxdef *mctx, int sccnt, objnum objn, int classflg)
+{
+ objdef *o;
+ uint flags = 0;
+
+ /* get a lock on the object */
+ o = (objdef *)mcmlck(mctx, objn);
+
+ memset(o, 0, (size_t)10);
+ objsnsc(o, sccnt);
+ objsfree(o, ((uchar *)objsc(o) + 2*sccnt) - (uchar *)o);
+
+ /* set up flags */
+ if (classflg) flags |= OBJFCLASS;
+ objsflg(o, flags);
+
+ /* tell cache manager that this object has been modified */
+ mcmtch(mctx, objn);
+ mcmunlck(mctx, objn);
+}
+
+/*
+ * Get the first superclass of an object. If it doesn't have any
+ * superclasses, return invalid.
+ */
+objnum objget1sc(mcmcxdef *ctx, objnum objn)
+{
+ objdef *p;
+ objnum retval;
+
+ /* lock the object */
+ p = mcmlck(ctx, (mcmon)objn);
+
+ /* get the first superclass if it has any */
+ if (objnsc(p) == 0)
+ retval = MCMONINV;
+ else
+ retval = osrp2(objsc(p));
+
+ /* unlock the object and return the superclass value */
+ mcmunlck(ctx, (mcmon)objn);
+ return retval;
+}
} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk
-
-#endif
diff --git a/engines/glk/tads/tads2/opcode.h b/engines/glk/tads/tads2/opcode.h
new file mode 100644
index 0000000..4175db3
--- /dev/null
+++ b/engines/glk/tads/tads2/opcode.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
+#define GLK_TADS_TADS2_OPCODE
+
+/*
+ * 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/opcode_defs.h b/engines/glk/tads/tads2/opcode_defs.h
deleted file mode 100644
index 32519ae..0000000
--- a/engines/glk/tads/tads2/opcode_defs.h
+++ /dev/null
@@ -1,218 +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_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/post_compilation.cpp b/engines/glk/tads/tads2/post_compilation.cpp
new file mode 100644
index 0000000..ca29601
--- /dev/null
+++ b/engines/glk/tads/tads2/post_compilation.cpp
@@ -0,0 +1,466 @@
+/* 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/post_compilation.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/os.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/*
+ * Special character sequence description table for TADS language.
+ * Note that operators that start with common sequences must be grouped
+ * together, with the shorter sequences preceding the longer sequences.
+ * For example, ":" and ":=" must be adjacent, and ":" must precede
+ * ":=". Other than this restriction, the order of tokens doesn't
+ * matter.
+ */
+tokldef supsctab[] =
+{
+ { TOKTCOLON, ":" },
+ { TOKTASSIGN, ":=" },
+ { TOKTLT, "<" },
+ { TOKTLE, "<=" },
+ { TOKTSHL, "<<" },
+ { TOKTSHLEQ, "<<="},
+ { TOKTNE, "<>" },
+ { TOKTGT, ">" },
+ { TOKTSHR, ">>" },
+ { TOKTSHREQ, ">>="},
+ { TOKTGE, ">=" },
+ { TOKTLPAR, "(" },
+ { TOKTRPAR, ")" },
+ { TOKTPLUS, "+" },
+ { TOKTINC, "++" },
+ { TOKTPLEQ, "+=" },
+ { TOKTMINUS, "-" },
+ { TOKTPOINTER, "->" },
+ { TOKTDEC, "--" },
+ { TOKTMINEQ, "-=" },
+ { TOKTDIV, "/" },
+ { TOKTMOD, "%" },
+ { TOKTMODEQ, "%=" },
+ { TOKTDIVEQ, "/=" },
+ { TOKTTIMES, "*" },
+ { TOKTTIMEQ, "*=" },
+ { TOKTEQ, "=" },
+ { TOKTEQEQ, "==" },
+ { TOKTLBRACK, "[" },
+ { TOKTRBRACK, "]" },
+ { TOKTLBRACE, "{" },
+ { TOKTRBRACE, "}" },
+ { TOKTSEM, ";" },
+ { TOKTCOMMA, "," },
+ { TOKTDOT, "." },
+ { TOKTELLIPSIS,"..." },
+ { TOKTPOUND, "#" },
+ { TOKTBAND, "&" },
+ { TOKTBANDEQ, "&=" },
+ { TOKTAND, "&&" },
+ { TOKTBOR, "|" },
+ { TOKTBOREQ, "|=" },
+ { TOKTOR, "||" },
+ { TOKTQUESTION,"?" },
+ { TOKTDSTRING, "\"" },
+ { TOKTSSTRING, "'" },
+ { TOKTNOT, "!" },
+ { TOKTNE, "!=" },
+ { TOKTXOR, "^" },
+ { TOKTXOREQ, "^=" },
+ { TOKTTILDE, "~" },
+ { 0, "" }
+};
+
+typedef struct supkwdef supkwdef;
+struct supkwdef
+{
+ char *supkwnam;
+ int supkwtok;
+};
+
+static supkwdef supkwtab[] =
+{
+ { "not", TOKTNOT },
+ { "if", TOKTIF },
+ { "else", TOKTELSE },
+ { "while", TOKTWHILE },
+ { "break", TOKTBREAK },
+ { "continue", TOKTCONTINUE },
+ { "exit", TOKTEXIT },
+ { "abort", TOKTABORT },
+ { "and", TOKTAND },
+ { "or", TOKTOR },
+ { "function", TOKTFUNCTION },
+ { "return", TOKTRETURN },
+ { "local", TOKTLOCAL },
+ { "object", TOKTOBJECT },
+ { "nil", TOKTNIL },
+ { "true", TOKTTRUE },
+ { "pass", TOKTPASS },
+ { "askdo", TOKTASKDO },
+ { "askio", TOKTASKIO },
+ { "ioSynonym", TOKTIOSYN },
+ { "doSynonym", TOKTDOSYN },
+ { "external", TOKTEXTERN },
+ { "formatstring", TOKTFORMAT },
+ { "compoundWord", TOKTCOMPOUND },
+ { "specialWords", TOKTSPECIAL },
+ { "class", TOKTCLASS },
+
+ /* new keywords for V2 */
+ { "\002", 0 }, /* special flag for start of v2 section */
+ { "for", TOKTFOR },
+ { "\001", 0 }, /* special flag that "do" is next keyword */
+ { "do", TOKTDO },
+ { "switch", TOKTSWITCH },
+ { "case", TOKTCASE },
+ { "default", TOKTDEFAULT },
+ { "goto", TOKTGOTO },
+ { "replace", TOKTREPLACE },
+ { "modify", TOKTMODIFY },
+
+ { "new", TOKTNEW },
+ { "delete", TOKTDELETE },
+ { (char *)0, 0 }
+};
+
+typedef struct supprdef supprdef;
+struct supprdef
+{
+ char *supprnam;
+ prpnum supprval;
+};
+
+static supprdef supprtab[] =
+{
+ { "verb", PRP_VERB },
+ { "noun", PRP_NOUN },
+ { "adjective", PRP_ADJ },
+ { "preposition", PRP_PREP },
+ { "article", PRP_ARTICLE },
+ { "plural", PRP_PLURAL },
+
+ /* add some more built-in properties */
+ { "doAction", PRP_DOACTION },
+ { "ioAction", PRP_IOACTION },
+ { "sdesc", PRP_SDESC },
+ { "thedesc", PRP_THEDESC },
+ { "ioDefault", PRP_IODEFAULT },
+ { "doDefault", PRP_DODEFAULT },
+ { "location", PRP_LOCATION },
+ { "value", PRP_VALUE },
+ { "roomAction", PRP_ROOMACTION },
+ { "actorAction", PRP_ACTORACTION },
+ { "contents", PRP_CONTENTS },
+ { "prepDefault", PRP_PREPDEFAULT },
+ { "verActor", PRP_VERACTOR },
+ { "validDo", PRP_VALIDDO },
+ { "validIo", PRP_VALIDIO },
+ { "lookAround", PRP_LOOKAROUND },
+ { "roomCheck", PRP_ROOMCHECK },
+ { "statusLine", PRP_STATUSLINE },
+ { "locationOK", PRP_LOCOK },
+ { "isVisible", PRP_ISVIS },
+ { "cantReach", PRP_NOREACH },
+ { "isHim", PRP_ISHIM },
+ { "isHer", PRP_ISHER },
+ { "action", PRP_ACTION },
+ { "validDoList", PRP_VALDOLIST },
+ { "validIoList", PRP_VALIOLIST },
+ { "dobjGen", PRP_DOBJGEN },
+ { "iobjGen", PRP_IOBJGEN },
+ { "nilPrep", PRP_NILPREP },
+ { "rejectMultiDobj", PRP_REJECTMDO },
+ { "moveInto", PRP_MOVEINTO },
+ { "construct", PRP_CONSTRUCT },
+ { "destruct", PRP_DESTRUCT },
+ { "validActor", PRP_VALIDACTOR },
+ { "preferredActor", PRP_PREFACTOR },
+ { "isEquivalent", PRP_ISEQUIV },
+ { "adesc", PRP_ADESC },
+ { "multisdesc", PRP_MULTISDESC },
+ { "anyvalue", PRP_ANYVALUE },
+ { "newNumbered", PRP_NEWNUMOBJ },
+ { "parseUnknownDobj", PRP_PARSEUNKNOWNDOBJ },
+ { "parseUnknownIobj", PRP_PARSEUNKNOWNIOBJ },
+ { "dobjCheck", PRP_DOBJCHECK },
+ { "iobjCheck", PRP_IOBJCHECK },
+ { "verbAction", PRP_VERBACTION },
+ { "disambigDobj", PRP_DISAMBIGDO },
+ { "disambigIobj", PRP_DISAMBIGIO },
+ { "prefixdesc", PRP_PREFIXDESC },
+ { "isThem", PRP_ISTHEM },
+
+ /* still more - TADS/Graphic properties */
+ { "gp_picture", PRP_GP_PIC },
+ { "gp_name", PRP_GP_NAME },
+ { "gp_defverb", PRP_GP_DEFVERB },
+ { "gp_active", PRP_GP_ACTIVE },
+ { "gp_hotlist", PRP_GP_HOTLIST },
+ { "gp_icon", PRP_GP_ICON },
+ { "gp_defverb2", PRP_GP_DEFVERB2 },
+ { "gp_defprep", PRP_GP_DEFPREP },
+ { "gp_hotid", PRP_GP_HOTID },
+ { "gp_overlay", PRP_GP_OVERLAY },
+ { "gp_hotx", PRP_GP_HOTX },
+ { "gp_hoty", PRP_GP_HOTY },
+
+ /* flag end of list with null property name */
+ { (char *)0, 0 }
+};
+
+/* define a built-in symbol */
+static void supaddsym(toktdef *tab, char *txt, int styp, int sval,
+ int casefold)
+{
+ char buf[40];
+
+ if (casefold)
+ {
+ strcpy(buf, txt);
+ os_strlwr(buf);
+ txt = buf;
+ }
+ (*tab->toktfadd)(tab, txt, (int)strlen(txt), styp, sval, tokhsh(txt));
+}
+
+/* add a built-in function to a symbol table */
+static void supaddbi(void (*bif[])(bifcxdef *, int),
+ toktdef *tab, char *txt,
+ void (*fn)(bifcxdef *, int), int num, int casefold)
+{
+ supaddsym(tab, txt, TOKSTBIFN, num, casefold);
+ bif[num] = fn;
+}
+
+/* set up reserved words: built-in functions and properties, keywords, etc */
+void suprsrv(supcxdef *sup, void (*bif[])(bifcxdef *, int),
+ toktdef *tab, int max, int v1compat, char *new_do,
+ int casefold)
+{
+ supkwdef *kw;
+ supbidef *p;
+ int i;
+ supprdef *pr;
+ extern supbidef osfar_t supbitab[];
+ int do_kw = FALSE;
+ char *kwname;
+ char buf[40];
+
+ /* add built-in functions */
+ for (p = supbitab, i = 0 ; p->supbinam ; ++i, ++p)
+ {
+ if (i >= max) errsig(sup->supcxerr, ERR_MANYBIF);
+ supaddbi(bif, tab, p->supbinam, p->supbifn, i, casefold);
+ }
+
+ /* add keywords */
+ for (kw = supkwtab ; kw->supkwnam ; ++kw)
+ {
+ if (kw->supkwnam[0] == '\002')
+ {
+ if (v1compat) break; /* no v2 keywords - quit now */
+ else continue; /* keep going, but skip this flag entry */
+ }
+
+ /* if this is the "do" keyword, change to user-supplied value */
+ if (do_kw && new_do)
+ kwname = new_do;
+ else
+ kwname = kw->supkwnam;
+
+ if (kw->supkwnam[0] == '\001')
+ {
+ do_kw = TRUE;
+ continue;
+ }
+ else
+ do_kw = FALSE;
+
+ if (casefold)
+ {
+ strcpy(buf, kwname);
+ os_strlwr(buf);
+ kwname = buf;
+ }
+ (*tab->toktfadd)(tab, kwname, (int)strlen(kwname),
+ TOKSTKW, kw->supkwtok, tokhsh(kwname));
+ }
+
+ /* add pseudo-variables */
+ supaddsym(tab, "self", TOKSTSELF, 0, casefold);
+ supaddsym(tab, "inherited", TOKSTINHERIT, 0, casefold);
+ supaddsym(tab, "argcount", TOKSTARGC, 0, casefold);
+
+ /* add built-in properties */
+ for (pr = supprtab ; pr->supprnam ; ++pr)
+ supaddsym(tab, pr->supprnam, TOKSTPROP, pr->supprval, casefold);
+}
+
+/* get name of an object out of symbol table */
+void supgnam(char *buf, tokthdef *tab, objnum objn)
+{
+ toksdef sym;
+
+ if (!tab)
+ {
+ strcpy(buf, "<NO SYMBOL TABLE>");
+ return;
+ }
+
+ if (tokthfind((toktdef *)tab, TOKSTOBJ, (uint)objn, &sym)
+ || tokthfind((toktdef *)tab, TOKSTFWDOBJ, (uint)objn, &sym))
+ {
+ memcpy(buf, sym.toksnam, (size_t)sym.tokslen);
+ buf[sym.tokslen] = '\0';
+ return;
+ }
+
+ strcpy(buf, "<UNKNOWN>");
+}
+
+/* set up inherited vocabulary */
+void supivoc(supcxdef *ctx)
+{
+ vocidef ***vpg;
+ vocidef **v;
+ voccxdef *voc = ctx->supcxvoc;
+ int i;
+ int j;
+ objnum obj;
+
+ /* delete all existing inherited words */
+ vocdelinh(voc);
+
+ for (vpg = voc->voccxinh, i = 0 ; i < VOCINHMAX ; ++vpg, ++i)
+ {
+ if (!*vpg) continue; /* no entries on this page */
+ for (v = *vpg, obj = (i << 8), j = 0 ; j < 256 ; ++v, ++obj, ++j)
+ {
+ /* if it's not a class, inherit vocabulary for the object */
+ if (!*v) continue;
+ if (!((*v)->vociflg & VOCIFCLASS))
+ {
+ (*v)->vociilc = MCMONINV; /* no inherited location yet */
+ supivoc1(ctx, ctx->supcxvoc, *v, obj, FALSE, 0);
+ }
+ }
+ }
+}
+
+/* find a single required object, by name */
+static void supfind1(errcxdef *ec, toktdef *tab, char *nam, objnum *objp,
+ int required, int *errp, int warnlevel, int casefold)
+{
+ toksdef sym;
+ int namel = strlen(nam);
+ char buf[40];
+
+ if (casefold)
+ {
+ strcpy(buf, nam);
+ os_strlwr(buf);
+ nam = buf;
+ }
+ if ((*tab->toktfsea)(tab, nam, namel, tokhsh(nam), &sym))
+ {
+ *objp = (objnum)sym.toksval;
+ }
+ else
+ {
+ if (required || warnlevel > 1)
+ errlog1(ec, (required ? ERR_RQOBJNF : ERR_WRNONF),
+ ERRTSTR, errstr(ec, nam, namel));
+ *objp = MCMONINV;
+ if (required) *errp = 1;
+ }
+}
+
+/* find required objects/functions */
+void supfind(errcxdef *ec, tokthdef *htab, voccxdef *voc,
+ objnum *preinit, int warnlevel, int cf)
+{
+ int err = 0;
+ toktdef *tab = &htab->tokthsc;
+
+ /* look up the required and optional symbols */
+ supfind1(ec, tab, "Me", &voc->voccxme, TRUE, &err, warnlevel, cf);
+ supfind1(ec, tab, "takeVerb", &voc->voccxvtk, TRUE, &err, warnlevel, cf);
+ supfind1(ec, tab, "strObj", &voc->voccxstr, TRUE, &err, warnlevel, cf);
+ supfind1(ec, tab, "numObj", &voc->voccxnum, TRUE, &err, warnlevel, cf);
+ supfind1(ec, tab, "pardon", &voc->voccxprd, TRUE, &err, warnlevel, cf);
+ supfind1(ec, tab, "againVerb", &voc->voccxvag, TRUE, &err, warnlevel, cf);
+ supfind1(ec, tab, "init", &voc->voccxini, TRUE, &err, warnlevel, cf);
+ supfind1(ec, tab, "preinit", preinit, FALSE, &err, warnlevel, cf);
+ supfind1(ec, tab, "preparse", &voc->voccxpre, FALSE, &err, warnlevel, cf);
+ supfind1(ec, tab, "preparseExt", &voc->voccxpre2, FALSE, &err,
+ warnlevel, cf);
+ supfind1(ec, tab, "parseError", &voc->voccxper, FALSE, &err, warnlevel,
+ cf);
+ supfind1(ec, tab, "commandPrompt", &voc->voccxprom, FALSE, &err,
+ warnlevel, cf);
+ supfind1(ec, tab, "parseDisambig", &voc->voccxpdis, FALSE, &err,
+ warnlevel, cf);
+ supfind1(ec, tab, "parseError2", &voc->voccxper2, FALSE, &err, warnlevel,
+ cf);
+ supfind1(ec, tab, "parseDefault", &voc->voccxpdef, FALSE, &err, warnlevel,
+ cf);
+ supfind1(ec, tab, "parseDefaultExt", &voc->voccxpdef2, FALSE, &err,
+ warnlevel, cf);
+ supfind1(ec, tab, "parseAskobj", &voc->voccxpask, FALSE, &err, warnlevel,
+ cf);
+ supfind1(ec, tab, "preparseCmd", &voc->voccxppc, FALSE, &err, warnlevel,
+ cf);
+ supfind1(ec, tab, "parseAskobjActor", &voc->voccxpask2, FALSE,
+ &err, warnlevel, cf);
+ supfind1(ec, tab, "parseAskobjIndirect", &voc->voccxpask3, FALSE,
+ &err, warnlevel, cf);
+ supfind1(ec, tab, "parseErrorParam", &voc->voccxperp, FALSE, &err,
+ warnlevel, cf);
+ supfind1(ec, tab, "commandAfterRead", &voc->voccxpostprom, FALSE,
+ &err, warnlevel, cf);
+ supfind1(ec, tab, "initRestore", &voc->voccxinitrestore, FALSE,
+ &err, warnlevel, cf);
+ supfind1(ec, tab, "parseUnknownVerb", &voc->voccxpuv, FALSE,
+ &err, warnlevel, cf);
+ supfind1(ec, tab, "parseNounPhrase", &voc->voccxpnp, FALSE,
+ &err, warnlevel, cf);
+ supfind1(ec, tab, "postAction", &voc->voccxpostact, FALSE,
+ &err, warnlevel, cf);
+ supfind1(ec, tab, "endCommand", &voc->voccxendcmd, FALSE,
+ &err, warnlevel, cf);
+ supfind1(ec, tab, "preCommand", &voc->voccxprecmd, FALSE,
+ &err, warnlevel, cf);
+
+ /* "Me" is always the initial Me object */
+ voc->voccxme_init = voc->voccxme;
+
+ /* if we encountered any errors, signal the problem */
+ if (err)
+ errsig(ec, ERR_UNDEF);
+}
+
+} // 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
index 71b8d78..732cfdf 100644
--- a/engines/glk/tads/tads2/run.h
+++ b/engines/glk/tads/tads2/run.h
@@ -37,7 +37,7 @@
#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/opcode.h"
#include "glk/tads/tads2/property.h"
#include "glk/tads/tads2/text_io.h"
#include "glk/tads/tads2/tokenizer.h"
Commit: fcb2592ec24f983c49f1e2e1b4f41cc9659a9229
https://github.com/scummvm/scummvm/commit/fcb2592ec24f983c49f1e2e1b4f41cc9659a9229
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00
Commit Message:
GLK: TADS2: Added built in functions
Changed paths:
A engines/glk/tads/tads2/built_in.cpp
A engines/glk/tads/tads2/debug.cpp
A engines/glk/tads/tads2/string_resources.h
engines/glk/module.mk
engines/glk/tads/os_glk.h
engines/glk/tads/osfrobtads.cpp
engines/glk/tads/osfrobtads.h
engines/glk/tads/tads2/error.cpp
engines/glk/tads/tads2/error_handling.cpp
engines/glk/tads/tads2/os.cpp
diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index ef5dcb3..810c493 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -94,8 +94,10 @@ MODULE_OBJS := \
tads/detection.o \
tads/osfrobtads.o \
tads/tads.o \
+ tads/tads2/built_in.o \
tads/tads2/character_map.o \
tads/tads2/data.o \
+ tads/tads2/debug.o \
tads/tads2/error.o \
tads/tads2/error_handling.o \
tads/tads2/file_io.o \
diff --git a/engines/glk/tads/os_glk.h b/engines/glk/tads/os_glk.h
index e58868b..75354ea 100644
--- a/engines/glk/tads/os_glk.h
+++ b/engines/glk/tads/os_glk.h
@@ -31,6 +31,9 @@
namespace Glk {
namespace TADS {
+#define TADS_RUNTIME_VERSION "2.5.17"
+#define OS_SYSTEM_NAME "ScummGlk"
+
#define OSPATHCHAR '/'
#define OSPATHALT ""
#define OSPATHURL "/"
diff --git a/engines/glk/tads/osfrobtads.cpp b/engines/glk/tads/osfrobtads.cpp
index eabd297..6dcaadb 100644
--- a/engines/glk/tads/osfrobtads.cpp
+++ b/engines/glk/tads/osfrobtads.cpp
@@ -81,6 +81,10 @@ char *osfgets(char *buf, size_t count, osfildef *fp) {
return buf;
}
+int osfputs(const char *str, osfildef *fp) {
+ return dynamic_cast<Common::WriteStream *>(fp)->write(str, strlen(str)) == strlen(str) ? 0 : -1;
+}
+
bool os_locate(const char *fname, int flen, const char *arg0, char *buf, size_t bufsiz) {
Common::String name = !flen ? Common::String(fname) : Common::String(fname, fname + flen);
diff --git a/engines/glk/tads/osfrobtads.h b/engines/glk/tads/osfrobtads.h
index 5c9bcd8..d503a7b 100644
--- a/engines/glk/tads/osfrobtads.h
+++ b/engines/glk/tads/osfrobtads.h
@@ -168,7 +168,7 @@ int osfmode( const char* fname, int follow_links, unsigned long* mode,
#define osrealloc realloc
/* Open text file for reading. */
-#define osfoprt(fname,typ) (fopen((fname),"r"))
+#define osfoprt(fname,typ) osfoprb(fname,typ)
/* Open text file for writing. */
osfildef *osfopwt(const char *fname, os_filetype_t typ);
@@ -208,7 +208,7 @@ inline osfildef *osfoprwtb(const char *fname, os_filetype_t typ);
char *osfgets(char *buf, size_t count, osfildef *fp);
/* Write a line of text to a text file. */
-#define osfputs fputs
+int osfputs(const char *str, osfildef *fp);
/* Write bytes to file. */
inline bool osfwb(osfildef *fp, const void *buf, size_t count);
diff --git a/engines/glk/tads/tads2/built_in.cpp b/engines/glk/tads/tads2/built_in.cpp
new file mode 100644
index 0000000..30c2772
--- /dev/null
+++ b/engines/glk/tads/tads2/built_in.cpp
@@ -0,0 +1,4333 @@
+/* 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/built_in.h"
+#include "glk/tads/tads2/appctx.h"
+#include "glk/tads/tads2/character_map.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/file_io.h"
+#include "glk/tads/tads2/list.h"
+#include "glk/tads/tads2/os.h"
+#include "glk/tads/tads2/run.h"
+#include "glk/tads/tads2/string_resources.h"
+#include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/os_glk.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+/* yorn - yes or no */
+void bifyon(bifcxdef *ctx, int argc)
+{
+ char rsp[128];
+ char *p;
+ runsdef val;
+ char yesbuf[64];
+ char nobuf[64];
+ re_context rectx;
+ int match_yes;
+ int match_no;
+
+ bifcntargs(ctx, 0, argc); /* check for proper argument count */
+
+ /* load the "yes" and "no" reply patterns */
+ if (os_get_str_rsc(RESID_YORN_YES, yesbuf, sizeof(yesbuf)))
+ strcpy(yesbuf, "[Yy].*");
+ if (os_get_str_rsc(RESID_YORN_NO, nobuf, sizeof(nobuf)))
+ strcpy(nobuf, "[Nn].*");
+
+ /* if we're in HTML mode, switch to input font */
+ if (tio_is_html_mode())
+ tioputs(ctx->bifcxtio, "<font face='TADS-Input'>");
+
+ /* ensure the prompt is displayed */
+ tioflushn(ctx->bifcxtio, 0);
+
+ /* reset count of lines since the last keyboard input */
+ tioreset(ctx->bifcxtio);
+
+ /* read a line of text */
+ if (tiogets(ctx->bifcxtio, (char *)0, rsp, (int)sizeof(rsp)))
+ runsig(ctx->bifcxrun, ERR_RUNQUIT);
+
+ /* if we're in HTML mode, close the input font tag */
+ if (tio_is_html_mode())
+ tioputs(ctx->bifcxtio, "</font>");
+
+ /* scan off leading spaces */
+ for (p = rsp ; t_isspace(*p) ; ++p) ;
+
+ /* set up our regex context */
+ re_init(&rectx, ctx->bifcxerr);
+
+ /* check for a "yes" response */
+ match_yes = re_compile_and_match(&rectx, yesbuf, strlen(yesbuf),
+ p, strlen(p));
+
+ /* check for a "no" response */
+ match_no = re_compile_and_match(&rectx, nobuf, strlen(nobuf),
+ p, strlen(p));
+
+ /* check the result */
+ if (match_yes == (int)strlen(p))
+ val.runsv.runsvnum = 1;
+ else if (match_no == (int)strlen(p))
+ val.runsv.runsvnum = 0;
+ else
+ val.runsv.runsvnum = -1;
+
+ /* delete our regex context */
+ re_delete(&rectx);
+
+ /* push the result */
+ runpush(ctx->bifcxrun, DAT_NUMBER, &val);
+}
+
+/* setfuse */
+void bifsfs(bifcxdef *ctx, int argc)
+{
+ objnum func;
+ uint tm;
+ runsdef val;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ bifcntargs(ctx, 3, argc); /* check for proper argument count */
+ func = runpopfn(ctx->bifcxrun);
+ tm = runpopnum(ctx->bifcxrun);
+ runpop(ctx->bifcxrun, &val);
+
+ /* limitation: don't allow string or list for value */
+ if (val.runstyp == DAT_LIST || val.runstyp == DAT_SSTRING)
+ runsig(ctx->bifcxrun, ERR_FUSEVAL);
+
+ vocsetfd(voc, voc->voccxfus, func, (prpnum)0,
+ tm, &val, ERR_MANYFUS);
+}
+
+/* remfuse */
+void bifrfs(bifcxdef *ctx, int argc)
+{
+ objnum func;
+ runsdef val;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ bifcntargs(ctx, 2, argc);
+ func = runpopfn(ctx->bifcxrun);
+ runpop(ctx->bifcxrun, &val);
+ vocremfd(voc, voc->voccxfus, func, (prpnum)0,
+ &val, ERR_NOFUSE);
+}
+
+/* setdaemon */
+void bifsdm(bifcxdef *ctx, int argc)
+{
+ objnum func;
+ runsdef val;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ bifcntargs(ctx, 2, argc); /* check for proper argument count */
+ func = runpopfn(ctx->bifcxrun);
+ runpop(ctx->bifcxrun, &val);
+
+ /* limitation: don't allow string or list for value */
+ if (val.runstyp == DAT_LIST || val.runstyp == DAT_SSTRING)
+ runsig(ctx->bifcxrun, ERR_FUSEVAL);
+
+ vocsetfd(voc, voc->voccxdmn, func, (prpnum)0, 0,
+ &val, ERR_MANYDMN);
+}
+
+/* remdaemon */
+void bifrdm(bifcxdef *ctx, int argc)
+{
+ objnum func;
+ runsdef val;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ bifcntargs(ctx, 2, argc);
+ func = runpopfn(ctx->bifcxrun);
+ runpop(ctx->bifcxrun, &val);
+ vocremfd(voc, voc->voccxdmn, func, (prpnum)0,
+ &val, ERR_NODMN);
+}
+
+/* incturn */
+void bifinc(bifcxdef *ctx, int argc)
+{
+ int turncnt;
+
+ if (argc == 1)
+ {
+ /* get the number of turns to skip */
+ turncnt = runpopnum(ctx->bifcxrun);
+ if (turncnt < 1)
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "incturn");
+ }
+ else
+ {
+ /* no arguments -> increment by one turn */
+ bifcntargs(ctx, 0, argc);
+ turncnt = 1;
+ }
+
+ /* skip the given number of turns */
+ vocturn(ctx->bifcxrun->runcxvoc, turncnt, TRUE);
+}
+
+/* skipturn */
+void bifskt(bifcxdef *ctx, int argc)
+{
+ int turncnt;
+
+ bifcntargs(ctx, 1, argc);
+ turncnt = runpopnum(ctx->bifcxrun);
+ if (turncnt < 1)
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "skipturn");
+ vocturn(ctx->bifcxrun->runcxvoc, turncnt, FALSE);
+}
+
+/* quit */
+void bifqui(bifcxdef *ctx, int argc)
+{
+ /* check for proper argument count */
+ bifcntargs(ctx, 0, argc);
+
+ /* flush output buffer, and signal the end of the game */
+ tioflush(ctx->bifcxtio);
+ errsig(ctx->bifcxerr, ERR_RUNQUIT);
+}
+
+/* internal function to convert a TADS string into a C-string */
+static void bifcstr(bifcxdef *ctx, char *buf, size_t bufsiz, uchar *str)
+{
+ size_t srcrem;
+ size_t dstrem;
+ uchar *src;
+ char *dst;
+
+ /* get the length and text portion of the string */
+ srcrem = osrp2(str) - 2;
+ str += 2;
+
+ /* scan the string, and convert escapes */
+ for (src = str, dst = buf, dstrem = bufsiz ;
+ srcrem != 0 && dstrem != 0 ; ++src, --srcrem)
+ {
+ /* if we have an escape sequence, convert it */
+ if (*src == '\\')
+ {
+ /* skip the backslash in the input */
+ ++src;
+ --srcrem;
+
+ /* if there's nothing left, store the backslash */
+ if (srcrem == 0)
+ {
+ /* store the backslash */
+ *dst++ = '\\';
+ --dstrem;
+
+ /* there's nothing left to scan */
+ break;
+ }
+
+ /* see what the second half of the escape sequence is */
+ switch(*src)
+ {
+ case 'n':
+ /* store a C-style newline character */
+ *dst++ = '\n';
+ --dstrem;
+ break;
+
+ case 't':
+ /* store a C-style tab */
+ *dst++ = '\t';
+ --dstrem;
+ break;
+
+ case '(':
+ case ')':
+ /* entirely omit the highlighting sequences */
+ break;
+
+ default:
+ /* store everything else unchanged */
+ *dst++ = *src;
+ --dstrem;
+ break;
+ }
+ }
+ else
+ {
+ /* copy this character unchanged */
+ *dst++ = *src;
+ --dstrem;
+ }
+ }
+
+ /* if the buffer wasn't big enough, signal an error */
+ if (dstrem == 0)
+ runsig(ctx->bifcxrun, ERR_BIFCSTR);
+
+ /* null-terminate the result string */
+ *dst = '\0';
+}
+
+/* save */
+void bifsav(bifcxdef *ctx, int argc)
+{
+ uchar *fn;
+ char buf[OSFNMAX];
+ int err;
+ runsdef val;
+
+ bifcntargs(ctx, 1, argc);
+ fn = runpopstr(ctx->bifcxrun);
+ bifcstr(ctx, buf, (size_t)sizeof(buf), fn);
+ os_defext(buf, ctx->bifcxsavext != 0 ? ctx->bifcxsavext : "sav");
+ err = fiosav(ctx->bifcxrun->runcxvoc, buf, ctx->bifcxrun->runcxgamename);
+ runpush(ctx->bifcxrun, runclog(err), &val);
+}
+
+/* restore */
+void bifrso(bifcxdef *ctx, int argc)
+{
+ uchar *fn;
+ char buf[OSFNMAX];
+ int err;
+ voccxdef *vctx = ctx->bifcxrun->runcxvoc;
+
+ bifcntargs(ctx, 1, argc);
+
+ /* check for special restore(nil) - restore game given as parameter */
+ if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+ {
+ /* get filename from startup parameter, if any */
+ if (!os_paramfile(buf))
+ {
+ /* no startup parameter */
+ runpnum(ctx->bifcxrun, FIORSO_NO_PARAM_FILE);
+ return;
+ }
+ }
+ else
+ {
+ /* get string parameter - it's the filename */
+ fn = runpopstr(ctx->bifcxrun);
+ bifcstr(ctx, buf, (size_t)sizeof(buf), fn);
+ os_defext(buf, ctx->bifcxsavext != 0 ? ctx->bifcxsavext : "sav");
+ }
+
+ /* try restoring the file */
+ err = fiorso(vctx, buf);
+
+ /* blow away all undo records */
+ objulose(vctx->voccxundo);
+
+ /* return the result code from fiorso */
+ runpnum(ctx->bifcxrun, err);
+
+ /* note that the rest of the command line is to be ignored */
+ vctx->voccxflg |= VOCCXFCLEAR;
+}
+
+/* logging */
+void biflog(bifcxdef *ctx, int argc)
+{
+ char buf[OSFNMAX];
+ uchar *str;
+
+ bifcntargs(ctx, 1, argc);
+ if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+ {
+ rundisc(ctx->bifcxrun);
+ tiologcls(ctx->bifcxtio);
+ }
+ else
+ {
+ str = runpopstr(ctx->bifcxrun);
+ bifcstr(ctx, buf, (size_t)sizeof(buf), str);
+ tiologopn(ctx->bifcxtio, buf);
+ }
+}
+
+/* restart */
+void bifres(bifcxdef *ctx, int argc)
+{
+ voccxdef *vctx = ctx->bifcxrun->runcxvoc;
+ objnum fn;
+
+ if (argc == 2)
+ fn = runpopfn(ctx->bifcxrun); /* get function if present */
+ else
+ {
+ bifcntargs(ctx, 0, argc); /* check for proper argument count */
+ fn = MCMONINV; /* no function was specified */
+ }
+
+ objulose(vctx->voccxundo); /* blow away all undo records */
+ vocrevert(vctx); /* revert all objects to original state */
+ vocdmnclr(vctx); /* clear out fuses/deamons/notifiers */
+
+ /* restore the original "Me" object */
+ vctx->voccxme = vctx->voccxme_init;
+
+ /* call preinit if necessary (call it before invoking the user callback) */
+ if (vctx->voccxpreinit != MCMONINV)
+ runfn(ctx->bifcxrun, vctx->voccxpreinit, 0);
+
+ /*
+ * If a restart function was provided, call it. Note that we left
+ * the argument for the function on the stack, so there's no need to
+ * re-push it!
+ */
+ if (fn != MCMONINV) runfn(ctx->bifcxrun, fn, 1);
+
+ /* restart the game */
+ errsig(ctx->bifcxerr, ERR_RUNRESTART);
+}
+
+/* input - get a line of input from the keyboard */
+void bifinp(bifcxdef *ctx, int argc)
+{
+ char inbuf[128];
+
+ /* check for proper argument count */
+ bifcntargs(ctx, 0, argc);
+
+ /* make sure the prompt is displayed */
+ tioflushn(ctx->bifcxtio, 0);
+
+ /* reset count of lines since the last keyboard input */
+ tioreset(ctx->bifcxtio);
+
+ /* read a line of text */
+ if (tiogets(ctx->bifcxtio, (char *)0, inbuf, (int)sizeof(inbuf)))
+ runsig(ctx->bifcxrun, ERR_RUNQUIT);
+
+ /* push the string, converting escapes */
+ runpushcstr(ctx->bifcxrun, inbuf, strlen(inbuf), 0);
+}
+
+/* notify */
+void bifnfy(bifcxdef *ctx, int argc)
+{
+ objnum objn;
+ prpnum prp;
+ uint tm;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ bifcntargs(ctx, 3, argc); /* check for proper argument count */
+ objn = runpopobj(ctx->bifcxrun);
+ prp = runpopprp(ctx->bifcxrun);
+ tm = runpopnum(ctx->bifcxrun);
+
+ /* a time of zero means every turn */
+ if (tm == 0)
+ tm = VOCDTIM_EACH_TURN;
+
+ vocsetfd(voc, voc->voccxalm, objn, prp, tm,
+ (runsdef *)0, ERR_MANYNFY);
+}
+
+
+/* unnotify */
+void bifunn(bifcxdef *ctx, int argc)
+{
+ objnum objn;
+ prpnum prop;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ bifcntargs(ctx, 2, argc);
+ objn = runpopobj(ctx->bifcxrun);
+ prop = runpopprp(ctx->bifcxrun);
+ vocremfd(voc, voc->voccxalm, objn, prop,
+ (runsdef *)0, ERR_NONFY);
+}
+
+/* trace on/off */
+void biftrc(bifcxdef *ctx, int argc)
+{
+ runsdef val;
+ int n;
+ int flag;
+
+ if (argc == 2)
+ {
+ /* get the type indicator and the on/off status */
+ n = runpopnum(ctx->bifcxrun);
+ flag = runpoplog(ctx->bifcxrun);
+
+ /* see what type of debugging they want to turn on or off */
+ switch(n)
+ {
+ case 1:
+ /* turn on parser tracing */
+ if (flag)
+ ctx->bifcxrun->runcxvoc->voccxflg |= VOCCXFDBG;
+ else
+ ctx->bifcxrun->runcxvoc->voccxflg &= ~VOCCXFDBG;
+ break;
+
+ default:
+ /* ignore other requests */
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "debugTrace");
+ }
+ }
+ else
+ {
+ /* break into debugger; return whether debugger is present */
+ bifcntargs(ctx, 0, argc);
+ runpush(ctx->bifcxrun, runclog(dbgstart(ctx->bifcxrun->runcxdbg)),
+ &val);
+ }
+}
+
+/* say */
+void bifsay(bifcxdef *ctx, int argc)
+{
+ uchar *str;
+ long num;
+ char numbuf[30];
+
+ if (argc != 2) bifcntargs(ctx, 1, argc);
+
+ switch(runtostyp(ctx->bifcxrun))
+ {
+ case DAT_NUMBER:
+ num = runpopnum(ctx->bifcxrun);
+ sprintf(numbuf, "%ld", num);
+ tioputs(ctx->bifcxtio, numbuf);
+ break;
+
+ case DAT_SSTRING:
+ str = runpopstr(ctx->bifcxrun);
+ outfmt(ctx->bifcxtio, str);
+ break;
+
+ case DAT_NIL:
+ (void)runpoplog(ctx->bifcxrun);
+ break;
+
+ default:
+ runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "say");
+ }
+}
+
+/* car */
+void bifcar(bifcxdef *ctx, int argc)
+{
+ uchar *lstp;
+ uint lstsiz;
+ runsdef val;
+
+ bifcntargs(ctx, 1, argc);
+ bifchkarg(ctx, DAT_LIST);
+
+ lstp = runpoplst(ctx->bifcxrun);
+
+ /* get list's size, and point to its data string */
+ lstsiz = osrp2(lstp) - 2;
+ lstp += 2;
+
+ /* push first element if one is present, otherwise push nil */
+ if (lstsiz)
+ runpbuf(ctx->bifcxrun, *lstp, lstp+1);
+ else
+ runpush(ctx->bifcxrun, DAT_NIL, &val);
+}
+
+/* cdr */
+void bifcdr(bifcxdef *ctx, int argc)
+{
+ uchar *lstp;
+ uint siz;
+ uint lstsiz;
+ runsdef val;
+ runsdef stkval;
+
+ bifcntargs(ctx, 1, argc);
+ bifchkarg(ctx, DAT_LIST);
+
+ lstp = runpoplst(ctx->bifcxrun);
+ stkval.runstyp = DAT_LIST;
+ stkval.runsv.runsvstr = lstp;
+
+ /* get list's size, and point to its data string */
+ lstsiz = osrp2(lstp) - 2;
+ lstp += 2;
+
+ if (lstsiz != 0)
+ {
+ /* deduct size of first element from size of list */
+ siz = datsiz(*lstp, lstp+1) + 1;
+ lstsiz -= siz;
+
+ /* add in the size prefix for our new list size */
+ lstsiz += 2;
+
+ /* allocate space for new list containing rest of list */
+ runhres1(ctx->bifcxrun, lstsiz, 1, &stkval);
+ lstp = stkval.runsv.runsvstr + siz + 2;
+
+ /* write out size followed by list value string */
+ oswp2(ctx->bifcxrun->runcxhp, lstsiz);
+ memcpy(ctx->bifcxrun->runcxhp+2, lstp, (size_t)(lstsiz-2));
+
+ val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+ val.runstyp = DAT_LIST;
+ ctx->bifcxrun->runcxhp += lstsiz;
+ runrepush(ctx->bifcxrun, &val);
+ }
+ else
+ runpush(ctx->bifcxrun, DAT_NIL, &val); /* empty list - cdr is nil */
+}
+
+/* caps */
+void bifcap(bifcxdef *ctx, int argc)
+{
+ bifcntargs(ctx, 0, argc);
+ tiocaps(ctx->bifxtio); /* set output driver next-char-capitalized flag */
+}
+
+/* nocaps */
+void bifnoc(bifcxdef *ctx, int argc)
+{
+ bifcntargs(ctx, 0, argc);
+ tionocaps(ctx->bifxtio); /* set next-not-capitalized flag */
+}
+
+/* length */
+void biflen(bifcxdef *ctx, int argc)
+{
+ uchar *p;
+ runsdef val;
+ long len = 0;
+ int l;
+
+ bifcntargs(ctx, 1, argc);
+ switch(runtostyp(ctx->bifcxrun))
+ {
+ case DAT_SSTRING:
+ p = (uchar *)runpopstr(ctx->bifcxrun);
+ len = osrp2(p) - 2;
+ break;
+
+ case DAT_LIST:
+ p = runpoplst(ctx->bifcxrun);
+ l = osrp2(p) - 2;
+ p += 2;
+
+ /* count all elements in list */
+ for (len = 0 ; l ; ++len)
+ {
+ int cursiz;
+
+ /* get size of this element, and move past it */
+ cursiz = datsiz(*p, p+1) + 1;
+ l -= cursiz;
+ p += cursiz;
+ }
+ break;
+
+ default:
+ runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "length");
+ }
+
+ val.runsv.runsvnum = len;
+ runpush(ctx->bifcxrun, DAT_NUMBER, &val);
+}
+
+/* find */
+void biffnd(bifcxdef *ctx, int argc)
+{
+ uchar *p1, *p2;
+ int len1, len2;
+ int outv = 0;
+ runsdef val;
+ int typ = 0;
+ int siz;
+
+ bifcntargs(ctx, 2, argc);
+ switch(runtostyp(ctx->bifcxrun))
+ {
+ case DAT_SSTRING:
+ p1 = runpopstr(ctx->bifcxrun);
+ len1 = osrp2(p1) - 2;
+ p1 += 2;
+
+ p2 = runpopstr(ctx->bifcxrun);
+ len2 = osrp2(p2) - 2;
+ p2 += 2;
+
+ /* look for p2 within p1 */
+ for (typ = DAT_NIL, outv = 1 ; len1 >= len2 ; ++p1, --len1, ++outv)
+ {
+ if (!memcmp(p1, p2, (size_t)len2))
+ {
+ typ = DAT_NUMBER; /* use number in outv after all */
+ break; /* that's it - we've found it */
+ }
+ }
+ break;
+
+ case DAT_LIST:
+ p1 = runpoplst(ctx->bifcxrun);
+ len1 = osrp2(p1) - 2;
+ p1 += 2;
+
+ /* get second item: any old datatype */
+ runpop(ctx->bifcxrun, &val);
+
+ for (typ = DAT_NIL, outv = 1 ; len1 ; ++outv, p1 += siz, len1 -= siz)
+ {
+ siz = datsiz(*p1, p1 + 1) + 1; /* get size of this element */
+ if (val.runstyp != *p1) continue; /* types don't match */
+
+ switch(val.runstyp)
+ {
+ case DAT_NUMBER:
+ if (val.runsv.runsvnum != osrp4s(p1 + 1)) continue;
+ break;
+
+ case DAT_SSTRING:
+ case DAT_LIST:
+ if (osrp2(p1 + 1) != osrp2(val.runsv.runsvstr) ||
+ memcmp(p1 + 3, val.runsv.runsvstr + 2,
+ (size_t)(osrp2(p1 + 1) - 2)))
+ continue;
+ break;
+
+ case DAT_PROPNUM:
+ if (osrp2(p1 + 1) != val.runsv.runsvprp) continue;
+ break;
+
+ case DAT_OBJECT:
+ case DAT_FNADDR:
+ if (osrp2(p1 + 1) != val.runsv.runsvobj) continue;
+ break;
+
+ default:
+ break;
+ }
+
+ /* if we got here, it means we found a match */
+ typ = DAT_NUMBER; /* use the value in outv */
+ break; /* that's it - we've found it */
+ }
+ break;
+
+ default:
+ runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "find");
+ }
+
+ /* push the value given by typ and outv */
+ val.runsv.runsvnum = outv;
+ runpush(ctx->bifcxrun, typ, &val);
+}
+
+/* setit - set current 'it' */
+void bifsit(bifcxdef *ctx, int argc)
+{
+ objnum obj;
+ int typ;
+ voccxdef *vcx = ctx->bifcxrun->runcxvoc;
+
+ /* check for extended version that allows setting him/her */
+ if (argc == 2)
+ {
+ if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+ {
+ rundisc(ctx->bifcxrun); /* discard the nil */
+ obj = MCMONINV; /* use invalid object */
+ }
+ else
+ obj = runpopobj(ctx->bifcxrun); /* get the object */
+
+ typ = runpopnum(ctx->bifcxrun); /* get the code */
+ vcx->voccxthc = 0; /* clear the 'them' list */
+
+ switch(typ)
+ {
+ case 0: /* set "it" */
+ vcx->voccxit = obj;
+ break;
+
+ case 1: /* set "him" */
+ vcx->voccxhim = obj;
+ break;
+
+ case 2: /* set "her" */
+ vcx->voccxher = obj;
+ break;
+ }
+ return;
+ }
+
+ /* "setit classic" has one argument only */
+ bifcntargs(ctx, 1, argc);
+
+ /* check to see if we're setting 'it' or 'them' */
+ if (runtostyp(ctx->bifcxrun) == DAT_LIST)
+ {
+ uchar *lst;
+ uint siz;
+ int cnt;
+
+ lst = runpoplst(ctx->bifcxrun);
+ siz = osrp2(lst);
+ lst += 2;
+ siz -= 2;
+
+ for (cnt = 0 ; siz ; )
+ {
+ /* if this is an object, add to 'them' list (otherwise ignore) */
+ if (*lst == DAT_OBJECT)
+ vcx->voccxthm[cnt++] = osrp2(lst+1);
+
+ lstadv(&lst, &siz);
+ }
+ vcx->voccxthc = cnt;
+ vcx->voccxit = MCMONINV;
+ }
+ else
+ {
+ /* set 'it', and delete 'them' list */
+ if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+ {
+ vcx->voccxit = MCMONINV;
+ rundisc(ctx->bifcxrun);
+ }
+ else
+ vcx->voccxit = runpopobj(ctx->bifcxrun);
+ vcx->voccxthc = 0;
+ }
+}
+
+/* randomize - seed random number generator */
+void bifsrn(bifcxdef *ctx, int argc)
+{
+ bifcntargs(ctx, 0, argc);
+ os_rand(&ctx->bifcxrnd);
+ ctx->bifcxrndset = TRUE;
+}
+
+/* rand - get a random number */
+void bifrnd(bifcxdef *ctx, int argc)
+{
+ unsigned long result, max, randseed;
+ int tmp;
+ runsdef val;
+
+ /* get argument - number giving upper bound of generated number */
+ bifcntargs(ctx, 1, argc);
+ bifchkarg(ctx, DAT_NUMBER);
+ max = runpopnum(ctx->bifcxrun);
+
+ /* if the max is zero, just return zero */
+ if (max == 0)
+ {
+ runpnum(ctx->bifcxrun, 0);
+ return;
+ }
+
+ /*
+ * If the random number generator has been seeded by a call to
+ * randomize(), use the new, improved random number generator. If
+ * not, use the old random number generator to ensure that the same
+ * sequence of numbers is generated as always (to prevent breaking
+ * existing test scripts based on the old sequence).
+ */
+ if (!ctx->bifcxrndset)
+ {
+ /* compute the next number in sequence, using old cheesy generator */
+ randseed = ctx->bifcxrnd;
+ randseed *= 1033;
+ randseed += 5;
+ tmp = randseed / 16384;
+ randseed %= 16384;
+ result = tmp / 7;
+
+ /* adjust the result to be in the requested range */
+ result = ( randseed % max ) + 1;
+
+ /* save the new seed value, and return the value */
+ ctx->bifcxrnd = randseed;
+ val.runsv.runsvnum = result;
+ runpush(ctx->bifcxrun, DAT_NUMBER, &val);
+ }
+ else
+ {
+#define BIF_RAND_M ((ulong)2147483647)
+#define BIF_RAND_Q ((ulong)127773)
+#define BIF_RAND_A ((ulong)16807)
+#define BIF_RAND_R ((ulong)2836)
+
+ long lo, hi, test;
+
+ lo = ctx->bifcxrnd / BIF_RAND_Q;
+ hi = ctx->bifcxrnd % BIF_RAND_Q;
+ test = BIF_RAND_A*lo - BIF_RAND_R*hi;
+ ctx->bifcxrnd = test;
+ if (test > 0)
+ ctx->bifcxrnd = test;
+ else
+ ctx->bifcxrnd = test + BIF_RAND_M;
+ runpnum(ctx->bifcxrun, (((ulong)ctx->bifcxrnd) % max) + 1);
+ }
+}
+
+/*
+ * case-insensitive substring matching
+ */
+static char *bif_stristr(const char *s1, const char *s2)
+{
+ size_t s1len;
+ size_t s2len;
+
+ /* scan for a match */
+ for (s1len = strlen(s1), s2len = strlen(s2) ; s1len >= s2len ;
+ ++s1, --s1len)
+ {
+ /* if this is a match, return this substring */
+ if (memicmp(s1, s2, s2len) == 0)
+ return (char *)s1;
+ }
+
+ return 0;
+}
+
+/*
+ * askfile flags
+ */
+#define BIF_ASKF_EXT_RET 1 /* extended return codes */
+
+/*
+ * askfile
+ */
+void bifask(bifcxdef *ctx, int argc)
+{
+ uchar *prompt;
+ char buf[OSFNMAX + 2];
+ char pbuf[128];
+ int err;
+ int prompt_type;
+ int file_type;
+ ulong flags;
+
+ /* make sure we have an acceptable number of arguments */
+ if (argc != 1 && argc != 3 && argc != 4)
+ runsig(ctx->bifcxrun, ERR_BIFARGC);
+
+ /* get the first argument - the prompt string */
+ prompt = runpopstr(ctx->bifcxrun);
+ bifcstr(ctx, pbuf, (size_t)sizeof(pbuf), prompt);
+
+ /* presume we will have no flags */
+ flags = 0;
+
+ /* if we have the prompt type and file type parameters, get them */
+ if (argc >= 3)
+ {
+ /* get the prompt-type and the file-type arguments */
+ prompt_type = (int)runpopnum(ctx->bifcxrun);
+ file_type = (int)runpopnum(ctx->bifcxrun);
+
+ /* if we have a fourth argument, it's the flags */
+ if (argc == 4)
+ flags = runpopnum(ctx->bifcxrun);
+ }
+ else
+ {
+ static const char *save_strs[] =
+ {
+ "save",
+ "write",
+ 0
+ };
+ static const char *game_strs[] =
+ {
+ "restore",
+ "game",
+ 0
+ };
+ const char **sp;
+
+ /*
+ * No prompt type or file type were specified. Try to infer the
+ * dialog type and file type from the text of the prompt. (This
+ * is mostly to support older games, in particular those based
+ * on older versions of adv.t, since newer games should always
+ * provide explicit values for the file type and dialog type.
+ * We are thus inferring the types based on the prompt strings
+ * that older adv.t's used when calling askfile.)
+ *
+ * If the prompt contains any substring such as "save" or
+ * "write", specify that we're saving; otherwise, assume that
+ * we're opening an existing file for reading.
+ *
+ * If the prompt contains the substrings "restore" AND "game",
+ * assume that we're opening a game file; otherwise, don't make
+ * any assumptions, and use the "unknown" file type.
+ */
+
+ /* presume we're going to open a saved-game file */
+ prompt_type = OS_AFP_OPEN;
+ file_type = OSFTSAVE;
+
+ /* look for any one of the "save" substrings */
+ for (sp = save_strs ; *sp != 0 ; ++sp)
+ {
+ /* check to see if this substring matches */
+ if (bif_stristr(pbuf, *sp))
+ {
+ /* found it - use the "save" prompt */
+ prompt_type = OS_AFP_SAVE;
+
+ /* no need to look any further */
+ break;
+ }
+ }
+
+ /*
+ * look for *all* of the "restore game" strings - if we fail to
+ * find any of them, be conservative and make no assumptions
+ * about the file type
+ */
+ for (sp = game_strs ; *sp != 0 ; ++sp)
+ {
+ if (bif_stristr(pbuf, *sp) == 0)
+ {
+ /*
+ * this one doesn't match - don't make assumptions about
+ * the file type
+ */
+ file_type = OSFTUNK;
+
+ /* no need to look any further */
+ break;
+ }
+ }
+
+ /* check for a transcript */
+ if (file_type == OSFTUNK
+ && prompt_type == OS_AFP_SAVE
+ && bif_stristr(pbuf, "script") != 0)
+ {
+ /* looks like a log file */
+ file_type = OSFTLOG;
+ }
+ }
+
+ /* ask for a file */
+ err = tio_askfile(pbuf, buf, (int)sizeof(buf), prompt_type, (os_filetype_t)file_type);
+
+ /*
+ * if the caller requested extended return codes, return a list
+ * containing the return code as the first element and, if
+ * successful, the string as the second element
+ */
+ if ((flags & BIF_ASKF_EXT_RET) != 0)
+ {
+ ushort len;
+ runsdef val;
+ uchar *p;
+
+ /*
+ * Allocate space for the starter list - if we have a string to
+ * return, just allocate space for the number element for now;
+ * otherwise, allocate space for the number plus a nil second
+ * element (one byte).
+ */
+ len = 2 + (1 + 4);
+ if (err != OS_AFE_SUCCESS)
+ ++len;
+
+ /* allocate the space */
+ runhres(ctx->bifcxrun, len, 0);
+
+ /* set up our list pointer */
+ val.runstyp = DAT_LIST;
+ val.runsv.runsvstr = p = ctx->bifcxrun->runcxhp;
+
+ /* write the length prefix */
+ oswp2(p, len);
+ p += 2;
+
+ /* write the return code as the first element */
+ *p++ = DAT_NUMBER;
+ oswp4s(p, err);
+ p += 4;
+
+ /* write the 'nil' second element if there's an error */
+ if (err != OS_AFE_SUCCESS)
+ *p++ = DAT_NIL;
+
+ /* commit the list's memory */
+ ctx->bifcxrun->runcxhp = p;
+
+ /* push the list */
+ runrepush(ctx->bifcxrun, &val);
+
+ /* if we were successful, add the string to the list */
+ if (err == OS_AFE_SUCCESS)
+ {
+ runsdef val2;
+
+ /* push the string value, converting to our string format */
+ runpushcstr(ctx->bifcxrun, buf, strlen(buf), 1);
+
+ /* add it to the list already on the stack */
+ runpop(ctx->bifcxrun, &val2);
+ runpop(ctx->bifcxrun, &val);
+ runadd(ctx->bifcxrun, &val, &val2, 2);
+
+ /* re-push the result */
+ runrepush(ctx->bifcxrun, &val);
+ }
+ }
+ else
+ {
+ /*
+ * use the traditional return codes - if askfile failed, return
+ * nil; otherwise, return the filename
+ */
+ if (err)
+ runpnil(ctx->bifcxrun);
+ else
+ runpushcstr(ctx->bifcxrun, buf, strlen(buf), 0);
+ }
+}
+
+/* setscore */
+void bifssc(bifcxdef *ctx, int argc)
+{
+ int s1, s2;
+
+ /* optional new way - string argument */
+ if (argc == 1 && runtostyp(ctx->bifcxrun) == DAT_SSTRING)
+ {
+ char buf[80];
+ uchar *p;
+
+ p = runpopstr(ctx->bifcxrun);
+ bifcstr(ctx, buf, (size_t)sizeof(buf), p);
+ tiostrsc(ctx->bifcxtio, buf);
+ }
+ else
+ {
+ /* old way - two numeric arguments (displays: x/y) */
+ bifcntargs(ctx, 2, argc);
+ s1 = runpopnum(ctx->bifcxrun);
+ s2 = runpopnum(ctx->bifcxrun);
+ tioscore(ctx->bifcxtio, s1, s2);
+ }
+}
+
+/* substr */
+void bifsub(bifcxdef *ctx, int argc)
+{
+ uchar *p;
+ int ofs;
+ int asklen;
+ int outlen;
+ int len;
+
+ bifcntargs(ctx, 3, argc);
+
+ /* get the string argument */
+ bifchkarg(ctx, DAT_SSTRING);
+ p = runpopstr(ctx->bifcxrun);
+ len = osrp2(p) - 2;
+ p += 2;
+
+ /* get the offset argument */
+ bifchkarg(ctx, DAT_NUMBER);
+ ofs = runpopnum(ctx->bifcxrun);
+ if (ofs < 1) runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "substr");
+
+ /* get the length argument */
+ bifchkarg(ctx, DAT_NUMBER);
+ asklen = runpopnum(ctx->bifcxrun);
+ if (asklen < 0) runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "substr");
+
+ --ofs; /* convert offset to a zero bias (user provided 1-bias) */
+ p += ofs; /* advance string pointer by offset */
+
+ if (ofs >= len)
+ outlen = 0; /* offset is past end of string */
+ else if (asklen > len - ofs)
+ outlen = len - ofs; /* just use rest of string */
+ else
+ outlen = asklen; /* requested length can be provided */
+
+ runpstr(ctx->bifcxrun, (char *)p, outlen, 3);
+}
+
+/* cvtstr - convert value to a string */
+void bifcvs(bifcxdef *ctx, int argc)
+{
+ char *p;
+ int len;
+ char buf[30];
+
+ bifcntargs(ctx, 1, argc);
+ switch(runtostyp(ctx->bifcxrun))
+ {
+ case DAT_NIL:
+ p = "nil";
+ len = 3;
+ (void)runpoplog(ctx->bifcxrun);
+ break;
+
+ case DAT_TRUE:
+ p = "true";
+ len = 4;
+ (void)runpoplog(ctx->bifcxrun);
+ break;
+
+ case DAT_NUMBER:
+ sprintf(buf, "%ld", runpopnum(ctx->bifcxrun));
+ p = buf;
+ len = strlen(buf);
+ break;
+
+ case DAT_SSTRING:
+ /* leave the string value on the stack unchanged */
+ return;
+
+ default:
+ /* throw the RUNEXITOBJ error */
+ runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "cvtstr");
+ }
+
+ runpstr(ctx->bifcxrun, p, len, 0);
+}
+
+/* cvtnum - convert a value to a number */
+void bifcvn(bifcxdef *ctx, int argc)
+{
+ runsdef val;
+ uchar *p;
+ int len;
+ int typ;
+ long acc;
+ int neg;
+
+ bifcntargs(ctx, 1, argc);
+ p = runpopstr(ctx->bifcxrun);
+ len = osrp2(p) - 2;
+ p += 2;
+
+ if (len == 3 && !memcmp(p, "nil", (size_t)3))
+ typ = DAT_NIL;
+ else if (len == 4 && !memcmp(p, "true", (size_t)4))
+ typ = DAT_TRUE;
+ else
+ {
+ typ = DAT_NUMBER;
+ for ( ; len != 0 && t_isspace(*p) ; ++p, --len) ;
+ if (len != 0 && *p == '-')
+ {
+ neg = TRUE;
+ for (++p, --len ; len != 0 && t_isspace(*p) ; ++p, --len) ;
+ }
+ else neg = FALSE;
+
+ /* accumulate the number digit by digit */
+ for (acc = 0 ; len != 0 && Common::isDigit(*p) ; ++p, --len)
+ acc = (acc << 3) + (acc << 1) + ((*p) - '0');
+
+ if (neg) acc = -acc;
+ val.runsv.runsvnum = acc;
+ }
+
+ runpush(ctx->bifcxrun, typ, &val);
+}
+
+/* general string conversion function */
+static void bifcvtstr(bifcxdef *ctx, void (*cvtfn)(uchar *, int), int argc)
+{
+ uchar *p;
+ int len;
+ runsdef val;
+ runsdef stkval;
+
+ bifcntargs(ctx, 1, argc);
+ bifchkarg(ctx, DAT_SSTRING);
+
+ p = runpopstr(ctx->bifcxrun);
+ stkval.runstyp = DAT_SSTRING;
+ stkval.runsv.runsvstr = p;
+ len = osrp2(p);
+
+ /* allocate space in heap for the string and convert */
+ runhres1(ctx->bifcxrun, len, 1, &stkval);
+ p = stkval.runsv.runsvstr;
+ memcpy(ctx->bifcxrun->runcxhp, p, (size_t)len);
+ (*cvtfn)(ctx->bifcxrun->runcxhp + 2, len - 2);
+
+ val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+ val.runstyp = DAT_SSTRING;
+ ctx->bifcxrun->runcxhp += len;
+ runrepush(ctx->bifcxrun, &val);
+}
+
+/* routine to convert a counted-length string to uppercase */
+static void bifstrupr(uchar *str, int len)
+{
+ for ( ; len ; --len, ++str)
+ {
+ if (*str == '\\' && len > 1)
+ --len, ++str;
+ else if (Common::isLower(*str))
+ *str = toupper(*str);
+ }
+}
+
+/* upper */
+void bifupr(bifcxdef *ctx, int argc)
+{
+ bifcvtstr(ctx, bifstrupr, argc);
+}
+
+/* convert a counted-length string to lowercase */
+static void bifstrlwr(uchar *str, int len)
+{
+ for ( ; len ; --len, ++str)
+ {
+ if (*str == '\\' && len > 1)
+ --len, ++str;
+ else if (Common::isUpper(*str))
+ *str = tolower(*str);
+ }
+}
+
+/* lower */
+void biflwr(bifcxdef *ctx, int argc)
+{
+ bifcvtstr(ctx, bifstrlwr, argc);
+}
+
+/* internal check to determine if object is of a class */
+int bifinh(voccxdef *voc, vocidef *v, objnum cls)
+{
+ int i;
+ objnum *sc;
+
+ if (!v) return(FALSE);
+ for (i = v->vocinsc, sc = v->vocisc ; i ; ++sc, --i)
+ {
+ if (*sc == cls
+ || bifinh(voc, vocinh(voc, *sc), cls))
+ return(TRUE);
+ }
+ return(FALSE);
+}
+
+/* isclass(obj, cls) */
+void bifisc(bifcxdef *ctx, int argc)
+{
+ objnum obj;
+ objnum cls;
+ runsdef val;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ bifcntargs(ctx, 2, argc);
+
+ /* if checking for nil, return nil */
+ if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+ {
+ rundisc(ctx->bifcxrun);
+ rundisc(ctx->bifcxrun);
+ runpnil(ctx->bifcxrun);
+ return;
+ }
+
+ /* get the arguments: object, class */
+ obj = runpopobj(ctx->bifcxrun);
+ cls = runpopobj(ctx->bifcxrun);
+
+ /* return the result from bifinh() */
+ runpush(ctx->bifcxrun, runclog(bifinh(voc, vocinh(voc, obj), cls)), &val);
+}
+
+/* firstsc(obj) - get the first superclass of an object */
+void bif1sc(bifcxdef *ctx, int argc)
+{
+ objnum obj;
+ objnum sc;
+
+ bifcntargs(ctx, 1, argc);
+ obj = runpopobj(ctx->bifcxrun);
+ sc = objget1sc(ctx->bifcxrun->runcxmem, obj);
+ runpobj(ctx->bifcxrun, sc);
+}
+
+/* firstobj */
+void biffob(bifcxdef *ctx, int argc)
+{
+ vocidef ***vpg;
+ vocidef **v;
+ objnum obj;
+ int i;
+ int j;
+ objnum cls = 0;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ /* get class to search for, if one is specified */
+ if (argc == 0)
+ cls = MCMONINV;
+ else if (argc == 1)
+ cls = runpopobj(ctx->bifcxrun);
+ else
+ runsig(ctx->bifcxrun, ERR_BIFARGC);
+
+ for (vpg = voc->voccxinh, i = 0 ; i < VOCINHMAX ; ++vpg, ++i)
+ {
+ if (!*vpg) continue;
+ for (v = *vpg, obj = (i << 8), j = 0 ; j < 256 ; ++v, ++obj, ++j)
+ {
+ if (!*v || ((*v)->vociflg & VOCIFCLASS)
+ || (cls != MCMONINV && !bifinh(voc, *v, cls)))
+ continue;
+
+ /* this is an object we can use - push it */
+ runpobj(ctx->bifcxrun, obj);
+ return;
+ }
+ }
+
+ /* no objects found at all - return nil */
+ runpnil(ctx->bifcxrun);
+}
+
+/* nextobj */
+void bifnob(bifcxdef *ctx, int argc)
+{
+ objnum prv;
+ vocidef ***vpg;
+ vocidef **v;
+ objnum obj;
+ int i;
+ int j;
+ objnum cls = 0;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ /* get last position in search */
+ prv = runpopobj(ctx->bifcxrun);
+
+ /* get class to search for, if one is specified */
+ if (argc == 1)
+ cls = MCMONINV;
+ else if (argc == 2)
+ cls = runpopobj(ctx->bifcxrun);
+ else
+ runsig(ctx->bifcxrun, ERR_BIFARGC);
+
+ /* start at previous object plus 1 */
+ i = (prv >> 8);
+ vpg = voc->voccxinh + i;
+ j = (prv & 255);
+ obj = prv;
+ v = (*vpg) + j;
+
+ for (;;)
+ {
+ ++j;
+ ++obj;
+ ++v;
+ if (j == 256)
+ {
+ j = 0;
+ ++i;
+ ++vpg;
+ if (!*vpg)
+ {
+ obj += 255;
+ j += 255;
+ continue;
+ }
+ v = (*vpg);
+ }
+ if (i >= VOCINHMAX)
+ {
+ runpnil(ctx->bifcxrun);
+ return;
+ }
+
+ if (!*v || ((*v)->vociflg & VOCIFCLASS)
+ || (cls != MCMONINV && !bifinh(voc, *v, cls)))
+ continue;
+
+ /* this is an object we can use - push it */
+ runpobj(ctx->bifcxrun, obj);
+ return;
+ }
+}
+
+/* setversion */
+void bifsvn(bifcxdef *ctx, int argc)
+{
+ bifcntargs(ctx, 1, argc);
+ (void)runpopstr(ctx->bifcxrun);
+ /* note - setversion doesn't do anything in v2; uses timestamp instead */
+}
+
+/* getarg */
+void bifarg(bifcxdef *ctx, int argc)
+{
+ int argnum;
+
+ bifcntargs(ctx, 1, argc);
+ bifchkarg(ctx, DAT_NUMBER);
+
+ /* get and verify argument number */
+ argnum = runpopnum(ctx->bifcxrun);
+ if (argnum < 1) runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "getarg");
+
+ runrepush(ctx->bifcxrun, ctx->bifcxrun->runcxbp - argnum - 1);
+}
+
+/* datatype */
+void biftyp(bifcxdef *ctx, int argc)
+{
+ runsdef val;
+
+ bifcntargs(ctx, 1, argc);
+
+ /* get whatever it is, and push the type */
+ runpop(ctx->bifcxrun, &val);
+ val.runsv.runsvnum = val.runstyp; /* new value is the datatype */
+ runpush(ctx->bifcxrun, DAT_NUMBER, &val);
+}
+
+/* undo */
+void bifund(bifcxdef *ctx, int argc)
+{
+ objucxdef *ucx = ctx->bifcxrun->runcxvoc->voccxundo;
+ mcmcxdef *mcx = ctx->bifcxrun->runcxmem;
+ errcxdef *ec = ctx->bifcxerr;
+ int err;
+ int undone;
+ runsdef val;
+
+ bifcntargs(ctx, 0, argc); /* no arguments */
+
+ ERRBEGIN(ec)
+ if (ucx)
+ {
+ objundo(mcx, ucx); /* try to undo to previous savepoint */
+ undone = TRUE; /* looks like we succeeded */
+ }
+ else
+ undone = FALSE; /* no undo context; can't undo */
+ ERRCATCH(ec, err)
+ if (err == ERR_NOUNDO || err == ERR_ICUNDO)
+ undone = FALSE;
+ else
+ errrse(ec); /* don't know how to handle other errors */
+ ERREND(ec)
+
+ /* return a value indicating whether the undo operation succeeded */
+ runpush(ctx->bifcxrun, runclog(undone), &val);
+
+ /* note that the rest of the command line is to be ignored */
+ ctx->bifcxrun->runcxvoc->voccxflg |= VOCCXFCLEAR;
+}
+
+/* flags for defined() function */
+#define BIFDEF_DEFINED_ANY 1
+#define BIFDEF_DEFINED_DIRECTLY 2
+#define BIFDEF_DEFINED_INHERITS 3
+#define BIFDEF_DEFINED_GET_CLASS 4
+
+/* defined */
+void bifdef(bifcxdef *ctx, int argc)
+{
+ prpnum prpn;
+ objnum objn;
+ uint ofs;
+ runsdef val;
+ objnum def_objn;
+ int flag;
+
+ /* get object and property arguments */
+ objn = runpopobj(ctx->bifcxrun);
+ prpn = runpopprp(ctx->bifcxrun);
+
+ /* if there's a flag argument, get it as well */
+ if (argc == 3)
+ {
+ /* get the flag */
+ flag = (int)runpopnum(ctx->bifcxrun);
+ }
+ else
+ {
+ /* check the argument count */
+ bifcntargs(ctx, 2, argc);
+
+ /* use the default flag value (DEFINES_OR_INHERITS) */
+ flag = BIFDEF_DEFINED_ANY;
+ }
+
+ /* get the offset of the property and the defining object */
+ ofs = objgetap(ctx->bifcxrun->runcxmem, objn, prpn, &def_objn, FALSE);
+
+ /* determine the type of information they want */
+ switch(flag)
+ {
+ case BIFDEF_DEFINED_ANY:
+ /* if the property is defined, return true, else return nil */
+ runpush(ctx->bifcxrun, runclog(ofs != 0), &val);
+ break;
+
+ case BIFDEF_DEFINED_DIRECTLY:
+ /* if the property is defined directly by the object, return true */
+ runpush(ctx->bifcxrun, runclog(ofs != 0 && def_objn == objn), &val);
+ break;
+
+ case BIFDEF_DEFINED_INHERITS:
+ /* if the property is inherited, return true */
+ runpush(ctx->bifcxrun, runclog(ofs != 0 && def_objn != objn), &val);
+ break;
+
+ case BIFDEF_DEFINED_GET_CLASS:
+ /* if it's defined, return the defining object, otherwise nil */
+ if (ofs == 0)
+ runpnil(ctx->bifcxrun);
+ else
+ runpobj(ctx->bifcxrun, def_objn);
+ break;
+
+ default:
+ /* invalid flag value */
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "defined");
+ }
+}
+
+/* proptype */
+void bifpty(bifcxdef *ctx, int argc)
+{
+ prpnum prpn;
+ objnum objn;
+ uint ofs;
+ runsdef val;
+ objnum orn;
+ objdef *objptr;
+ prpdef *propptr;
+
+ bifcntargs(ctx, 2, argc);
+
+ /* get offset of obj.prop */
+ objn = runpopobj(ctx->bifcxrun);
+ prpn = runpopprp(ctx->bifcxrun);
+ ofs = objgetap(ctx->bifcxrun->runcxmem, objn, prpn, &orn, FALSE);
+
+ if (ofs)
+ {
+ /* lock the object, read the prpdef, and unlock it */
+ objptr = (objdef *)mcmlck(ctx->bifcxrun->runcxmem, (mcmon)orn);
+ propptr = objofsp(objptr, ofs);
+ val.runsv.runsvnum = prptype(propptr);
+ mcmunlck(ctx->bifcxrun->runcxmem, (mcmon)orn);
+ }
+ else
+ {
+ /* property is not defined by object - indicate that type is nil */
+ val.runsv.runsvnum = DAT_NIL;
+ }
+
+ /* special case: DAT_DEMAND -> DAT_LIST (for contents properties) */
+ if (val.runsv.runsvnum == DAT_DEMAND)
+ val.runsv.runsvnum = DAT_LIST;
+
+ /* return the property type as a number */
+ runpush(ctx->bifcxrun, DAT_NUMBER, &val);
+}
+
+/* outhide */
+void bifoph(bifcxdef *ctx, int argc)
+{
+ runsdef val;
+ int hidden, output_occurred;
+
+ bifcntargs(ctx, 1, argc);
+ outstat(&hidden, &output_occurred);
+ if (runtostyp(ctx->bifcxrun) == DAT_TRUE)
+ {
+ /* throw away the flag */
+ rundisc(ctx->bifcxrun);
+
+ /* figure out appropriate return value */
+ if (!hidden)
+ val.runsv.runsvnum = 0;
+ else if (!output_occurred)
+ val.runsv.runsvnum = 1;
+ else
+ val.runsv.runsvnum = 2;
+ runpush(ctx->bifcxrun, DAT_NUMBER, &val);
+
+ /* actually hide the output, resetting count flag */
+ outhide();
+ }
+ else if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+ {
+ /* throw away the flag */
+ rundisc(ctx->bifcxrun);
+
+ /* show output, returning status */
+ runpush(ctx->bifcxrun, runclog(outshow()), &val);
+ }
+ else if (runtostyp(ctx->bifcxrun) == DAT_NUMBER)
+ {
+ int n = runpopnum(ctx->bifcxrun);
+
+ if (n == 0)
+ {
+ /* output was not hidden - show output and return status */
+ runpush(ctx->bifcxrun, runclog(outshow()), &val);
+ }
+ else if (n == 1)
+ {
+ /*
+ * Output was hidden, but no output had occurred yet.
+ * Leave output hidden and return whether any output has
+ * occurred.
+ */
+ runpush(ctx->bifcxrun, runclog(output_occurred), &val);
+ }
+ else if (n == 2)
+ {
+ /*
+ * Output was hidden, and output had already occurred. If
+ * more output has occurred, return true, else return nil.
+ * In either case, set the output_occurred flag back to
+ * true, since it was true before the outhide(true).
+ */
+ runpush(ctx->bifcxrun, runclog(output_occurred), &val);
+ outsethidden();
+ }
+ else
+ errsig1(ctx->bifcxerr, ERR_INVVBIF, ERRTSTR, "outhide");
+ }
+ else
+ errsig(ctx->bifcxerr, ERR_REQNUM);
+}
+
+/* put a numeric value in a list */
+static uchar *bifputnum(uchar *lstp, uint val)
+{
+ *lstp++ = DAT_NUMBER;
+ oswp4s(lstp, (long)val);
+ return(lstp + 4);
+}
+
+/* gettime */
+void biftim(bifcxdef *ctx, int argc) {
+ TimeDate tm;
+ uint timer;
+ uchar ret[80];
+ uchar *p;
+ runsdef val;
+ int typ;
+ int tm_yday;
+ const int MONTH_DAYS[11] = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30 };
+
+ if (argc == 1)
+ {
+ /* get the time type */
+ typ = (int)runpopnum(ctx->bifcxrun);
+ }
+ else
+ {
+ /* make sure no arguments are specified */
+ bifcntargs(ctx, 0, argc);
+
+ /* use the default time type */
+ typ = 1;
+ }
+
+ switch(typ)
+ {
+ case 1:
+ /*
+ * default information format - list format with current system
+ * time and date
+ */
+
+ /* make sure the time zone is set up properly */
+ os_tzset();
+
+ /* get the local time information */
+ g_system->getTimeAndDate(tm);
+
+ /* adjust values for return format */
+ tm.tm_wday++;
+
+ // Get number of days since start of year
+ tm_yday = tm.tm_mday;
+ for (int monthNum = 1; monthNum < tm.tm_mon; ++monthNum) {
+ int daysInMonth = MONTH_DAYS[monthNum - 1];
+ if (monthNum == 2)
+ daysInMonth = (tm.tm_year % 4) == 0 && (((tm.tm_year % 100) != 0) || ((tm.tm_year % 400) == 0)) ? 29 : 28;
+
+ tm_yday += daysInMonth;
+ }
+
+ // TODO: Convert dae/tme to Unix style local time
+ timer = 0;
+
+ /* build return list value */
+ oswp2(ret, 47);
+ p = ret + 2;
+ p = bifputnum(p, tm.tm_year);
+ p = bifputnum(p, tm.tm_mon);
+ p = bifputnum(p, tm.tm_mday);
+ p = bifputnum(p, tm.tm_wday);
+ p = bifputnum(p, tm_yday);
+ p = bifputnum(p, tm.tm_hour);
+ p = bifputnum(p, tm.tm_min);
+ p = bifputnum(p, tm.tm_sec);
+ *p++ = DAT_NUMBER;
+ oswp4s(p, (long)timer);
+
+ val.runstyp = DAT_LIST;
+ val.runsv.runsvstr = ret;
+ runpush(ctx->bifcxrun, DAT_LIST, &val);
+ break;
+
+ case 2:
+ /*
+ * High-precision system timer value - returns the system time
+ * in milliseconds, relative to an arbitrary zero point
+ */
+ runpnum(ctx->bifcxrun, os_get_sys_clock_ms());
+ break;
+
+ default:
+ /* other types are invalid */
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "gettime");
+ break;
+ }
+}
+
+/* getfuse */
+void bifgfu(bifcxdef *ctx, int argc)
+{
+ vocddef *daem;
+ objnum func;
+ runsdef val;
+ runcxdef *rcx = ctx->bifcxrun;
+ int slots;
+ prpnum prop;
+ voccxdef *vcx = ctx->bifcxrun->runcxvoc;
+
+ bifcntargs(ctx, 2, argc);
+
+ if (runtostyp(rcx) == DAT_FNADDR)
+ {
+ /* check on a setfuse()-style fuse: get fnaddr, parm */
+ func = runpopfn(rcx);
+ runpop(rcx, &val);
+
+ for (slots = vcx->voccxfuc, daem = vcx->voccxfus ;
+ slots ; ++daem, --slots)
+ {
+ if (daem->vocdfn == func
+ && daem->vocdarg.runstyp == val.runstyp
+ && !memcmp(&val.runsv, &daem->vocdarg.runsv,
+ (size_t)datsiz(val.runstyp, &val.runsv)))
+ goto ret_num;
+ }
+ }
+ else
+ {
+ /* check on a notify()-style fuse: get object, &message */
+ func = runpopobj(rcx);
+ prop = runpopprp(rcx);
+
+ for (slots = vcx->voccxalc, daem = vcx->voccxalm ;
+ slots ; ++daem, --slots)
+ {
+ if (daem->vocdfn == func && daem->vocdprp == prop)
+ goto ret_num;
+ }
+ }
+
+ /* didn't find anything - return nil */
+ runpush(rcx, DAT_NIL, &val);
+ return;
+
+ret_num:
+ /* return current daem->vocdtim */
+ runpnum(rcx, (long)daem->vocdtim);
+ return;
+}
+
+/* runfuses */
+void bifruf(bifcxdef *ctx, int argc)
+{
+ int ret;
+ runsdef val;
+
+ bifcntargs(ctx, 0, argc);
+ ret = exefuse(ctx->bifcxrun->runcxvoc, TRUE);
+ runpush(ctx->bifcxrun, runclog(ret), &val);
+}
+
+/* rundaemons */
+void bifrud(bifcxdef *ctx, int argc)
+{
+ bifcntargs(ctx, 0, argc);
+ exedaem(ctx->bifcxrun->runcxvoc);
+}
+
+/* intersect */
+void bifsct(bifcxdef *bifctx, int argc)
+{
+ runcxdef *ctx = bifctx->bifcxrun;
+ uchar *l1;
+ uchar *l2;
+ uchar *l3;
+ uint siz1;
+ uint siz2;
+ uint siz3;
+ uchar *p;
+ uint l;
+ uint dsz1;
+ uint dsz2;
+ runsdef val;
+ runsdef stk1, stk2;
+
+ bifcntargs(bifctx, 2, argc);
+ l1 = runpoplst(ctx);
+ siz1 = osrp2(l1);
+ l2 = runpoplst(ctx);
+ siz2 = osrp2(l2);
+
+ /* make sure the first list is smaller - if not, switch them */
+ if (siz1 > siz2)
+ l3 = l1, l1 = l2, l2 = l3, siz3 = siz1, siz1 = siz2, siz2 = siz3;
+
+ /* size of result is at most size of smaller list (which is now siz1) */
+ stk1.runstyp = stk2.runstyp = DAT_LIST;
+ stk1.runsv.runsvstr = l1;
+ stk2.runsv.runsvstr = l2;
+ runhres2(ctx, siz1, 2, &stk1, &stk2);
+ l1 = stk1.runsv.runsvstr;
+ l2 = stk2.runsv.runsvstr;
+ l3 = ctx->runcxhp + 2;
+
+ /* go through list1, and copy each element that is found in list2 */
+ for (l1 += 2, l2 += 2, siz1 -= 2, siz2 -= 2 ; siz1 ; lstadv(&l1, &siz1))
+ {
+ dsz1 = datsiz(*l1, l1 + 1) + 1;
+ for (l = siz2, p = l2 ; l ; lstadv(&p, &l))
+ {
+ dsz2 = datsiz(*p, p + 1) + 1;
+#ifndef AMIGA
+ if (dsz1 == dsz2 && !memcmp(l1, p, (size_t)dsz1))
+#else /* AMIGA */
+ if (!memcmp(l1, p, (size_t)dsz1) && (dsz1 == dsz2) )
+#endif /* AMIGA */
+ {
+ memcpy(l3, p, (size_t)dsz1);
+ l3 += dsz1;
+ break;
+ }
+ }
+ }
+
+ /* set up return value, take it out of the heap, and push value */
+ val.runsv.runsvstr = ctx->runcxhp;
+ val.runstyp = DAT_LIST;
+ oswp2(ctx->runcxhp, (uint)(l3 - ctx->runcxhp));
+ ctx->runcxhp = l3;
+ runrepush(ctx, &val);
+}
+
+/*
+ * Portable keystroke mappings. We map the extended key codes to these
+ * strings, so that the TADS code can access arrow keys and the like.
+ */
+static char *ext_key_names[] =
+{
+ "[up]", /* CMD_UP - 1 */
+ "[down]", /* CMD_DOWN - 2 */
+ "[right]", /* CMD_RIGHT - 3 */
+ "[left]", /* CMD_LEFT - 4 */
+ "[end]", /* CMD_END - 5 */
+ "[home]", /* CMD_HOME - 6 */
+ "[del-eol]", /* CMD_DEOL - 7 */
+ "[del-line]", /* CMD_KILL - 8 */
+ "[del]", /* CMD_DEL - 9 */
+ "[scroll]", /* CMD_SCR - 10 */
+ "[page up]", /* CMD_PGUP - 11 */
+ "[page down]", /* CMD_PGDN - 12 */
+ "[top]", /* CMD_TOP - 13 */
+ "[bottom]", /* CMD_BOT - 14 */
+ "[f1]", /* CMD_F1 - 15 */
+ "[f2]", /* CMD_F2 - 16 */
+ "[f3]", /* CMD_F3 - 17 */
+ "[f4]", /* CMD_F4 - 18 */
+ "[f5]", /* CMD_F5 - 19 */
+ "[f6]", /* CMD_F6 - 20 */
+ "[f7]", /* CMD_F7 - 21 */
+ "[f8]", /* CMD_F8 - 22 */
+ "[f9]", /* CMD_F9 - 23 */
+ "[f10]", /* CMD_F10 - 24 */
+ "[?]", /* invalid key - CMD_CHOME - 25 */
+ "[tab]", /* CMD_TAB - 26 */
+ "[?]", /* invalid key - shift-F2 - 27 */
+ "[?]", /* not used (obsolete) - 28 */
+ "[word-left]", /* CMD_WORD_LEFT - 29 */
+ "[word-right]", /* CMD_WORD_RIGHT - 30 */
+ "[del-word]", /* CMD_WORDKILL - 31 */
+ "[eof]", /* CMD_EOF - 32 */
+ "[break]" /* CMD_BREAK - 33 */
+};
+
+/*
+ * Get the name of a keystroke. Pass in the one or two characters
+ * returned by os_getc(), and we'll fill in the buffer with the
+ * inputkey() name of the keystroke. Returns true if the key was valid,
+ * false if not. 'c' is the first character returned by os_getc() for
+ * the keystroke; if 'c' is zero, then 'extc' is the character returned
+ * by the second call to os_getc() to get the CMD_xxx code for the
+ * keystroke.
+ *
+ * The name buffer should be 20 characters long - this will ensure that
+ * any name will fit.
+ *
+ * For ordinary, printable characters, we'll simply return the
+ * character; the letter 'a', for example, is returned as the string "a".
+ *
+ * For extended keys, we'll look up the CMD_xxx code and return the name
+ * of the command, enclosed in square brackets; see the ext_key_names
+ * table for the mappings. The left-arrow cursor key, for example,
+ * returns "[left]".
+ *
+ * For control characters, we'll generate a name like "[ctrl-a]", except
+ * for the following characters:
+ *
+ *. ascii 10 returns "\n"
+ *. ascii 13 returns "\n"
+ *. ascii 9 returns "\t"
+ *. ascii 8 returns "[bksp]"
+ */
+static int get_ext_key_name(char *namebuf, int c, int extc)
+{
+ /* if it's a control character, translate it */
+ if (c >= 1 && c <= 27)
+ {
+ switch(c)
+ {
+ case 10:
+ case 13:
+ /* return '\n' for LF and CR characters */
+ strcpy(namebuf, "\\n");
+ return TRUE;
+
+ case 9:
+ /* return '\t' for TAB characters */
+ strcpy(namebuf, "\\t");
+ return TRUE;
+
+ case 8:
+ /* return '[bksp]' for backspace characters */
+ strcpy(namebuf, "[bksp]");
+ return TRUE;
+
+ case 27:
+ /* return '[esc]' for the escape key */
+ strcpy(namebuf, "[esc]");
+ return TRUE;
+
+ default:
+ /* return '[ctrl-X]' for other control characters */
+ strcpy(namebuf, "[ctrl-X]");
+ namebuf[6] = (char)(c + 'a' - 1);
+ return TRUE;
+ }
+ }
+
+ /* if it's any other non-extended key, return it as-is */
+ if (c != 0)
+ {
+ namebuf[0] = c;
+ namebuf[1] = '\0';
+ return TRUE;
+ }
+
+ /* if it's in the key name array, use the array entry */
+ if (extc >= 1
+ && extc <= (int)(sizeof(ext_key_names)/sizeof(ext_key_names[0])))
+ {
+ /* use the array name */
+ strcpy(namebuf, ext_key_names[extc - 1]);
+ return TRUE;
+ }
+
+ /* if it's in the ALT key range, generate an ALT key name */
+ if (extc >= CMD_ALT && extc <= CMD_ALT + 25)
+ {
+ /* generate an ALT key name */
+ strcpy(namebuf, "[alt-X]");
+ namebuf[5] = (char)(extc - CMD_ALT + 'a');
+ return TRUE;
+ }
+
+ /* it's not a valid key - use '[?]' as the name */
+ strcpy(namebuf, "[?]");
+ return FALSE;
+}
+
+
+/* inputkey */
+void bifink(bifcxdef *ctx, int argc)
+{
+ int c;
+ int extc;
+ char str[20];
+ size_t len;
+
+ bifcntargs(ctx, 0, argc);
+ tioflushn(ctx->bifcxtio, 0);
+
+ /* get a key */
+ c = os_getc_raw();
+
+ /* if it's extended, get the second part of the extended sequence */
+ extc = (c == 0 ? os_getc_raw() : 0);
+
+ /* map the extended key name */
+ get_ext_key_name(str, c, extc);
+
+ /* get the length of the name */
+ len = strlen(str);
+
+ /* reset the [more] counter */
+ outreset();
+
+ /* return the string, translating escapes */
+ runpstr(ctx->bifcxrun, str, len, 0);
+}
+
+/* get direct/indirect object word list */
+void bifwrd(bifcxdef *ctx, int argc)
+{
+ int ob;
+ vocoldef *v;
+ uchar buf[128];
+ uchar *dst;
+ uchar *src;
+ uint len;
+ runsdef val;
+
+ bifcntargs(ctx, 1, argc);
+
+ /* figure out what word list to get */
+ ob = runpopnum(ctx->bifcxrun);
+ switch(ob)
+ {
+ case 1:
+ v = ctx->bifcxrun->runcxvoc->voccxdobj;
+ break;
+
+ case 2:
+ v = ctx->bifcxrun->runcxvoc->voccxiobj;
+ break;
+
+ default:
+ runpnil(ctx->bifcxrun);
+ return;
+ }
+
+ /* now build a list of strings from the words, if there are any */
+ if (v != 0 && voclistlen(v) != 0 && v->vocolfst != 0 && v->vocollst != 0)
+ {
+ for (dst = buf + 2, src = (uchar *)v->vocolfst ;
+ src <= (uchar *)v->vocollst ; src += len + 1)
+ {
+ *dst++ = DAT_SSTRING;
+ len = strlen((char *)src);
+ oswp2(dst, len + 2);
+ strcpy((char *)dst + 2, (char *)src);
+ dst += len + 2;
+ }
+ }
+ else
+ dst = buf + 2;
+
+ /* finish setting up the list length and return it */
+ len = dst - buf;
+ oswp2(buf, len);
+ val.runsv.runsvstr = buf;
+ val.runstyp = DAT_LIST;
+ runpush(ctx->bifcxrun, DAT_LIST, &val);
+}
+
+/* add a vocabulary word to an object */
+void bifadw(bifcxdef *ctx, int argc)
+{
+ uchar *wrd;
+ objnum objn;
+ prpnum prpn;
+ vocidef *voci;
+ int classflg;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ bifcntargs(ctx, 3, argc);
+
+ /* get the arguments */
+ objn = runpopobj(ctx->bifcxrun);
+ prpn = runpopprp(ctx->bifcxrun);
+ wrd = runpopstr(ctx->bifcxrun);
+
+ /* make sure the property is a valid part of speech property */
+ if (!prpisvoc(prpn))
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "addword");
+
+ /* get the vocidef for the object, and see if it's a class object */
+ voci = vocinh(voc, objn);
+
+ classflg = VOCFNEW;
+ if (voci->vociflg & VOCIFCLASS) classflg |= VOCFCLASS;
+
+ /* add the word */
+ vocadd(voc, prpn, objn, classflg, (char *)wrd);
+
+ /* generate undo for the operation */
+ vocdusave_addwrd(voc, objn, prpn, classflg, (char *)wrd);
+}
+
+/* delete a vocabulary word from an object */
+void bifdlw(bifcxdef *ctx, int argc)
+{
+ uchar *wrd;
+ objnum objn;
+ prpnum prpn;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ bifcntargs(ctx, 3, argc);
+
+ /* get the arguments */
+ objn = runpopobj(ctx->bifcxrun);
+ prpn = runpopprp(ctx->bifcxrun);
+ wrd = runpopstr(ctx->bifcxrun);
+
+ /* make sure the property is a valid part of speech property */
+ if (!prpisvoc(prpn))
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "delword");
+
+ /* delete the word */
+ vocdel1(voc, objn, (char *)wrd, prpn, FALSE, FALSE, TRUE);
+}
+
+/* callback context for word list builder */
+struct bifgtw_cb_ctx
+{
+ uchar *p;
+ int typ;
+};
+
+/* callback for word list builder */
+static void bifgtw_cb(void *ctx0, vocdef *voc, vocwdef *vocw)
+{
+ struct bifgtw_cb_ctx *ctx = (struct bifgtw_cb_ctx *)ctx0;
+
+ /* ignore deleted objects */
+ if (vocw->vocwflg & VOCFDEL)
+ return;
+
+ /* ignore objects of the inappropriate type */
+ if (vocw->vocwtyp != ctx->typ)
+ return;
+
+ /* the datatype is string */
+ *ctx->p = DAT_SSTRING;
+
+ /* copy the first word */
+ memcpy(ctx->p + 3, voc->voctxt, (size_t)voc->voclen);
+
+ /* if there are two words, add a space and the second word */
+ if (voc->vocln2)
+ {
+ *(ctx->p + 3 + voc->voclen) = ' ';
+ memcpy(ctx->p + 4 + voc->voclen, voc->voctxt + voc->voclen,
+ (size_t)voc->vocln2);
+ oswp2(ctx->p + 1, voc->voclen + voc->vocln2 + 3);
+ ctx->p += voc->voclen + voc->vocln2 + 4;
+ }
+ else
+ {
+ oswp2(ctx->p + 1, voc->voclen+2);
+ ctx->p += voc->voclen + 3;
+ }
+}
+
+/* get the list of words for an object for a particular part of speech */
+void bifgtw(bifcxdef *ctx, int argc)
+{
+ objnum objn;
+ prpnum prpn;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+ int cnt;
+ int siz;
+ runsdef val;
+ struct bifgtw_cb_ctx fnctx;
+
+ bifcntargs(ctx, 2, argc);
+
+ /* get the arguments */
+ objn = runpopobj(ctx->bifcxrun);
+ prpn = runpopprp(ctx->bifcxrun);
+
+ /* make sure the property is a valid part of speech property */
+ if (!prpisvoc(prpn))
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "delword");
+
+ /* get the size of the list we'll need to build */
+ voc_count(voc, objn, prpn, &cnt, &siz);
+
+ /*
+ * calculate how much space it will take to make a list out of all
+ * these words: 2 bytes for the list length header; plus, for each
+ * entry, 1 byte for the type header, 2 bytes for the string size
+ * header, and possibly one extra byte for the two-word separator --
+ * a total of 4 bytes extra per word.
+ */
+ siz += 2 + 4*cnt;
+
+ /* reserve the space */
+ runhres(ctx->bifcxrun, siz, 0);
+
+ /* set up our callback context, and build the list */
+ fnctx.p = ctx->bifcxrun->runcxhp + 2;
+ fnctx.typ = prpn;
+ voc_iterate(voc, objn, bifgtw_cb, &fnctx);
+
+ /* set up the return value */
+ val.runstyp = DAT_LIST;
+ val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+
+ /* write the list length, and advance past the space we used */
+ oswp2(ctx->bifcxrun->runcxhp, fnctx.p - ctx->bifcxrun->runcxhp);
+ ctx->bifcxrun->runcxhp = fnctx.p;
+
+ /* return the list */
+ runrepush(ctx->bifcxrun, &val);
+}
+
+/* verbinfo service routine - add an object to the output list */
+static uchar *bifvin_putprpn(uchar *p, prpnum prpn)
+{
+ *p++ = DAT_PROPNUM;
+ oswp2(p, prpn);
+ return p + 2;
+}
+
+/* verbinfo */
+void bifvin(bifcxdef *ctx, int argc)
+{
+ objnum verb;
+ objnum prep;
+ uchar tplbuf[VOCTPL2SIZ];
+ int newstyle;
+
+ /* get the verb */
+ verb = runpopobj(ctx->bifcxrun);
+
+ /* check for the presence of a preposition */
+ if (argc == 1)
+ {
+ /* no preposition */
+ prep = MCMONINV;
+ }
+ else
+ {
+ /* the second argument is the preposition */
+ bifcntargs(ctx, 2, argc);
+ prep = runpopobj(ctx->bifcxrun);
+ }
+
+ /* look up the template */
+ if (voctplfnd(ctx->bifcxrun->runcxvoc, verb, prep, tplbuf, &newstyle))
+ {
+ prpnum prp_do, prp_verdo, prp_io, prp_verio;
+ int flg_dis_do;
+ ushort siz;
+ uchar *p;
+ runsdef val;
+
+ /* get the information from the template */
+ prp_do = voctpldo(tplbuf);
+ prp_verdo = voctplvd(tplbuf);
+ prp_io = voctplio(tplbuf);
+ prp_verio = voctplvi(tplbuf);
+ flg_dis_do = (voctplflg(tplbuf) & VOCTPLFLG_DOBJ_FIRST) != 0;
+
+ /*
+ * figure space for the return value: if there's a prep, three
+ * property pointers plus a boolean, otherwise just two property
+ * pointers
+ */
+ siz = 2 + 2*(2+1);
+ if (prep != MCMONINV)
+ siz += (2+1) + 1;
+
+ /* reserve the space */
+ runhres(ctx->bifcxrun, siz, 0);
+
+ /* build the output list */
+ p = ctx->bifcxrun->runcxhp;
+ oswp2(p, siz);
+ p += 2;
+
+ p = bifvin_putprpn(p, prp_verdo);
+ if (prep == MCMONINV)
+ {
+ p = bifvin_putprpn(p, prp_do);
+ }
+ else
+ {
+ p = bifvin_putprpn(p, prp_verio);
+ p = bifvin_putprpn(p, prp_io);
+ *p++ = runclog(flg_dis_do);
+ }
+
+ /* build the return value */
+ val.runstyp = DAT_LIST;
+ val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+
+ /* consume the space */
+ ctx->bifcxrun->runcxhp += siz;
+
+ /* return the list */
+ runrepush(ctx->bifcxrun, &val);
+ }
+ else
+ {
+ /* no template for this verb - return nil */
+ runpnil(ctx->bifcxrun);
+ }
+}
+
+
+/* clearscreen */
+void bifcls(bifcxdef *ctx, int argc)
+{
+ /* this takes no arguments */
+ bifcntargs(ctx, 0, argc);
+
+ /* flush any pending output */
+ tioflushn(ctx->bifcxtio, 0);
+
+ /* clear the screen */
+ oscls();
+}
+
+/*
+ * File operations
+ */
+
+/*
+ * fopen(file, mode).
+ *
+ * Operations are allowed only if they conform to the current I/O safety
+ * level. The safety level can be set by the user on the command line
+ * when running the game, and some implementations may allow the setting
+ * to be saved as a preference. The possible levels are:
+ *
+ *. 0 - minimum safety - read and write in any directory
+ *. 1 - read in any directory, write in current directory
+ *. 2 - read/write access in current directory only
+ *. 3 - read-only access in current directory only
+ *. 4 - maximum safety - no file I/O allowed
+ *
+ * When operations are allowed only in the current directory, the
+ * operations will fail if the filename contains any sort of path
+ * specifier (for example, on Unix, any file that contains a '/' is
+ * considered to have a path specifier, and will always fail if
+ * operations are only allowed in the current directory).
+ */
+void biffopen(bifcxdef *ctx, int argc)
+{
+ char fname[OSFNMAX];
+ uchar *p;
+ uchar *mode;
+ int modelen;
+ int fnum;
+ osfildef *fp;
+ int bin_mode = TRUE; /* flag: mode is binary (rather than text) */
+ int rw_mode = FALSE; /* flag: both read and write are allowed */
+ char main_mode; /* 'r' for read, 'w' for write */
+ int in_same_dir; /* flag: file is in current directory */
+ appctxdef *appctx;
+
+ bifcntargs(ctx, 2, argc);
+
+ /* get the filename */
+ p = runpopstr(ctx->bifcxrun);
+ bifcstr(ctx, fname, (size_t)sizeof(fname), p);
+
+ /*
+ * If it's a relative path, combine it with the game file path to form
+ * the absolute path. This ensures that relative paths are always
+ * relative to the original working directory if the OS-level working
+ * directory has changed.
+ */
+ if (!os_is_file_absolute(fname))
+ {
+ /* combine the game file path with the relative filename */
+ char newname[OSFNMAX];
+ os_build_full_path(newname, sizeof(newname),
+ ctx->bifcxrun->runcxgamepath, fname);
+
+ /* replace the original filename with the full path */
+ strcpy(fname, newname);
+ }
+
+ /* get the mode string */
+ mode = runpopstr(ctx->bifcxrun);
+ modelen = osrp2(mode) - 2;
+ mode += 2;
+ if (modelen < 1)
+ goto bad_mode;
+
+ /* allocate a filenum for the file */
+ for (fnum = 0 ; fnum < BIFFILMAX ; ++fnum)
+ {
+ if (ctx->bifcxfile[fnum].fp == 0)
+ break;
+ }
+ if (fnum == BIFFILMAX)
+ {
+ /* return nil to indicate failure */
+ runpnil(ctx->bifcxrun);
+ return;
+ }
+
+ /* parse the main mode */
+ switch(*mode)
+ {
+ case 'w':
+ case 'W':
+ main_mode = 'w';
+ break;
+
+ case 'r':
+ case 'R':
+ main_mode = 'r';
+ break;
+
+ default:
+ goto bad_mode;
+ }
+
+ /* skip the main mode, and check for a '+' flag */
+ ++mode;
+ --modelen;
+ if (modelen > 0 && *mode == '+')
+ {
+ /* note the read/write mode */
+ rw_mode = TRUE;
+
+ /* skip the speciifer */
+ ++mode;
+ --modelen;
+ }
+
+ /* check for a binary/text specifier */
+ if (modelen > 0)
+ {
+ switch(*mode)
+ {
+ case 'b':
+ case 'B':
+ bin_mode = TRUE;
+ break;
+
+ case 't':
+ case 'T':
+ bin_mode = FALSE;
+ break;
+
+ default:
+ goto bad_mode;
+ }
+
+ /* skip the binary/text specifier */
+ ++mode;
+ --modelen;
+ }
+
+ /* it's an error if there's anything left unparsed */
+ if (modelen > 0)
+ goto bad_mode;
+
+ /*
+ * If we have a host application context, and it provides a file
+ * safety level callback function, ask the host system for its
+ * current file safety level, which overrides our current setting.
+ */
+ appctx = ctx->bifcxappctx;
+ if (appctx != 0 && appctx->get_io_safety_level != 0)
+ {
+ /*
+ * ask the host system for the current level, and override any
+ * setting we previously had
+ */
+ (*appctx->get_io_safety_level)(
+ appctx->io_safety_level_ctx,
+ &ctx->bifcxsafetyr, &ctx->bifcxsafetyw);
+ }
+
+ /*
+ * Check to see if the file is in the current working directory - if
+ * not, we may have to disallow the operation based on safety level
+ * settings.
+ */
+ in_same_dir = os_is_file_in_dir(
+ fname, ctx->bifcxrun->runcxgamepath, TRUE, FALSE);
+
+ /* check file safety settings */
+ switch(main_mode)
+ {
+ case 'w':
+ /*
+ * writing - we must be at a safety level no higher than 2
+ * (read/write current directory) to write at all, and we must be
+ * level 0 to write a file that's not in the current directory
+ */
+ if (ctx->bifcxsafetyw > 2
+ || (!in_same_dir && ctx->bifcxsafetyw > 0))
+ {
+ /* this operation is not allowed - return failure */
+ runpnil(ctx->bifcxrun);
+ return;
+ }
+ break;
+
+ case 'r':
+ /*
+ * reading - we must be at a safety level no higher than 3 (read
+ * current directory) to read at all, and we must be at safety
+ * level 1 (read any directory) or lower to read a file that's not
+ * in the current directory
+ */
+ if (ctx->bifcxsafetyr > 3
+ || (!in_same_dir && ctx->bifcxsafetyr > 1))
+ {
+ /* this operation is not allowed - return failure */
+ runpnil(ctx->bifcxrun);
+ return;
+ }
+ break;
+
+ default:
+ /*
+ * fail the operation, as a code maintenance measure to make
+ * sure that we add appropriate cases to this switch (even if
+ * merely to allow the operation unconditionally) in the event
+ * that more modes are added in the future
+ */
+ goto bad_mode;
+ }
+
+ /* try opening the file */
+ switch(main_mode)
+ {
+ case 'w':
+ /* check for binary vs text mode */
+ if (bin_mode)
+ {
+ /*
+ * binary mode -- allow read/write or just writing, but in
+ * either case truncate the file if it already exists, and
+ * create a new file if it doesn't exist
+ */
+ if (rw_mode)
+ fp = osfoprwtb(fname, OSFTDATA);
+ else
+ fp = osfopwb(fname, OSFTDATA);
+ }
+ else
+ {
+ /* text mode - don't allow read/write on a text file */
+ if (rw_mode)
+ goto bad_mode;
+
+ /* open the file */
+ fp = osfopwt(fname, OSFTTEXT);
+ }
+ break;
+
+ case 'r':
+ /* check for binary vs text mode */
+ if (bin_mode)
+ {
+ /*
+ * Binary mode -- allow read/write or just reading; leave
+ * any existing file intact.
+ */
+ if (rw_mode)
+ {
+ /* open for reading and writing, keeping existing data */
+ fp = osfoprwb(fname, OSFTDATA);
+ }
+ else
+ {
+ /* open for read-only */
+ fp = osfoprb(fname, OSFTDATA);
+ }
+ }
+ else
+ {
+ /* text mode -- only allow reading */
+ if (rw_mode)
+ goto bad_mode;
+
+ /* open the file */
+ fp = osfoprt(fname, OSFTTEXT);
+ }
+ break;
+
+ default:
+ goto bad_mode;
+ }
+
+ /* if we couldn't open it, return nil */
+ if (fp == 0)
+ {
+ runpnil(ctx->bifcxrun);
+ return;
+ }
+
+ /* store the flags */
+ ctx->bifcxfile[fnum].flags = 0;
+ if (bin_mode)
+ ctx->bifcxfile[fnum].flags |= BIFFIL_F_BINARY;
+
+ /* remember the file handle */
+ ctx->bifcxfile[fnum].fp = fp;
+
+ /* return the file number (i.e., the slot number) */
+ runpnum(ctx->bifcxrun, (long)fnum);
+ return;
+
+
+ /* come here on a mode error */
+bad_mode:
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "fopen");
+}
+
+/* service routine for file routines - get and validate a file number */
+static osfildef *bif_get_file(bifcxdef *ctx, int *fnump, int *bin_modep)
+{
+ long fnum;
+
+ /* get the file number and make sure it's valid */
+ fnum = runpopnum(ctx->bifcxrun);
+ if (fnum < 0 || fnum >= BIFFILMAX || ctx->bifcxfile[fnum].fp == 0)
+ runsig(ctx->bifcxrun, ERR_BADFILE);
+
+ /* put the validated file number, if the caller wants it */
+ if (fnump != 0)
+ *fnump = (int)fnum;
+
+ /* set the binary-mode flag, if the caller wants it */
+ if (bin_modep != 0)
+ *bin_modep = ((ctx->bifcxfile[fnum].flags & BIFFIL_F_BINARY) != 0);
+
+ /* return the file array pointer */
+ return ctx->bifcxfile[fnum].fp;
+}
+
+void biffclose(bifcxdef *ctx, int argc)
+{
+ int fnum;
+ osfildef *fp;
+
+ /* get the file number */
+ bifcntargs(ctx, 1, argc);
+ fp = bif_get_file(ctx, &fnum, 0);
+
+ /* close the file and release the slot */
+ osfcls(fp);
+ ctx->bifcxfile[fnum].fp = 0;
+}
+
+void bifftell(bifcxdef *ctx, int argc)
+{
+ osfildef *fp;
+
+ /* get the file number */
+ bifcntargs(ctx, 1, argc);
+ fp = bif_get_file(ctx, (int *)0, 0);
+
+ /* return the seek position */
+ runpnum(ctx->bifcxrun, osfpos(fp));
+}
+
+void biffseek(bifcxdef *ctx, int argc)
+{
+ osfildef *fp;
+ long pos;
+
+ /* get the file pointer */
+ bifcntargs(ctx, 2, argc);
+ fp = bif_get_file(ctx, (int *)0, 0);
+
+ /* get the seek position, and seek there */
+ pos = runpopnum(ctx->bifcxrun);
+ osfseek(fp, pos, OSFSK_SET);
+}
+
+void biffseekeof(bifcxdef *ctx, int argc)
+{
+ osfildef *fp;
+
+ /* get the file pointer */
+ bifcntargs(ctx, 1, argc);
+ fp = bif_get_file(ctx, (int *)0, 0);
+
+ /* seek to the end */
+ osfseek(fp, 0L, OSFSK_END);
+}
+
+void biffwrite(bifcxdef *ctx, int argc)
+{
+ osfildef *fp;
+ char typ;
+ char buf[32];
+ runsdef val;
+ int bin_mode;
+
+ /* get the file */
+ bifcntargs(ctx, 2, argc);
+ fp = bif_get_file(ctx, (int *)0, &bin_mode);
+
+ /* get the value to write */
+ runpop(ctx->bifcxrun, &val);
+ typ = val.runstyp;
+
+ if (bin_mode)
+ {
+ /* put a byte indicating the type */
+ if (osfwb(fp, &typ, 1))
+ goto ret_error;
+
+ /* see what type of data we want to put */
+ switch(typ)
+ {
+ case DAT_NUMBER:
+ oswp4s(buf, val.runsv.runsvnum);
+ if (osfwb(fp, buf, 4))
+ goto ret_error;
+ break;
+
+ case DAT_SSTRING:
+ /* write the string, including the length prefix */
+ if (osfwb(fp, val.runsv.runsvstr, osrp2(val.runsv.runsvstr)))
+ goto ret_error;
+ break;
+
+ case DAT_TRUE:
+ /* all we need for this is the type prefix */
+ break;
+
+ default:
+ /* other types are not acceptable */
+ runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "fwrite");
+ }
+ }
+ else
+ {
+ uint rem;
+ uchar *p;
+
+ switch(typ)
+ {
+ case DAT_SSTRING:
+ /*
+ * Copy and translate the string to our buffer, in pieces if
+ * the size of the string exceeds that of our buffer. If we
+ * encounter any escape codes, translate them.
+ */
+ rem = osrp2(val.runsv.runsvstr) - 2;
+ p = val.runsv.runsvstr + 2;
+ while (rem > 0)
+ {
+ uchar *dst;
+ uchar dbuf[256];
+
+ /* fill up the buffer */
+ for (dst = dbuf ;
+ rem != 0 && (size_t)(dst - dbuf) < sizeof(dbuf) - 1 ;
+ ++p, --rem)
+ {
+ /* if we have an escape character, translate it */
+ if (*p == '\\' && rem > 1)
+ {
+ /* skip the opening slash */
+ ++p;
+ --rem;
+
+ /* translate it */
+ switch(*p)
+ {
+ case 'n':
+ *dst++ = '\n';
+ break;
+
+ case 't':
+ *dst++ = '\t';
+ break;
+
+ default:
+ *dst++ = *p;
+ break;
+ }
+ }
+ else
+ {
+ /* copy this character directly */
+ *dst++ = *p;
+ }
+ }
+
+ /* null-terminate the buffer */
+ *dst = '\0';
+
+ /* write it out */
+ if (osfputs((char *)dbuf, fp) == EOF)
+ goto ret_error;
+ }
+
+ /* done */
+ break;
+
+ default:
+ /* other types are not allowed */
+ runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "fwrite");
+ }
+ }
+
+ /* success */
+ runpnil(ctx->bifcxrun);
+ return;
+
+ret_error:
+ val.runstyp = DAT_TRUE;
+ runpush(ctx->bifcxrun, DAT_TRUE, &val);
+}
+
+void biffread(bifcxdef *ctx, int argc)
+{
+ osfildef *fp;
+ char typ;
+ char buf[32];
+ runsdef val;
+ ushort len;
+ int bin_mode;
+
+ /* get the file pointer */
+ bifcntargs(ctx, 1, argc);
+ fp = bif_get_file(ctx, (int *)0, &bin_mode);
+
+ if (bin_mode)
+ {
+ /* binary file - read the type byte */
+ if (osfrb(fp, &typ, 1))
+ goto ret_error;
+
+ /* read the data according to the type */
+ switch(typ)
+ {
+ case DAT_NUMBER:
+ if (osfrb(fp, buf, 4))
+ goto ret_error;
+ runpnum(ctx->bifcxrun, osrp4s(buf));
+ break;
+
+ case DAT_SSTRING:
+ /* get the size */
+ if (osfrb(fp, buf, 2))
+ goto ret_error;
+ len = osrp2(buf);
+
+ /* reserve space */
+ runhres(ctx->bifcxrun, len, 0);
+
+ /* read the string into the reserved space */
+ if (osfrb(fp, ctx->bifcxrun->runcxhp + 2, len - 2))
+ goto ret_error;
+
+ /* set up the string */
+ oswp2(ctx->bifcxrun->runcxhp, len);
+ val.runstyp = DAT_SSTRING;
+ val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+
+ /* consume the space */
+ ctx->bifcxrun->runcxhp += len;
+
+ /* push the value */
+ runrepush(ctx->bifcxrun, &val);
+ break;
+
+ case DAT_TRUE:
+ val.runstyp = DAT_TRUE;
+ runpush(ctx->bifcxrun, DAT_TRUE, &val);
+ break;
+
+ default:
+ goto ret_error;
+ }
+ }
+ else
+ {
+ uchar dbuf[257];
+ uchar *dst;
+ uchar *src;
+ uint dlen;
+ uint res_total;
+ int found_nl;
+
+ /*
+ * reserve some space in the heap - we'll initially reserve
+ * space for twice our buffer, in case every single character
+ * needs to be expanded into an escape sequence
+ */
+ res_total = sizeof(dbuf) * 2;
+ runhres(ctx->bifcxrun, res_total, 0);
+
+ /* set up our output value */
+ val.runstyp = DAT_SSTRING;
+ val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+ dst = ctx->bifcxrun->runcxhp + 2;
+
+ /* keep going until we find a newline or run out of data */
+ for (found_nl = FALSE ; !found_nl ; )
+ {
+ /* text-mode - read the result into our buffer */
+ if (!osfgets((char *)dbuf, sizeof(dbuf) - 1, fp))
+ {
+ /*
+ * if we found a newline, return what we have;
+ * otherwise, return an error
+ */
+ if (found_nl)
+ break;
+ else
+ goto ret_error;
+ }
+
+ /*
+ * make sure it's null-terminated, in case the buffer was
+ * full
+ */
+ dbuf[256] = '\0';
+
+ /* translate into the heap area we've reserved */
+ for (src = dbuf ; *src != '\0' ; ++src, ++dst)
+ {
+ /* determine if we need translations */
+ switch(*src)
+ {
+ case '\n':
+ case '\r':
+ /* translate to a newline sequence */
+ *dst++ = '\\';
+ *dst = 'n';
+
+ /* note that we've found our newline */
+ found_nl = TRUE;
+ break;
+
+ case '\t':
+ /* translate to a tab sequence */
+ *dst++ = '\\';
+ *dst = 't';
+ break;
+
+ case '\\':
+ /* expand to a double-backslash sequence */
+ *dst++ = '\\';
+ *dst = '\\';
+ break;
+
+ default:
+ /* leave other characters intact */
+ *dst = *src;
+ break;
+ }
+ }
+
+ /*
+ * If we didn't find the newline, we'll need more space.
+ * This is a bit tricky, because the space we've already set
+ * up may move if we compact the heap while asking for more
+ * space. So, remember our current length, reserve another
+ * buffer-full of space, and set everything up at the new
+ * output location if necessary.
+ */
+ if (!found_nl)
+ {
+ /* reserve another buffer-full (double for expansion) */
+ res_total += sizeof(dbuf) * 2;
+
+ /* note our current offset */
+ dlen = dst - val.runsv.runsvstr;
+ oswp2(val.runsv.runsvstr, dlen);
+
+ /* ask for the space */
+ runhres(ctx->bifcxrun, res_total, 0);
+
+ /*
+ * Since we were at the top of the heap before, we
+ * should still be at the top of the heap. If not,
+ * we'll have to copy from our old location to the new
+ * top of the heap.
+ */
+ if (val.runsv.runsvstr != ctx->bifcxrun->runcxhp)
+ {
+ /* copy our existing text to our new location */
+ memmove(ctx->bifcxrun->runcxhp, val.runsv.runsvstr, dlen);
+
+ /* fix up our pointer */
+ val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+ }
+
+ /* re-establish our output pointer at our new location */
+ dst = val.runsv.runsvstr + dlen;
+ }
+ }
+
+ /* finish setting up the string */
+ dlen = dst - val.runsv.runsvstr;
+ oswp2(val.runsv.runsvstr, dlen);
+
+ /* consume the space */
+ ctx->bifcxrun->runcxhp += dlen;
+
+ /* push the value */
+ runrepush(ctx->bifcxrun, &val);
+ }
+
+ /* success - we've already pushed the return value */
+ return;
+
+ret_error:
+ runpnil(ctx->bifcxrun);
+}
+
+void bifcapture(bifcxdef *ctx, int argc)
+{
+ mcmcxdef *mcx = ctx->bifcxrun->runcxmem;
+ mcmon obj;
+ uint siz;
+ uint ofs;
+ uchar *p;
+
+ /* get the capture on/off flag */
+ bifcntargs(ctx, 1, argc);
+ switch(runtostyp(ctx->bifcxrun))
+ {
+ case DAT_TRUE:
+ /* turn on capturing */
+ tiocapture(ctx->bifcxtio, mcx, TRUE);
+
+ /*
+ * The return value is a status code used to restore the
+ * original status on the bracketing call to turn off output.
+ * The only status necessary is the current output size.
+ */
+ siz = tiocapturesize(ctx->bifcxtio);
+ runpnum(ctx->bifcxrun, (long)siz);
+ break;
+
+ case DAT_NUMBER:
+ /* get the original offset */
+ ofs = runpopnum(ctx->bifcxrun);
+
+ /* get the capture object and size */
+ obj = tiogetcapture(ctx->bifcxtio);
+ siz = tiocapturesize(ctx->bifcxtio);
+ if (obj == MCMONINV)
+ {
+ runpnil(ctx->bifcxrun);
+ return;
+ }
+
+ /* turn off capturing and reset the buffer on the outermost call */
+ if (ofs == 0)
+ {
+ tiocapture(ctx->bifcxtio, mcx, FALSE);
+ tioclrcapture(ctx->bifcxtio);
+ }
+
+ /* lock the object */
+ p = mcmlck(mcx, obj);
+
+ /* include only the part that happened after the matching call */
+ p += ofs;
+ siz = (ofs > siz) ? 0 : siz - ofs;
+
+ ERRBEGIN(ctx->bifcxerr)
+
+ /* push the string onto the stack */
+ runpstr(ctx->bifcxrun, (char *)p, siz, 0);
+
+ ERRCLEAN(ctx->bifcxerr)
+ /* done with the object - unlock it */
+ mcmunlck(mcx, obj);
+ ERRENDCLN(ctx->bifcxerr)
+
+ /* done with the object - unlock it */
+ mcmunlck(mcx, obj);
+ break;
+
+ default:
+ runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "outcapture");
+ }
+}
+
+/*
+ * systemInfo
+ */
+void bifsysinfo(bifcxdef *ctx, int argc)
+{
+ runsdef val;
+ int id;
+ long result;
+
+ /* see what we have */
+ switch(id = (int)runpopnum(ctx->bifcxrun))
+ {
+ case SYSINFO_SYSINFO:
+ /* systemInfo call is supported in this version - return true */
+ bifcntargs(ctx, 1, argc);
+ val.runstyp = DAT_TRUE;
+ runpush(ctx->bifcxrun, DAT_TRUE, &val);
+ break;
+
+ case SYSINFO_VERSION:
+ /* get the run-time version string */
+ bifcntargs(ctx, 1, argc);
+ runpushcstr(ctx->bifcxrun, TADS_RUNTIME_VERSION,
+ strlen(TADS_RUNTIME_VERSION), 0);
+ break;
+
+ case SYSINFO_OS_NAME:
+ /* get the operating system name */
+ bifcntargs(ctx, 1, argc);
+ runpushcstr(ctx->bifcxrun, OS_SYSTEM_NAME, strlen(OS_SYSTEM_NAME), 0);
+ break;
+
+ case SYSINFO_HTML:
+ case SYSINFO_JPEG:
+ case SYSINFO_PNG:
+ case SYSINFO_WAV:
+ case SYSINFO_MIDI:
+ case SYSINFO_WAV_MIDI_OVL:
+ case SYSINFO_WAV_OVL:
+ case SYSINFO_PREF_IMAGES:
+ case SYSINFO_PREF_SOUNDS:
+ case SYSINFO_PREF_MUSIC:
+ case SYSINFO_PREF_LINKS:
+ case SYSINFO_MPEG:
+ case SYSINFO_MPEG1:
+ case SYSINFO_MPEG2:
+ case SYSINFO_MPEG3:
+ case SYSINFO_LINKS_HTTP:
+ case SYSINFO_LINKS_FTP:
+ case SYSINFO_LINKS_NEWS:
+ case SYSINFO_LINKS_MAILTO:
+ case SYSINFO_LINKS_TELNET:
+ case SYSINFO_PNG_TRANS:
+ case SYSINFO_PNG_ALPHA:
+ case SYSINFO_OGG:
+ case SYSINFO_MNG:
+ case SYSINFO_MNG_TRANS:
+ case SYSINFO_MNG_ALPHA:
+ case SYSINFO_TEXT_HILITE:
+ case SYSINFO_INTERP_CLASS:
+ /*
+ * these information types are all handled by the OS layer, and
+ * take no additional arguments
+ */
+ bifcntargs(ctx, 1, argc);
+ if (os_get_sysinfo(id, 0, &result))
+ {
+ /* we got a valid result - return it */
+ runpnum(ctx->bifcxrun, result);
+ }
+ else
+ {
+ /* the code was unknown - return nil */
+ runpnil(ctx->bifcxrun);
+ }
+ break;
+
+ case SYSINFO_HTML_MODE:
+ /* ask the output formatter for its current HTML setting */
+ bifcntargs(ctx, 1, argc);
+ val.runstyp = runclog(tio_is_html_mode());
+ runpush(ctx->bifcxrun, val.runstyp, &val);
+ break;
+
+ case SYSINFO_TEXT_COLORS:
+ /*
+ * Text colors are only supported in full HTML interpreters. If
+ * this is an HTML interpreter, ask the underlying OS layer about
+ * color support; otherwise, colors are not available, since we
+ * don't handle colors in our text-only HTML subset.
+ *
+ * Colors are NOT supported in the HTML mini-parser in text-only
+ * interpreters in TADS 2. So, even if we're running in HTML
+ * mode, if this is a text-only interpreter, we can't display text
+ * colors.
+ */
+ bifcntargs(ctx, 1, argc);
+ if (os_get_sysinfo(SYSINFO_HTML, 0, &result) && result != 0)
+ {
+ /*
+ * we're in HTML mode, so ask the underlying HTML OS
+ * implementation for its level of text color support
+ */
+ if (os_get_sysinfo(id, 0, &result))
+ {
+ /* push the OS-level result */
+ runpnum(ctx->bifcxrun, result);
+ }
+ else
+ {
+ /* the OS code doesn't recognize it; assume no support */
+ runpnum(ctx->bifcxrun, SYSINFO_TXC_NONE);
+ }
+ }
+ else
+ {
+ /* we're a text-only interpreter - no color support */
+ runpnum(ctx->bifcxrun, SYSINFO_TXC_NONE);
+ }
+ break;
+
+ case SYSINFO_BANNERS:
+ /* TADS 2 does not offer banner support */
+ bifcntargs(ctx, 1, argc);
+ runpnum(ctx->bifcxrun, 0);
+ break;
+
+ default:
+ /*
+ * Other codes fail harmlessly with a nil return value. Pop all
+ * remaining arguments and return nil.
+ */
+ for ( ; argc > 1 ; --argc)
+ rundisc(ctx->bifcxrun);
+ runpnil(ctx->bifcxrun);
+ break;
+ }
+}
+
+/*
+ * morePrompt - display the more prompt and wait for the user to respond
+ */
+void bifmore(bifcxdef *ctx, int argc)
+{
+ /* this function takes no arguments */
+ bifcntargs(ctx, 0, argc);
+
+ /* display the MORE prompt */
+ tioflushn(ctx->bifcxtio, 1);
+ out_more_prompt();
+}
+
+/*
+ * parserSetMe
+ */
+void bifsetme(bifcxdef *ctx, int argc)
+{
+ objnum new_me;
+
+ /* this function takes one argument */
+ bifcntargs(ctx, 1, argc);
+
+ /* get the new "Me" object */
+ new_me = runpopobj(ctx->bifcxrun);
+
+ /* "Me" cannot be nil */
+ if (new_me == MCMONINV)
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "parserSetMe");
+
+ /* set the current "Me" object in the parser */
+ voc_set_me(ctx->bifcxrun->runcxvoc, new_me);
+}
+
+/*
+ * parserGetMe
+ */
+void bifgetme(bifcxdef *ctx, int argc)
+{
+ /* this function takes no arguments */
+ bifcntargs(ctx, 0, argc);
+
+ /* return the current Me object */
+ runpobj(ctx->bifcxrun, ctx->bifcxrun->runcxvoc->voccxme);
+}
+
+/*
+ * reSearch
+ */
+void bifresearch(bifcxdef *ctx, int argc)
+{
+ uchar *patstr;
+ size_t patlen;
+ uchar *searchstr;
+ size_t searchlen;
+ int result_len;
+ int match_ofs;
+
+ /* this function takes two parameters: pattern, string */
+ bifcntargs(ctx, 2, argc);
+
+ /* get the pattern string */
+ patstr = runpopstr(ctx->bifcxrun);
+ patlen = osrp2(patstr) - 2;
+ patstr += 2;
+
+ /* get the search string */
+ searchstr = runpopstr(ctx->bifcxrun);
+ searchlen = osrp2(searchstr) - 2;
+ searchstr += 2;
+
+ /* search for the pattern in the string */
+ match_ofs = re_compile_and_search(&ctx->bifcxregex,
+ (char *)patstr, patlen,
+ (char *)searchstr, searchlen,
+ &result_len);
+
+ /*
+ * if we didn't match, return nil; otherwise, return a list with the
+ * match offset and length
+ */
+ if (match_ofs < 0)
+ {
+ /* no match - return nil */
+ runpnil(ctx->bifcxrun);
+ }
+ else
+ {
+ ushort listsiz;
+ runsdef val;
+ uchar *p;
+
+ /*
+ * build a list consisting of two numbers and a string: two
+ * bytes for the list header, then two elements at (one byte for
+ * the datatype header, four bytes for the number), then the
+ * string element with (one byte for the datatype, two bytes for
+ * the string length prefix, and the bytes of the string)
+ */
+ listsiz = 2 + (1+4)*2 + (1 + 2 + (ushort)(result_len));
+
+ /* allocate the space */
+ runhres(ctx->bifcxrun, listsiz, 0);
+
+ /* set up the list stack item */
+ val.runstyp = DAT_LIST;
+ p = val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+
+ /* set the list's length */
+ oswp2(p, listsiz);
+ p += 2;
+
+ /*
+ * Add the offset element. For consistency with TADS
+ * conventions, use 1 as the offset of the first character in
+ * the string - this makes it easy to use the offset value with
+ * substr().
+ */
+ *p++ = DAT_NUMBER;
+ oswp4s(p, match_ofs + 1);
+ p += 4;
+
+ /* add the length element */
+ *p++ = DAT_NUMBER;
+ oswp4s(p, result_len);
+ p += 4;
+
+ /* add the result string */
+ *p++ = DAT_SSTRING;
+ oswp2(p, result_len + 2);
+ p += 2;
+ memcpy(p, ctx->bifcxregex.strbuf + match_ofs, result_len);
+
+ /* reserve the space in the heap */
+ ctx->bifcxrun->runcxhp += listsiz;
+
+ /* return the list */
+ runrepush(ctx->bifcxrun, &val);
+ }
+}
+
+/* reGetGroup */
+void bifregroup(bifcxdef *ctx, int argc)
+{
+ int grp;
+ size_t len;
+ re_group_register *reg;
+ ushort hplen;
+ runsdef val;
+ uchar *p;
+ long numval;
+
+ /* this function takes one parameter: the group number to retrieve */
+ bifcntargs(ctx, 1, argc);
+
+ /* get the group number */
+ grp = (int)runpopnum(ctx->bifcxrun);
+
+ /* make sure it's within range */
+ if (grp < 1 || grp > RE_GROUP_REG_CNT)
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "reGetGroup");
+
+ /* adjust from a 1-bias to an array index */
+ --grp;
+
+ /* if the group was never set, return nil */
+ if (grp >= ctx->bifcxregex.cur_group)
+ {
+ runpnil(ctx->bifcxrun);
+ return;
+ }
+
+ /* get the register */
+ reg = &ctx->bifcxregex.regs[grp];
+
+ /* if the group wasn't set, return nil */
+ if (reg->start_ofs == 0 || reg->end_ofs == 0)
+ {
+ runpnil(ctx->bifcxrun);
+ return;
+ }
+
+ /* calculate the length of the string in this group */
+ len = reg->end_ofs - reg->start_ofs;
+
+ /*
+ * reserve the necessary heap space: two bytes for the list length
+ * prefix, two number elements (one byte each for the type, four
+ * bytes each for the value), and the string element (one byte for
+ * the type, two bytes for the length prefix, plus the string
+ * itself).
+ */
+ hplen = (ushort)(2 + 2*(1+4) + (1 + 2 + len));
+ runhres(ctx->bifcxrun, hplen, 0);
+
+ /* set up the stack value */
+ val.runstyp = DAT_LIST;
+ p = val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+
+ /* put in the list length prefix */
+ oswp2(p, hplen);
+ p += 2;
+
+ /* add the starting character position of the group - adjust to 1-bias */
+ *p++ = DAT_NUMBER;
+ numval = (long)(reg->start_ofs - ctx->bifcxregex.strbuf) + 1;
+ oswp4s(p, numval);
+ p += 4;
+
+ /* add the length of the group */
+ *p++ = DAT_NUMBER;
+ numval = (long)(reg->end_ofs - reg->start_ofs);
+ oswp4s(p, numval);
+ p += 4;
+
+ /* set up the string */
+ *p++ = DAT_SSTRING;
+ oswp2(p, len+2);
+ p += 2;
+ memcpy(p, reg->start_ofs, len);
+
+ /* consume the heap space */
+ ctx->bifcxrun->runcxhp += hplen;
+
+ /* push the result */
+ runrepush(ctx->bifcxrun, &val);
+}
+
+
+/*
+ * inputevent
+ */
+void bifinpevt(bifcxdef *ctx, int argc)
+{
+ unsigned long timeout = 0;
+ int use_timeout = FALSE;
+ os_event_info_t info;
+ int evt;
+ uchar *p;
+ ushort lstsiz;
+ runsdef val;
+ size_t paramlen = 0;
+ char keyname[20];
+
+ /* check for a timeout value */
+ if (argc == 0)
+ {
+ /* there's no timeout */
+ use_timeout = FALSE;
+ timeout = 0;
+ }
+ else if (argc >= 1)
+ {
+ /* get the timeout value */
+ use_timeout = TRUE;
+ timeout = (unsigned long)runpopnum(ctx->bifcxrun);
+ }
+
+ /* ensure we don't have too many arguments */
+ if (argc > 1)
+ runsig(ctx->bifcxrun, ERR_BIFARGC);
+
+ /* flush any pending output */
+ tioflushn(ctx->bifcxtio, 0);
+
+ /* reset count of lines since keyboard input */
+ tioreset(ctx->bifcxtio);
+
+ /* ask the OS code for an event */
+ evt = os_get_event(timeout, use_timeout, &info);
+
+ /*
+ * the list always minimally needs two bytes of length prefix plus a
+ * number with the event code (one byte for the type, four bytes for
+ * the value)
+ */
+ lstsiz = 2 + (1 + 4);
+
+ /* figure out how much space we'll need based on the event type */
+ switch(evt)
+ {
+ case OS_EVT_KEY:
+ /*
+ * we need space for a string with one or two bytes (depending
+ * on whether or not we have an extended key code) - 1 byte for
+ * type code, 2 for length prefix, and 1 or 2 for the string's
+ * contents
+ */
+ paramlen = (info.key[0] == 0 ? 2 : 1);
+
+ /* map the extended key */
+ get_ext_key_name(keyname, info.key[0], info.key[1]);
+
+ /* determine the length we need for the string */
+ paramlen = strlen(keyname);
+
+ /* add it into the list */
+ lstsiz += 1 + 2 + paramlen;
+ break;
+
+ case OS_EVT_HREF:
+ /*
+ * we need space for the href string - 1 byte for type code, 2
+ * for length prefix, plus the string's contents
+ */
+ paramlen = strlen(info.href);
+ lstsiz += 1 + 2 + (ushort)paramlen;
+ break;
+
+ default:
+ /* other event types have no extra data */
+ break;
+ }
+
+ /* allocate space for the list */
+ runhres(ctx->bifcxrun, lstsiz, 0);
+
+ /* set up the stack value */
+ val.runstyp = DAT_LIST;
+ p = val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+
+ /* set up the list length prefix */
+ oswp2(p, lstsiz);
+ p += 2;
+
+ /* set up the event type element */
+ *p++ = DAT_NUMBER;
+ oswp4s(p, evt);
+ p += 4;
+
+ /* add the event parameters, if any */
+ switch(evt)
+ {
+ case OS_EVT_KEY:
+ /* set up the string for the key */
+ *p++ = DAT_SSTRING;
+ oswp2(p, paramlen + 2);
+ p += 2;
+
+ /* add the characters to the string */
+ memcpy(p, keyname, paramlen);
+ p += paramlen;
+ break;
+
+ case OS_EVT_HREF:
+ /* add the string for the href */
+ *p++ = DAT_SSTRING;
+ oswp2(p, paramlen + 2);
+ memcpy(p + 2, info.href, paramlen);
+ break;
+ }
+
+ /* consume the heap space */
+ ctx->bifcxrun->runcxhp += lstsiz;
+
+ /* push the result */
+ runrepush(ctx->bifcxrun, &val);
+}
+
+/* timeDelay */
+void bifdelay(bifcxdef *ctx, int argc)
+{
+ long delay;
+
+ /* ensure we have the right number of arguments */
+ bifcntargs(ctx, 1, argc);
+
+ /* flush any pending output */
+ tioflushn(ctx->bifcxtio, 0);
+
+ /* get the delay time */
+ delay = runpopnum(ctx->bifcxrun);
+
+ /* let the system perform the delay */
+ os_sleep_ms(delay);
+}
+
+/* setOutputFilter */
+void bifsetoutfilter(bifcxdef *ctx, int argc)
+{
+ /* ensure we have the right number of arguments */
+ bifcntargs(ctx, 1, argc);
+
+ /* see what we have */
+ switch(runtostyp(ctx->bifcxrun))
+ {
+ case DAT_NIL:
+ /* remove the current filter */
+ out_set_filter(MCMONINV);
+
+ /* discard the argument */
+ rundisc(ctx->bifcxrun);
+ break;
+
+ case DAT_FNADDR:
+ /* set the filter to the given function */
+ out_set_filter(runpopfn(ctx->bifcxrun));
+ break;
+
+ default:
+ /* anything else is invalid */
+ runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "setOutputFilter");
+ }
+}
+
+/*
+ * Get an optional object argument. If the next argument is not an
+ * object value, or we're out of arguments, we'll return MCMONINV.
+ * Otherwise, we'll pop the object value and return it, decrementing the
+ * remaining argument counter provided.
+ */
+static objnum bif_get_optional_obj_arg(bifcxdef *ctx, int *rem_argc)
+{
+ /* if we're out of arguments, there's no object value */
+ if (*rem_argc == 0)
+ return MCMONINV;
+
+ /*
+ * if the next argument is not an object or nil, we're out of object
+ * arguments
+ */
+ if (runtostyp(ctx->bifcxrun) != DAT_OBJECT
+ && runtostyp(ctx->bifcxrun) != DAT_NIL)
+ return MCMONINV;
+
+ /* we have an object - remove it from the remaining argument count */
+ --(*rem_argc);
+
+ /* pop and return the object value */
+ return runpopobjnil(ctx->bifcxrun);
+}
+
+/*
+ * execCommand flag values
+ */
+#define EC_HIDE_SUCCESS 0x00000001
+#define EC_HIDE_ERROR 0x00000002
+#define EC_SKIP_VALIDDO 0x00000004
+#define EC_SKIP_VALIDIO 0x00000008
+
+/*
+ * execCommand - execute a recursive command
+ */
+void bifexec(bifcxdef *ctx, int argc)
+{
+ objnum actor;
+ objnum verb;
+ objnum dobj;
+ objnum prep;
+ objnum iobj;
+ int err;
+ uint capture_start = 0;
+ uint capture_end;
+ objnum capture_obj;
+ ulong flags;
+ int hide_any;
+ int rem_argc;
+
+ /*
+ * Check for the correct argument count. The first two arguments
+ * are required; additional arguments are optional.
+ */
+ if (argc < 2 || argc > 6)
+ runsig(ctx->bifcxrun, ERR_BIFARGC);
+
+ /* pop the arguments - actor, verb, dobj, prep, iobj */
+ actor = runpopobjnil(ctx->bifcxrun);
+ verb = runpopobjnil(ctx->bifcxrun);
+
+ /*
+ * The other object arguments are optional. If we run into a
+ * numeric argument, it's the flags value, in which case we're out
+ * of objects.
+ */
+ rem_argc = argc - 2;
+ dobj = bif_get_optional_obj_arg(ctx, &rem_argc);
+ prep = bif_get_optional_obj_arg(ctx, &rem_argc);
+ iobj = bif_get_optional_obj_arg(ctx, &rem_argc);
+
+ /* if we have a flags argument, pop it */
+ if (rem_argc > 0)
+ {
+ /* the last argument is the flags - pop the numeric value */
+ flags = runpopnum(ctx->bifcxrun);
+
+ /* remove it from the remaining argument counter */
+ --rem_argc;
+ }
+ else
+ {
+ /* no flags specified - use zero by default */
+ flags = 0;
+ }
+
+ /*
+ * make sure we don't have any arguments left - if we do, then it
+ * means that we got an incorrect type and skipped an argument when
+ * we were trying to sense the meanings of the arguments from their
+ * types
+ */
+ if (rem_argc != 0)
+ runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "execCommand");
+
+ /* if we're hiding any output, start output capture */
+ hide_any = ((flags & (EC_HIDE_SUCCESS | EC_HIDE_ERROR)) != 0);
+ if (hide_any)
+ {
+ /* start capturing */
+ tiocapture(ctx->bifcxtio, ctx->bifcxrun->runcxmem, TRUE);
+
+ /* note the current output position */
+ capture_start = tiocapturesize(ctx->bifcxtio);
+ }
+
+ /* execute the command */
+ err = execmd_recurs(ctx->bifcxrun->runcxvoc,
+ actor, verb, dobj, prep, iobj,
+ (flags & EC_SKIP_VALIDDO) == 0,
+ (flags & EC_SKIP_VALIDIO) == 0);
+
+ /* if we're hiding any output, end hiding */
+ if (hide_any)
+ {
+ uchar *p;
+ int hide;
+
+ /* get the capture buffer size */
+ capture_end = tiocapturesize(ctx->bifcxtio);
+
+ /* turn off capture if it wasn't already on when we started */
+ if (capture_start == 0)
+ tiocapture(ctx->bifcxtio, ctx->bifcxrun->runcxmem, FALSE);
+
+ /* determine whether we're hiding or showing the result */
+ if (err == 0)
+ hide = ((flags & EC_HIDE_SUCCESS) != 0);
+ else
+ hide = ((flags & EC_HIDE_ERROR) != 0);
+
+ /* show or hide the result, as appropriate */
+ if (hide)
+ {
+ /*
+ * We're hiding this result, so do not display the string.
+ * If there's an enclosing capture, remove the string from
+ * the enclosing capture.
+ */
+ if (capture_start != 0)
+ tiopopcapture(ctx->bifcxtio, capture_start);
+ }
+ else
+ {
+ /*
+ * We're showing the text. If we're in an enclosing
+ * capture, do nothing - simply leave the string in the
+ * enclosing capture buffer; otherwise, actually display it
+ */
+ if (capture_start == 0)
+ {
+ /* lock the capture object */
+ capture_obj = tiogetcapture(ctx->bifcxtio);
+ p = mcmlck(ctx->bifcxrun->runcxmem, capture_obj);
+
+ ERRBEGIN(ctx->bifcxerr)
+ {
+ /* display the string */
+ outformatlen((char *)p + capture_start,
+ capture_end - capture_start);
+ }
+ ERRCLEAN(ctx->bifcxerr)
+ {
+ /* unlock the capture object before signalling out */
+ mcmunlck(ctx->bifcxrun->runcxmem, capture_obj);
+ }
+ ERRENDCLN(ctx->bifcxerr);
+
+ /* unlock the capture object */
+ mcmunlck(ctx->bifcxrun->runcxmem, capture_obj);
+ }
+ }
+
+ /* clear the capture buffer if it wasn't on when we started */
+ if (capture_start == 0)
+ tioclrcapture(ctx->bifcxtio);
+ }
+
+ /* push the result code */
+ runpnum(ctx->bifcxrun, err);
+}
+
+/*
+ * parserGetObj - get one of the objects associated with the command
+ */
+void bifgetobj(bifcxdef *ctx, int argc)
+{
+ int id;
+ objnum obj = 0;
+ voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+ /* check the argument count */
+ bifcntargs(ctx, 1, argc);
+
+ /* get the argument */
+ id = (int)runpopnum(ctx->bifcxrun);
+
+ /* get the appropriate object */
+ switch(id)
+ {
+ case 1:
+ /* get the current actor */
+ obj = voc->voccxactor;
+
+ /* if there's no current actor, use the current 'me' by default */
+ if (obj == MCMONINV)
+ obj = voc->voccxme;
+
+ /* done */
+ break;
+
+ case 2:
+ /* verb */
+ obj = voc->voccxverb;
+ break;
+
+ case 3:
+ /* direct object */
+ obj = (voc->voccxdobj == 0 ? MCMONINV : voc->voccxdobj->vocolobj);
+ break;
+
+ case 4:
+ /* preposition */
+ obj = voc->voccxprep;
+ break;
+
+ case 5:
+ /* indirect object */
+ obj = (voc->voccxiobj == 0 ? MCMONINV : voc->voccxiobj->vocolobj);
+ break;
+
+ case 6:
+ /* "it" */
+ obj = voc->voccxit;
+ break;
+
+ case 7:
+ /* "him" */
+ obj = voc->voccxhim;
+ break;
+
+ case 8:
+ /* "her" */
+ obj = voc->voccxher;
+ break;
+
+ case 9:
+ /* them */
+ voc_push_objlist(voc, voc->voccxthm, voc->voccxthc);
+
+ /*
+ * return directly, since we've already pushed the result (it's
+ * a list, not an object)
+ */
+ return;
+
+ default:
+ /* invalid argument */
+ runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "parserGetObj");
+ break;
+ }
+
+ /* return the object */
+ runpobj(ctx->bifcxrun, obj);
+}
+
+/*
+ * parseNounList - parse a noun list. Call like this:
+ *
+ * parserParseNounList(wordlist, typelist, starting_index, complain,
+ * multi, check_actor);
+ */
+void bifparsenl(bifcxdef *ctx, int argc)
+{
+ /* check the argument count */
+ bifcntargs(ctx, 6, argc);
+
+ /* call the parser */
+ voc_parse_np(ctx->bifcxrun->runcxvoc);
+}
+
+/*
+ * parserTokenize - given a string, produce a list of tokens. Returns
+ * nil on error, or a list of token strings.
+ *
+ * parserTokenize(commandString);
+ */
+void bifprstok(bifcxdef *ctx, int argc)
+{
+ /* check arguments */
+ bifcntargs(ctx, 1, argc);
+
+ /* call the parser */
+ voc_parse_tok(ctx->bifcxrun->runcxvoc);
+}
+
+/*
+ * parserGetTokTypes - given a list of tokens (represented as strings),
+ * get a corresponding list of token types.
+ *
+ * parserGetTokTypes(tokenList);
+ */
+void bifprstoktyp(bifcxdef *ctx, int argc)
+{
+ /* check arguments */
+ bifcntargs(ctx, 1, argc);
+
+ /* call the parser */
+ voc_parse_types(ctx->bifcxrun->runcxvoc);
+}
+
+/*
+ * parserDictLookup - given a list of tokens and their types, produce a
+ * list of all of the objects that match all of the words.
+ *
+ * parserDictLookup(tokenList, typeList);
+ */
+void bifprsdict(bifcxdef *ctx, int argc)
+{
+ /* check arguments */
+ bifcntargs(ctx, 2, argc);
+
+ /* call the parser */
+ voc_parse_dict_lookup(ctx->bifcxrun->runcxvoc);
+}
+
+/*
+ * parserResolveObjects - resolve an object list of the sort returned by
+ * parseNounList. Validates and disambiguates the objects.
+ *
+ * parserResolveObjects(actor, verb, prep, otherobj, usageType,
+ * verprop, tokenList, objList, silent)
+ */
+void bifprsrslv(bifcxdef *ctx, int argc)
+{
+ /* check arguments */
+ bifcntargs(ctx, 9, argc);
+
+ /* call the parser */
+ voc_parse_disambig(ctx->bifcxrun->runcxvoc);
+}
+
+/*
+ * parserReplaceCommand - replace the current command line with a new
+ * string. Aborts the current command.
+ */
+void bifprsrplcmd(bifcxdef *ctx, int argc)
+{
+ /* check arguments */
+ bifcntargs(ctx, 1, argc);
+
+ /* call the parser */
+ voc_parse_replace_cmd(ctx->bifcxrun->runcxvoc);
+}
+
+/*
+ * exitobj - throw a RUNEXITOBJ error
+ */
+void bifexitobj(bifcxdef *ctx, int argc)
+{
+ /* no arguments are allowed */
+ bifcntargs(ctx, 0, argc);
+
+ /* throw the RUNEXITOBJ error */
+ errsig(ctx->bifcxerr, ERR_RUNEXITOBJ);
+}
+
+/*
+ * Standard system button labels for bifinpdlg()
+ */
+#define BIFINPDLG_LBL_OK 1
+#define BIFINPDLG_LBL_CANCEL 2
+#define BIFINPDLG_LBL_YES 3
+#define BIFINPDLG_LBL_NO 4
+
+/*
+ * inputdialog
+ */
+void bifinpdlg(bifcxdef *ctx, int argc)
+{
+ uchar *p;
+ char prompt[256];
+ char lblbuf[256];
+ char *labels[10];
+ char *dst;
+ char *xp;
+ uint len;
+ size_t bcnt;
+ int default_resp, cancel_resp;
+ int resp;
+ int std_btns;
+ int icon_id;
+
+ /* check for proper arguments */
+ bifcntargs(ctx, 5, argc);
+
+ /* get the icon number */
+ icon_id = runpopnum(ctx->bifcxrun);
+
+ /* get the prompt string */
+ p = runpopstr(ctx->bifcxrun);
+ bifcstr(ctx, prompt, (size_t)sizeof(prompt), p);
+
+ /* translate from internal to local characters */
+ for (xp = prompt ; *xp != '\0' ; xp++)
+ *xp = (char)cmap_i2n(*xp);
+
+ /* check for a standard button set selection */
+ if (runtostyp(ctx->bifcxrun) == DAT_NUMBER)
+ {
+ /* get the standard button set ID */
+ std_btns = runpopnum(ctx->bifcxrun);
+
+ /* there are no actual buttons */
+ bcnt = 0;
+ }
+ else
+ {
+ /* we're not using standard buttons */
+ std_btns = 0;
+
+ /* get the response string list */
+ p = runpoplst(ctx->bifcxrun);
+ len = osrp2(p);
+ p += 2;
+
+ /* build our internal button list */
+ for (bcnt = 0, dst = lblbuf ; len != 0 ; lstadv(&p, &len))
+ {
+ /* see what we have */
+ if (*p == DAT_SSTRING)
+ {
+ /* it's a label string - convert to a C string */
+ bifcstr(ctx, dst, sizeof(lblbuf) - (dst - lblbuf), p + 1);
+
+ /* translate from internal to local characters */
+ for (xp = dst ; *xp != '\0' ; xp++)
+ *xp = (char)cmap_i2n(*xp);
+
+ /* set this button to point to the converted text */
+ labels[bcnt++] = dst;
+
+ /* move past this label in the button buffer */
+ dst += strlen(dst) + 1;
+ }
+ else if (*p == DAT_NUMBER)
+ {
+ int id;
+ int resid;
+
+ /* it's a standard system label ID - get the ID */
+ id = (int)osrp4s(p + 1);
+
+ /* translate it to the appropriate string resource */
+ switch(id)
+ {
+ case BIFINPDLG_LBL_OK:
+ resid = RESID_BTN_OK;
+ break;
+
+ case BIFINPDLG_LBL_CANCEL:
+ resid = RESID_BTN_CANCEL;
+ break;
+
+ case BIFINPDLG_LBL_YES:
+ resid = RESID_BTN_YES;
+ break;
+
+ case BIFINPDLG_LBL_NO:
+ resid = RESID_BTN_NO;
+ break;
+
+ default:
+ resid = 0;
+ break;
+ }
+
+ /*
+ * if we got a valid resource ID, load the resource;
+ * otherwise, skip this button
+ */
+ if (resid != 0
+ && !os_get_str_rsc(resid, dst,
+ sizeof(lblbuf) - (dst - lblbuf)))
+ {
+ /* set this button to point to the converted text */
+ labels[bcnt++] = dst;
+
+ /* move past this label in the button buffer */
+ dst += strlen(dst) + 1;
+ }
+ }
+
+ /* if we have exhausted our label array, stop now */
+ if (bcnt >= sizeof(labels)/sizeof(labels[0])
+ || dst >= lblbuf + sizeof(lblbuf))
+ break;
+ }
+ }
+
+ /* get the default response */
+ if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+ {
+ rundisc(ctx->bifcxrun);
+ default_resp = 0;
+ }
+ else
+ default_resp = runpopnum(ctx->bifcxrun);
+
+ /* get the cancel response */
+ if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+ {
+ rundisc(ctx->bifcxrun);
+ cancel_resp = 0;
+ }
+ else
+ cancel_resp = runpopnum(ctx->bifcxrun);
+
+ /* flush output before showing the dialog */
+ tioflushn(ctx->bifcxtio, 0);
+
+ /* show the dialog */
+ resp = tio_input_dialog(icon_id, prompt, std_btns,
+ (const char **)labels, bcnt,
+ default_resp, cancel_resp);
+
+ /* return the result */
+ runpnum(ctx->bifcxrun, resp);
+}
+
+/*
+ * Determine if a resource exists
+ */
+void bifresexists(bifcxdef *ctx, int argc)
+{
+ uchar *p;
+ char resname[OSFNMAX];
+ appctxdef *appctx;
+ int found;
+ runsdef val;
+
+ /* check for proper arguments */
+ bifcntargs(ctx, 1, argc);
+
+ /* get the resource name string */
+ p = runpopstr(ctx->bifcxrun);
+ bifcstr(ctx, resname, (size_t)sizeof(resname), p);
+
+ /*
+ * if we have a host application context, and it provides a resource
+ * finder function, ask the resource finder if the resource is
+ * available; otherwise, report that the resource is not loadable,
+ * since we must not be running a version of the interpreter that
+ * supports external resource loading
+ */
+ appctx = ctx->bifcxappctx;
+ found = (appctx != 0
+ && appctx->resfile_exists != 0
+ && (*appctx->resfile_exists)(appctx->resfile_exists_ctx,
+ resname, strlen(resname)));
+
+ /* push the result */
+ runpush(ctx->bifcxrun, runclog(found), &val);
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/debug.cpp b/engines/glk/tads/tads2/debug.cpp
new file mode 100644
index 0000000..94e0b37
--- /dev/null
+++ b/engines/glk/tads/tads2/debug.cpp
@@ -0,0 +1,522 @@
+/* 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/debug.h"
+#include "glk/tads/tads2/list.h"
+#include "glk/tads/tads2/run.h"
+#include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/os_glk.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+/* add a string to the history buffer */
+void dbgaddhist(dbgcxdef *ctx, char *buf, int l)
+{
+ char *p;
+ int dell;
+
+ if (ctx->dbgcxhstf + l + 1 >= ctx->dbgcxhstl)
+ {
+ /* delete first lines until we have enough space */
+ for (dell = 0, p = ctx->dbgcxhstp ; *p || dell < l ; ++p, ++dell) ;
+ if (*p) ++p;
+ memmove(ctx->dbgcxhstp, ctx->dbgcxhstp + dell,
+ (size_t)(ctx->dbgcxhstf - dell));
+ ctx->dbgcxhstf -= dell;
+ }
+ memcpy(ctx->dbgcxhstp + ctx->dbgcxhstf, buf, (size_t)l);
+ ctx->dbgcxhstf += l;
+}
+
+/* callback for dbgent - saves history line to a char buffer */
+static void dbgent1(void *ctx0, const char *str, int strl)
+{
+ char **ctx = (char **)ctx0;
+ memcpy(*ctx, str, (size_t)strl);
+ *ctx += strl;
+}
+
+void dbgent(dbgcxdef *ctx, runsdef *bp, objnum self, objnum target,
+ prpnum prop, int binum, int argc)
+{
+ dbgfdef *p;
+
+ ++(ctx->dbgcxdep); /* increment actual depth */
+ if (ctx->dbgcxfcn == DBGMAXFRAME)
+ {
+ --(ctx->dbgcxfcn); /* back to top frame */
+ memmove(ctx->dbgcxfrm, ctx->dbgcxfrm + 1,
+ (size_t)((DBGMAXFRAME - 1) * sizeof(dbgfdef)));
+ }
+ p = &ctx->dbgcxfrm[ctx->dbgcxfcn];
+ ++(ctx->dbgcxfcn); /* increment frame pointer */
+
+ p->dbgfbp = bp;
+ p->dbgfself = self;
+ p->dbgftarg = target;
+ p->dbgfprop = prop;
+ p->dbgfbif = binum;
+ p->dbgfargc = argc;
+ p->dbgffr = 0; /* no frame has yet been recorded */
+ p->dbgflin = 0;
+
+ /* save call history */
+ if (ctx->dbgcxflg & DBGCXFTRC)
+ {
+ char buf[128];
+ char *tmp;
+ int l;
+
+ tmp = buf;
+ dbgstktr(ctx, dbgent1, &tmp, -1, TRUE, FALSE);
+ if ((l = (tmp - buf)) > 0 && buf[l-1] == '\n') --l;
+ buf[l++] = '\0';
+ dbgaddhist(ctx, buf, l);
+ }
+}
+
+void dbglv(dbgcxdef *ctx, int exittype)
+{
+ --(ctx->dbgcxdep); /* decrement actual depth */
+ if (ctx->dbgcxfcn) --(ctx->dbgcxfcn); /* decrement frame pointer */
+
+ /*
+ * if we're in STEP OUT/OVER mode, and the target context is level
+ * 0, and we're now at level 0, it means that we are stepping out of
+ * a routine called directly by the system and the debugger is
+ * supposed to break when that happens -- return to single-stepping
+ * mode so that we break into the debugger the next time the system
+ * calls a method
+ */
+ if ((ctx->dbgcxflg & DBGCXFSS) != 0
+ && (ctx->dbgcxflg & DBGCXFSO) != 0
+ && ctx->dbgcxsof == 0 && ctx->dbgcxdep == 0)
+ {
+ /*
+ * stepping out/over at level 0 - go to normal single-step mode
+ * (clear the out/over flag)
+ */
+ ctx->dbgcxflg &= ~DBGCXFSO;
+ }
+
+ /* record exit in call history if appropriate */
+ if (ctx->dbgcxflg & DBGCXFTRC)
+ {
+ char buf[128];
+ char *p;
+
+ switch(exittype)
+ {
+ case DBGEXVAL:
+ if (ctx->dbgcxfcn > 1)
+ {
+ memset(buf, ' ', (size_t)(ctx->dbgcxfcn - 1));
+ dbgaddhist(ctx, buf, (int)ctx->dbgcxfcn - 1);
+ }
+ memcpy(buf, " => ", (size_t)4);
+ p = buf + 4;
+ dbgpval(ctx, ctx->dbgcxrun->runcxsp - 1,
+ dbgent1, &p, TRUE);
+ *p++ = '\0';
+ dbgaddhist(ctx, buf, (int)(p - buf));
+ break;
+
+ case DBGEXPASS:
+ memcpy(buf, " [pass]", (size_t)8);
+ dbgaddhist(ctx, buf, 8);
+ break;
+ }
+ }
+}
+
+/* get a symbol name; returns length of name */
+int dbgnam(dbgcxdef *ctx, char *outbuf, int typ, int val)
+{
+ toksdef sym;
+
+ if (!ctx->dbgcxtab || !ctx->dbgcxtab->tokthhsh)
+ {
+ memcpy(outbuf, "<NO SYMBOL TABLE>", (size_t)17);
+ return(17);
+ }
+
+ if (tokthfind((toktdef *)ctx->dbgcxtab, typ, val, &sym))
+ {
+ memcpy(outbuf, sym.toksnam, (size_t)sym.tokslen);
+ return(sym.tokslen);
+ }
+ else if (typ == TOKSTOBJ)
+ {
+ if ((mcmon)val == MCMONINV)
+ {
+ memcpy(outbuf, "<invalid object>", 16);
+ return 16;
+ }
+ else
+ {
+ sprintf(outbuf, "<object#%u>", val);
+ return strlen(outbuf);
+ }
+ }
+ else
+ {
+ memcpy(outbuf, "<UNKNOWN>", (size_t)9);
+ return(9);
+ }
+}
+
+/* send a buffer value (as from a list) to ui callback for display */
+static void dbgpbval(dbgcxdef *ctx, dattyp typ, uchar *val,
+ void (*dispfn)(void *, const char *, int),
+ void *dispctx)
+{
+ char buf[TOKNAMMAX + 1];
+ char *p = buf;
+ uint len;
+
+ switch(typ)
+ {
+ case DAT_NUMBER:
+ sprintf(buf, "%ld", (long)osrp4s(val));
+ len = strlen(buf);
+ break;
+
+ case DAT_OBJECT:
+ len = dbgnam(ctx, buf, TOKSTOBJ, osrp2(val));
+ break;
+
+ case DAT_SSTRING:
+ len = osrp2(val) - 2;
+ p = (char *)val + 2;
+ break;
+
+ case DAT_NIL:
+ p = "nil";
+ len = 3;
+ break;
+
+ case DAT_LIST:
+ (*dispfn)(dispctx, "[", 1);
+ len = osrp2(val) - 2;
+ p = (char *)val + 2;
+ while (len)
+ {
+ dbgpbval(ctx, (dattyp)*p, (uchar *)(p + 1), dispfn, dispctx);
+ lstadv((uchar **)&p, &len);
+ if (len) (*dispfn)(dispctx, " ", 1);
+ }
+ (*dispfn)(dispctx, "]", 1);
+ len = 0;
+ break;
+
+ case DAT_TRUE:
+ p = "true";
+ len = 4;
+ break;
+
+ case DAT_FNADDR:
+ (*dispfn)(dispctx, "&", 1);
+ len = dbgnam(ctx, buf, TOKSTFUNC, osrp2(val));
+ break;
+
+ case DAT_PROPNUM:
+ (*dispfn)(dispctx, "&", 1);
+ len = dbgnam(ctx, buf, TOKSTPROP, osrp2(val));
+ break;
+
+ default:
+ p = "[unknown type]";
+ len = 14;
+ break;
+ }
+
+ if (typ == DAT_SSTRING) (*dispfn)(dispctx, "'", 1);
+ if (len) (*dispfn)(dispctx, p, len);
+ if (typ == DAT_SSTRING) (*dispfn)(dispctx, "'", 1);
+}
+
+/* send a value to ui callback for display */
+void dbgpval(dbgcxdef *ctx, runsdef *val,
+ void (*dispfn)(void *ctx, const char *str, int strl),
+ void *dispctx,
+ int showtype)
+{
+ uchar buf[TOKNAMMAX + 1];
+ uint len;
+ uchar *p = buf;
+ char *typ = 0;
+
+ switch(val->runstyp)
+ {
+ case DAT_NUMBER:
+ sprintf((char *)buf, "%ld", val->runsv.runsvnum);
+ len = strlen((char *)buf);
+ typ = "number";
+ break;
+
+ case DAT_OBJECT:
+ len = dbgnam(ctx, (char *)buf, TOKSTOBJ, val->runsv.runsvobj);
+ typ = "object";
+ break;
+
+ case DAT_SSTRING:
+ len = osrp2(val->runsv.runsvstr) - 2;
+ p = val->runsv.runsvstr + 2;
+ typ = "string";
+ break;
+
+ case DAT_NIL:
+ p = (uchar *)"nil";
+ len = 3;
+ break;
+
+ case DAT_LIST:
+ if (showtype) (*dispfn)(dispctx, "list: ", 6);
+ (*dispfn)(dispctx, "[", 1);
+ len = osrp2(val->runsv.runsvstr) - 2;
+ p = val->runsv.runsvstr + 2;
+ while (len)
+ {
+ dbgpbval(ctx, (dattyp)*p, (uchar *)(p + 1), dispfn, dispctx);
+ lstadv(&p, &len);
+ if (len) (*dispfn)(dispctx, " ", 1);
+ }
+ (*dispfn)(dispctx, "]", 1);
+ len = 0;
+ break;
+
+ case DAT_TRUE:
+ p = (uchar *)"true";
+ len = 4;
+ break;
+
+ case DAT_FNADDR:
+ len = dbgnam(ctx, (char *)buf, TOKSTFUNC, val->runsv.runsvobj);
+ typ = "function pointer";
+ break;
+
+ case DAT_PROPNUM:
+ len = dbgnam(ctx, (char *)buf, TOKSTPROP, val->runsv.runsvprp);
+ typ = "property pointer";
+ break;
+
+ default:
+ p = (uchar *)"[unknown type]";
+ len = 14;
+ break;
+ }
+
+ /* show the type prefix if desired, or add a quote if it's a string */
+ if (typ && showtype)
+ {
+ /* show the type prefix */
+ (*dispfn)(dispctx, typ, (int)strlen(typ));
+ (*dispfn)(dispctx, ": ", 2);
+ }
+ else if (val->runstyp == DAT_SSTRING)
+ {
+ /* it's a string, and we're not showing a type - add a quote */
+ (*dispfn)(dispctx, "'", 1);
+ }
+
+ /*
+ * if possible, null-terminate the buffer - do this only if the
+ * length is actually within the buffer, which won't be the case if
+ * the text comes from someplace outside the buffer (which is the
+ * case if it's a string, for example)
+ */
+ if (len < sizeof(buf))
+ buf[len] = '\0';
+
+ /* display a "&" prefix if it's an address of some kind */
+ if (val->runstyp == DAT_PROPNUM || val->runstyp == DAT_FNADDR)
+ (*dispfn)(dispctx, "&", 1);
+
+ /* display the text */
+ if (len != 0)
+ (*dispfn)(dispctx, (char *)p, len);
+
+ /* add a closing quote if it's a string and we showed an open quote */
+ if (val->runstyp == DAT_SSTRING && !(typ && showtype))
+ (*dispfn)(dispctx, "'", 1);
+}
+
+void dbgstktr(dbgcxdef *ctx,
+ void (*dispfn)(void *ctx, const char *str, int strl),
+ void *dispctx,
+ int level, int toponly, int include_markers)
+{
+ dbgfdef *f;
+ int i;
+ int j;
+ int k;
+ char buf[128];
+ char *p;
+ char c;
+
+ for (i = ctx->dbgcxfcn, j = ctx->dbgcxdep, f = &ctx->dbgcxfrm[i-1]
+ ; i ; --f, --j, --i)
+ {
+ p = buf;
+ if (toponly)
+ {
+ int v = (i < 50 ? i : 50);
+
+ if (v > 1)
+ {
+ memset(buf, ' ', (size_t)(v - 1));
+ dbgaddhist(ctx, buf, v-1);
+ }
+ }
+ else if (include_markers)
+ {
+ c = (i == level + 1 ? '*' : ' ');
+ sprintf(buf, "%3d%c ", j, c);
+ p += 4;
+ }
+
+ if (f->dbgftarg == MCMONINV)
+ p += dbgnam(ctx, p, TOKSTBIFN, f->dbgfbif);
+ else
+ p += dbgnam(ctx, p,
+ (f->dbgfself == MCMONINV ? TOKSTFUNC : TOKSTOBJ),
+ (int)f->dbgftarg);
+
+ if (f->dbgfself != MCMONINV && f->dbgfself != f->dbgftarg)
+ {
+ memcpy(p, "<self=", (size_t)6);
+ p += 6;
+ p += dbgnam(ctx, p, TOKSTOBJ, (int)f->dbgfself);
+ *p++ = '>';
+ }
+ if (f->dbgfprop)
+ {
+ *p++ = '.';
+ p += dbgnam(ctx, p, TOKSTPROP, (int)f->dbgfprop);
+ }
+
+ /* display what we have so far */
+ *p++ = '\0';
+ (*dispfn)(dispctx, buf, (int)strlen(buf));
+
+ /* display arguments if there are any */
+ if (f->dbgfself == MCMONINV || f->dbgfargc != 0)
+ {
+ (*dispfn)(dispctx, "(", 1);
+ for (k = 0 ; k < f->dbgfargc ; ++k)
+ {
+ dbgpval(ctx, f->dbgfbp - k - 2, dispfn, dispctx, FALSE);
+ if (k + 1 < f->dbgfargc) (*dispfn)(dispctx, ", ", 2);
+ }
+ (*dispfn)(dispctx, ")", 1);
+ }
+
+ /* send out a newline, then move on to next frame */
+ (*dispfn)(dispctx, "\n", 1);
+
+ /* we're done if doing one function only */
+ if (toponly) break;
+ }
+}
+
+static void dbgdsdisp(void *ctx, const char *buf, int bufl)
+{
+ if (buf[0] == '\n')
+ tioflush((tiocxdef *)ctx);
+ else
+ tioputslen((tiocxdef *)ctx, (char *)buf, bufl);
+}
+
+/* dump the stack */
+void dbgds(dbgcxdef *ctx)
+{
+ /* don't do stack dumps if we're running from the debugger command line */
+ if (ctx->dbgcxflg & DBGCXFIND) return;
+
+ tioflush(ctx->dbgcxtio);
+ tioshow(ctx->dbgcxtio);
+ dbgstktr(ctx, dbgdsdisp, ctx->dbgcxtio, -1, FALSE, TRUE);
+ tioflush(ctx->dbgcxtio);
+ ctx->dbgcxfcn = ctx->dbgcxdep = 0;
+}
+
+/* get information about the currently executing source line */
+void dbglget(dbgcxdef *ctx, uchar *buf)
+{
+ dbglgetlvl(ctx, buf, 0);
+}
+
+/*
+ * Get information about a source line at a particular stack level into
+ * the buffer; leaves out frame info. level 0 is the currently
+ * executing line, 1 is the first enclosing level, and so on.
+ */
+int dbglgetlvl(dbgcxdef *ctx, uchar *buf, int level)
+{
+ uchar *linrec;
+ uchar *obj;
+ dbgfdef *fr;
+
+ /* make sure the level is valid */
+ if (level > ctx->dbgcxfcn - 1)
+ return 1;
+
+ /* get the frame at the given level */
+ fr = &ctx->dbgcxfrm[ctx->dbgcxfcn - level - 1];
+
+ /* if we're in an intrinsic, go to enclosing frame */
+ if (fr->dbgftarg == MCMONINV) --fr;
+
+ /* make sure we've encountered an OPCLINE in this frame */
+ if (fr->dbgflin == 0)
+ return 1;
+
+ /* we need to read from the target object - lock it */
+ obj = mcmlck(ctx->dbgcxmem, (mcmon)fr->dbgftarg);
+
+ linrec = obj + fr->dbgflin;
+ memcpy(buf, linrec + 3, (size_t)(*linrec - 3));
+
+ /* no longer need the target object locked */
+ mcmunlck(ctx->dbgcxmem, (mcmon)fr->dbgftarg);
+
+ /* success */
+ return 0;
+}
+
+/* tell the line source the location of the current line being compiled */
+void dbgclin(tokcxdef *tokctx, objnum objn, uint ofs)
+{
+ uchar buf[4];
+
+ /* package the information and send it to the line source */
+ oswp2(buf, objn);
+ oswp2(buf + 2, ofs);
+ lincmpinf(tokctx->tokcxlin, buf);
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/error.cpp b/engines/glk/tads/tads2/error.cpp
index 793ddcc..eeb59cd 100644
--- a/engines/glk/tads/tads2/error.cpp
+++ b/engines/glk/tads/tads2/error.cpp
@@ -21,11 +21,185 @@
*/
#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/error_handling.h"
+#include "glk/tads/tads2/tokenizer.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
+/* format an error message, sprintf-style, using an erradef array */
+int errfmt(char *outbuf, int outbufl, const char *fmt, int argc, const erradef *argv) {
+ int outlen = 0;
+ int argi = 0;
+ int len = 0;
+ char buf[20];
+ const char *p = nullptr;
+ char fmtchar;
+
+ while (*fmt)
+ {
+ 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;
+ break;
+
+ case '%':
+ p = "%";
+ len = 1;
+ break;
+
+ case 'd':
+ sprintf(buf, "%d", argv[argi].erraint);
+ len = strlen(buf);
+ p = buf;
+ break;
+
+ case 's':
+ p = argv[argi].errastr;
+ len = strlen(p);
+ break;
+
+ case 't':
+ {
+ int i;
+ static struct
+ {
+ int tokid;
+ char *toknam;
+ } toklist[] =
+ {
+ { TOKTSEM, "semicolon" },
+ { TOKTCOLON, "colon" },
+ { TOKTFUNCTION, "\"function\"" },
+ { TOKTCOMMA, "comma" },
+ { TOKTLBRACE, "left brace ('{')" },
+ { TOKTRPAR, "right paren (')')" },
+ { TOKTRBRACK, "right square bracket (']')" },
+ { TOKTWHILE, "\"while\"" },
+ { TOKTLPAR, "left paren ('(')" },
+ { TOKTEQ, "'='" },
+ { 0, (char *)0 }
+ };
+
+ for (i = 0 ; toklist[i].toknam ; ++i)
+ {
+ if (toklist[i].tokid == argv[argi].erraint)
+ {
+ p = toklist[i].toknam;
+ break;
+ }
+ }
+ if (!toklist[i].toknam)
+ p = "<unknown token>";
+ 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)
+ {
+ if (outbufl >= len)
+ {
+ memcpy(outbuf, p, (size_t)len);
+ outbufl -= len;
+ outbuf += len;
+ }
+ else if (outbufl)
+ {
+ memcpy(outbuf, p, (size_t)outbufl);
+ outbufl = 0;
+ }
+ outlen += len;
+ }
+ ++fmt;
+ }
+
+ if (outbufl) *outbuf++ = '\0';
+ return(outlen);
+}
+
+#ifdef DEBUG
+void errjmp(buf, e)
+jmp_buf buf;
+int e;
+{
+ longjmp(buf, e);
+}
+#endif /* DEBUG */
+
+
+
+#ifdef ERR_NO_MACRO
+
+/* base error signal function */
+void errsign(errcxdef *ctx, int e) {
+ ctx->errcxofs = 0;
+#if 0
+ longjmp(ctx->errcxptr->errbuf, e);
+#else
+ error("errsign");
+#endif
+}
+
+/* enter a string argument */
+char *errstr(errcxdef *ctx, char *str, int len) {
+ char *ret = &ctx->errcxbuf[ctx->errcxofs];
+
+ memcpy(ret, str, (size_t)len);
+ ret[len] = '\0';
+ ctx->errcxofs += len + 1;
+ return(ret);
+}
+
+/* log an error: base function */
+void errlogn(errcxdef *ctx, int err) {
+ error("errlogn");
+}
+
+#endif /* ERR_NO_MACRO */
} // End of namespace TADS2
} // End of namespace TADS
diff --git a/engines/glk/tads/tads2/error_handling.cpp b/engines/glk/tads/tads2/error_handling.cpp
index a2300ee..b71fc8c 100644
--- a/engines/glk/tads/tads2/error_handling.cpp
+++ b/engines/glk/tads/tads2/error_handling.cpp
@@ -20,13 +20,190 @@
*
*/
-#include "error_handling.h"
+#include "glk/tads/tads2/error_handling.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
+/* format an error message, sprintf-style, using an erradef array */
+int errfmt(char *outbuf, int outbufl, char *fmt, int argc, erradef *argv)
+{
+ int outlen = 0;
+ int argi = 0;
+ int len;
+ char buf[20];
+ 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;
+}
+
+#if defined(DEBUG) && !defined(ERR_NO_MACRO)
+void errjmp(jmp_buf buf, int e)
+{
+ longjmp(buf, e);
+}
+#endif /* DEBUG */
+
+
+
+#ifdef ERR_NO_MACRO
+
+/* base error signal function */
+void errsign(errcxdef *ctx, int e, char *facility)
+{
+ strncpy(ctx->errcxptr->errfac, facility, ERRFACMAX);
+ ctx->errcxptr->errfac[ERRFACMAX] = '\0';
+ ctx->errcxofs = 0;
+#if 0
+ longjmp(ctx->errcxptr->errbuf, e);
+#else
+ error("Error - %s", facility);
+#endif
+}
+
+/* signal an error with no arguments */
+void errsigf(errcxdef *ctx, char *facility, int e)
+{
+ errargc(ctx, 0);
+ errsign(ctx, e, facility);
+}
+
+/* enter a string argument */
+char *errstr(errcxdef *ctx, const char *str, int len)
+{
+ char *ret = &ctx->errcxbuf[ctx->errcxofs];
+
+ memcpy(ret, str, (size_t)len);
+ ret[len] = '\0';
+ ctx->errcxofs += len + 1;
+ return(ret);
+}
+
+/* resignal current error */
+void errrse1(errcxdef *ctx, errdef *fr)
+{
+ errargc(ctx, fr->erraac);
+ memcpy(ctx->errcxptr->erraav, fr->erraav,
+ (size_t)(fr->erraac * sizeof(erradef)));
+ errsign(ctx, fr->errcode, fr->errfac);
+}
+
+/* log an error: base function */
+void errlogn(errcxdef *ctx, int err, char *facility)
+{
+ ctx->errcxofs = 0;
+ (*ctx->errcxlog)(ctx->errcxlgc, facility, err, ctx->errcxptr->erraac,
+ ctx->errcxptr->erraav);
+}
+
+/* log an error with no arguments */
+void errlogf(errcxdef *ctx, char *facility, int err)
+{
+ errargc(ctx, 0);
+ errlogn(ctx, err, facility);
+}
+
+#endif /* ERR_NO_MACRO */
+
} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/os.cpp b/engines/glk/tads/tads2/os.cpp
index e51a4d9..219bd3b 100644
--- a/engines/glk/tads/tads2/os.cpp
+++ b/engines/glk/tads/tads2/os.cpp
@@ -21,333 +21,16 @@
*/
#include "glk/tads/tads2/os.h"
+#include "common/system.h"
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');
- Common::fill(&status_right[0], &status_right[OSS_STATUS_STRING_LEN], '\0');
+long os_get_sys_clock_ms() {
+ return g_system->getMillis();
}
-void OS::os_terminate(int rc) {
- glk_exit();
-}
-
-uint OS::oss_convert_prompt_type(int type) {
- if (type == OS_AFP_OPEN)
- return filemode_Read;
- return filemode_ReadWrite;
-}
-
-uint OS::oss_convert_file_type(int type) {
- if (type == OSFTSAVE)
- return fileusage_SavedGame;
- if (type == OSFTLOG || type == OSFTTEXT)
- return fileusage_Transcript;
- return fileusage_Data;
-}
-
-uint OS::oss_convert_fileref_to_string(frefid_t file_to_convert, char *buffer, int buf_len) {
-#ifdef TODO
- char temp_string[32];
- uint value, i = 0, digit,
- digit_flag = false, // Have we put a digit in the string yet?
- divisor = 1e9; // The max 32-bit integer is 4294967295
-
- // This could probably be done by using sprintf("%s%ld%s") but I don't want to risk it
- value = (uint)file_to_convert;
- while (divisor != 1) {
- digit = (char)(value / divisor);
- if (digit != 0 || digit_flag) { // This lets us handle, eg, 102
- temp_string[i++] = digit + '0';
- digit_flag = true;
- }
- value = value % divisor;
- divisor /= 10;
- }
- temp_string[i++] = (char)value + '0';
- temp_string[i] = 0;
- if (strlen(temp_string) + strlen(OSS_FILEREF_STRING_PREFIX) +
- strlen(OSS_FILEREF_STRING_SUFFIX) > (size_t)buf_len)
- return false;
- sprintf(buffer, "%s%s%s", OSS_FILEREF_STRING_PREFIX,
- temp_string, OSS_FILEREF_STRING_SUFFIX);
-#endif
- return true;
-}
-
-frefid_t OS::oss_convert_string_to_fileref(char *buffer, uint usage) {
-#ifdef TODO
- char temp_string[32];
- uint value = 0, i, multiplier = 1;
-
- // Does the buffer contain a hashed fileref?
- if (oss_is_string_a_fileref(buffer)) {
- // If so, we need only decode the string in the middle and return its value
- strcpy(temp_string, buffer + strlen(OSS_FILEREF_STRING_PREFIX));
- i = strlen(temp_string) - strlen(OSS_FILEREF_STRING_SUFFIX);
- temp_string[i] = 0;
- while (i != 0) {
- i--;
- value += ((uint)(temp_string[i] - '0') * multiplier);
- multiplier *= 10;
- }
- return ((frefid_t)value);
- }
-
- // If not, return the new fileref
- return (glk_fileref_create_by_name(usage, os_get_root_name(buffer), 0));
-#else
- return nullptr;
-#endif
-}
-
-bool OS::oss_is_string_a_fileref(char *buffer) {
- if ((strncmp(buffer, OSS_FILEREF_STRING_PREFIX,
- strlen(OSS_FILEREF_STRING_PREFIX)) == 0) &&
- (strncmp(buffer + strlen(buffer) - strlen(OSS_FILEREF_STRING_SUFFIX),
- OSS_FILEREF_STRING_SUFFIX,
- strlen(OSS_FILEREF_STRING_SUFFIX)) == 0))
- return true;
- return false;
-}
-
-unsigned char OS::oss_convert_keystroke_to_tads(uint key) {
- // Characters 0 - 255 we return per normal */
- if (key <= 255)
- return ((unsigned char)key);
- switch (key) {
- case keycode_Up:
- return CMD_UP;
- case keycode_Down:
- return CMD_DOWN;
- case keycode_Left:
- return CMD_LEFT;
- case keycode_Right:
- return CMD_RIGHT;
- case keycode_PageUp:
- return CMD_PGUP;
- case keycode_PageDown:
- return CMD_PGDN;
- case keycode_Home:
- return CMD_HOME;
- case keycode_End:
- return CMD_END;
- case keycode_Func1:
- return CMD_F1;
- case keycode_Func2:
- return CMD_F2;
- case keycode_Func3:
- return CMD_F3;
- case keycode_Func4:
- return CMD_F4;
- case keycode_Func5:
- return CMD_F5;
- case keycode_Func6:
- return CMD_F6;
- case keycode_Func7:
- return CMD_F7;
- case keycode_Func8:
- return CMD_F8;
- case keycode_Func9:
- return CMD_F9;
- case keycode_Func10:
- return CMD_F10;
- default:
- return 0;
- }
-}
-
-bool OS::oss_check_path(char *filename) {
- return false;
-}
-
-void OS::oss_revert_path() {
- // No implementation
-}
-
-osfildef *OS::oss_open_stream(char *buffer, uint tadsusage, uint tbusage,
- uint fmode, uint rock) {
- frefid_t fileref;
- strid_t osf;
- int changed_dirs;
-
- fileref = oss_convert_string_to_fileref(buffer,
- oss_convert_file_type(tadsusage) | tbusage);
- changed_dirs = oss_check_path(buffer);
- osf = glk_stream_open_file(fileref, (FileMode)fmode, rock);
- if (changed_dirs)
- oss_revert_path();
- return *osf;
-}
-
-osfildef *OS::osfoprb(const char *fname, uint typ) {
- Common::File *f = new Common::File();
- if (f->open(fname))
- return f;
-
- f->close();
- delete f;
- return nullptr;
-}
-
-void OS::os_gen_charmap_filename(char *filename, const char *internal_id,
- const char *argv0) {
- const char *p;
- const char *rootname;
- size_t pathlen;
-
- // find the path prefix of the original executable filename
- for (p = rootname = argv0; *p != '\0'; ++p) {
- if (*p == '/' || *p == '\\' || *p == ':')
- rootname = p + 1;
- }
-
- // copy the path prefix
- pathlen = rootname - argv0;
- memcpy(filename, argv0, pathlen);
-
- // if there's no trailing backslash, add one
- if (pathlen == 0 || filename[pathlen - 1] != '\\')
- filename[pathlen++] = '\\';
-
- // add "win_", plus the character set ID, plus the extension
- strcpy(filename + pathlen, "win_");
- strcat(filename + pathlen, internal_id);
- strcat(filename + pathlen, ".tcp");
-}
-
-void OS::oss_put_string_with_hilite(winid_t win, const char *str, size_t len) {
- glk_set_window(win);
- glk_put_buffer(str, len);
-}
-
-void OS::oss_draw_status_line(void) {
- uint width, height, division;
-
- if (status_win == nullptr) return; // In case this is a CheapGlk port
-
- glk_window_get_size(status_win, &width, &height);
- if (height == 0) return; // Don't bother if status is invisible
- division = width - strlen(status_right) - 1;
- glk_set_window(status_win);
- glk_window_clear(status_win);
- oss_put_string_with_hilite(status_win, status_left, strlen(status_left));
- glk_window_move_cursor(status_win, division, 0);
- glk_put_string(status_right);
-}
-
-void OS::oss_change_status_string(char *dest, const char *src, size_t len) {
- if (len > OSS_STATUS_STRING_LEN - 1)
- len = OSS_STATUS_STRING_LEN - 1;
- memcpy(dest, src, len);
- dest[len] = '\0';
-}
-
-void OS::oss_change_status_left(const char *str, size_t len) {
- oss_change_status_string(status_left, str, len);
- oss_draw_status_line();
-}
-
-void OS::oss_change_status_right(const char *str) {
- oss_change_status_string(status_right, str, strlen(str));
- oss_draw_status_line();
-}
-
-int OS::memicmp(const char *s1, const char *s2, int len) {
- char *x1, *x2;
- int i, result;
-
- x1 = (char *)malloc(len); x2 = (char *)malloc(len);
-
- if (!x1 || !x2) {
- glk_set_window(story_win);
- glk_put_string("memicmp has run out of memory. Quitting.\n");
- os_waitc();
- glk_exit();
- }
-
- for (i = 0; i < len; i++) {
- if (Common::isUpper(s1[i]))
- x1[i] = glk_char_to_lower((unsigned char)s1[i]);
- else x1[i] = s1[i];
-
- if (Common::isUpper(s2[i]))
- x2[i] = glk_char_to_lower((unsigned char)s2[i]);
- else x2[i] = s2[i];
- }
-
- result = memcmp(x1, x2, len);
- free(x1);
- free(x2);
- return result;
-}
-
-void OS::os_flush() {
- glk_tick();
-}
-
-void OS::os_print(const char *str, size_t len) {
- int current_status_mode;
-
- // Decide what to do based on our status mode
- current_status_mode = os_get_status();
- if (current_status_mode == OSS_STATUS_MODE_STORY) {
- oss_put_string_with_hilite(story_win, str, len);
- } else if (current_status_mode == OSS_STATUS_MODE_STATUS) {
- const char *p;
- size_t rem;
-
- // The string requires some fiddling for the status window
- for (p = str, rem = len; rem != 0 && *p == '\n'; p++, --rem);
- if (rem != 0 && p[rem - 1] == '\n')
- --rem;
-
- // if that leaves anything, update the statusline
- if (rem != 0)
- oss_change_status_left(p, rem);
- }
-}
-
-void OS::os_expause() {
- os_printz("(Strike any key to exit...)");
- os_flush();
- os_waitc();
-}
-
-int OS::oss_getc_from_window(winid_t win) {
- static unsigned char buffered_char = 0;
- int i;
- event_t ev;
-
- if (buffered_char != 0) {
- i = (int)buffered_char;
- buffered_char = 0;
- return i;
- }
- glk_request_char_event(win);
- do {
- glk_select(&ev);
- if (ev.type == evtype_Arrange)
- oss_draw_status_line();
- } while (ev.type != evtype_CharInput);
- if (ev.val1 == keycode_Return)
- ev.val1 = '\n';
- else if (ev.val1 == keycode_Tab)
- ev.val1 = '\t';
- if (ev.val1 <= 255)
- return ((int)ev.val1);
-
- // We got a special character, so handle it appropriately
- buffered_char = oss_convert_keystroke_to_tads(ev.val1);
- return 0;
-}
-#endif
-
} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/string_resources.h b/engines/glk/tads/tads2/string_resources.h
new file mode 100644
index 0000000..76f245e
--- /dev/null
+++ b/engines/glk/tads/tads2/string_resources.h
@@ -0,0 +1,69 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef GLK_TADS_TADS2_STRING_RESOURCES
+#define GLK_TADS_TADS2_STRING_RESOURCES
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/*
+ * Dialog buttons. These provide the text for standard buttons in
+ * dialogs created with os_input_dialog().
+ *
+ * These labels can use "&" to indicate a shortcut letter, per the
+ * normal os_input_dialog() interface; for example, if the Yes button
+ * label is "&Yes", the button has the shortcut letter "Y".
+ *
+ * The text of these buttons may vary by system, since these should
+ * conform to local conventions where there are local conventions. In
+ * addition, of course, these strings will vary by language.
+ */
+
+/* OK and Cancel buttons */
+#define RESID_BTN_OK 1
+#define RESID_BTN_CANCEL 2
+
+/* "Yes" and "No" buttons */
+#define RESID_BTN_YES 3
+#define RESID_BTN_NO 4
+
+/*
+ * Reply strings for the yorn() built-in function. These strings are
+ * regular expressions as matched by the regex.h functions. For
+ * English, for example, the "yes" string would be "[Yy].*" and the "no"
+ * string would be "[Nn].*". For German, it might be desirable to
+ * accept both "Ja" and "Yes", so the "Yes" string might be "[JjYy].*".
+ *
+ * It's not necessary in these patterns to consider leading spaces,
+ * since the yorn() function will skip any leading spaces before
+ * performing the pattern match.
+ */
+#define RESID_YORN_YES 5
+#define RESID_YORN_NO 6
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
Commit: 3d9e03af554814bee10f112c2efa0b7f0b722489
https://github.com/scummvm/scummvm/commit/3d9e03af554814bee10f112c2efa0b7f0b722489
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00
Commit Message:
GLK: TADS2: Soooo much more implementation
Changed paths:
A engines/glk/tads/os_buffer.cpp
A engines/glk/tads/os_buffer.h
A engines/glk/tads/os_frob_tads.cpp
A engines/glk/tads/os_frob_tads.h
A engines/glk/tads/os_glk.cpp
A engines/glk/tads/tads2/command_line.cpp
A engines/glk/tads/tads2/command_line.h
A engines/glk/tads/tads2/error_message.cpp
A engines/glk/tads/tads2/execute_command.cpp
A engines/glk/tads/tads2/ltk.cpp
A engines/glk/tads/tads2/ltk.h
A engines/glk/tads/tads2/play.cpp
A engines/glk/tads/tads2/play.h
A engines/glk/tads/tads2/qa_scriptor.cpp
A engines/glk/tads/tads2/runstat.cpp
A engines/glk/tads/tads2/runtime_app.cpp
A engines/glk/tads/tads2/runtime_app.h
A engines/glk/tads/tads2/runtime_driver.cpp
A engines/glk/tads/tads2/tokenizer_hash.cpp
A engines/glk/tads/tads2/vocabulary_parser.cpp
R engines/glk/tads/osfrobtads.cpp
R engines/glk/tads/osfrobtads.h
engines/glk/glk.h
engines/glk/glulxe/glulxe.cpp
engines/glk/glulxe/glulxe.h
engines/glk/module.mk
engines/glk/tads/os_glk.h
engines/glk/tads/tads.cpp
engines/glk/tads/tads.h
engines/glk/tads/tads2/appctx.h
engines/glk/tads/tads2/built_in.h
engines/glk/tads/tads2/character_map.cpp
engines/glk/tads/tads2/error_handling.h
engines/glk/tads/tads2/file_io.cpp
engines/glk/tads/tads2/file_io.h
engines/glk/tads/tads2/lib.h
engines/glk/tads/tads2/memory_cache.h
engines/glk/tads/tads2/memory_cache_swap.h
engines/glk/tads/tads2/os.cpp
engines/glk/tads/tads2/os.h
engines/glk/tads/tads2/post_compilation.cpp
engines/glk/tads/tads2/post_compilation.h
engines/glk/tads/tads2/run.h
engines/glk/tads/tads2/tads2.cpp
engines/glk/tads/tads2/tads2.h
engines/glk/tads/tads2/tokenizer.cpp
diff --git a/engines/glk/glk.h b/engines/glk/glk.h
index bebb482..6b95eb2 100644
--- a/engines/glk/glk.h
+++ b/engines/glk/glk.h
@@ -221,6 +221,11 @@ public:
* Generate a beep
*/
void beep();
+
+ /**
+ * Get a random number
+ */
+ uint getRandomNumber(uint max) { return _random.getRandomNumber(max); }
};
extern GlkEngine *g_vm;
diff --git a/engines/glk/glulxe/glulxe.cpp b/engines/glk/glulxe/glulxe.cpp
index cbe09ce..ba67dea 100644
--- a/engines/glk/glulxe/glulxe.cpp
+++ b/engines/glk/glulxe/glulxe.cpp
@@ -29,7 +29,7 @@ namespace Glulxe {
Glulxe *g_vm;
-Glulxe::Glulxe(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc), _random("glulxe"),
+Glulxe::Glulxe(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc),
vm_exited_cleanly(false), gamefile_start(0), gamefile_len(0), memmap(nullptr), stack(nullptr),
ramstart(0), endgamefile(0), origendmem(0), stacksize(0), startfuncaddr(0), checksum(0),
stackptr(0), frameptr(0), pc(0), prevpc(0), origstringtable(0), stringtable(0), valstackbase(0),
diff --git a/engines/glk/glulxe/glulxe.h b/engines/glk/glulxe/glulxe.h
index c0008e8..56a912e 100644
--- a/engines/glk/glulxe/glulxe.h
+++ b/engines/glk/glulxe/glulxe.h
@@ -82,7 +82,6 @@ private:
* to autorestore an initial game state, if the library has that capability. (Currently, only iosglk does.)
*/
void(*library_autorestore_hook)(void);
- Common::RandomSource _random;
/**@}*/
diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index 810c493..1ce2458 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -92,29 +92,42 @@ MODULE_OBJS := \
scott/detection.o \
scott/scott.o \
tads/detection.o \
- tads/osfrobtads.o \
+ tads/os_buffer.o \
+ tads/os_glk.o \
+ tads/os_frob_tads.o \
tads/tads.o \
tads/tads2/built_in.o \
tads/tads2/character_map.o \
+ tads/tads2/command_line.o \
tads/tads2/data.o \
tads/tads2/debug.o \
tads/tads2/error.o \
tads/tads2/error_handling.o \
+ tads/tads2/error_message.o \
+ tads/tads2/execute_command.o \
tads/tads2/file_io.o \
tads/tads2/line_source_file.o \
tads/tads2/list.o \
+ tads/tads2/ltk.o \
tads/tads2/memory_cache.o \
tads/tads2/memory_cache_heap.o \
tads/tads2/memory_cache_swap.o \
tads/tads2/object.o \
tads/tads2/os.o \
tads/tads2/output.o \
+ tads/tads2/play.o \
tads/tads2/post_compilation.o \
+ tads/tads2/qa_scriptor.o \
tads/tads2/regex.o \
tads/tads2/run.o \
+ tads/tads2/runstat.o \
+ tads/tads2/runtime_app.o \
+ tads/tads2/runtime_driver.o \
tads/tads2/tads2.o \
tads/tads2/tokenizer.o \
+ tads/tads2/tokenizer_hash.o \
tads/tads2/vocabulary.o \
+ tads/tads2/vocabulary_parser.o \
tads/tads3/tads3.o
# This module can be built as a plugin
diff --git a/engines/glk/tads/os_buffer.cpp b/engines/glk/tads/os_buffer.cpp
new file mode 100644
index 0000000..0f00e9d
--- /dev/null
+++ b/engines/glk/tads/os_buffer.cpp
@@ -0,0 +1,103 @@
+/* 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/os_buffer.h"
+#include "glk/tads/tads.h"
+
+namespace Glk {
+namespace TADS {
+
+extern winid_t mainwin;
+
+#ifndef GLK_MODULE_UNICODE
+
+void os_put_buffer(const char *buf, size_t len) {
+ g_vm->glk_put_buffer(buf, len);
+}
+
+void os_get_buffer (char *buf, size_t len, size_t init)
+{
+ g_vm->glk_request_line_event(mainwin, buf, len - 1, init);
+}
+
+char *os_fill_buffer(char *buf, size_t len)
+{
+ buf[len] = '\0';
+ return buf;
+}
+
+#else
+
+static uint32 *input = 0;
+static uint max = 0;
+
+extern uint os_parse_chars(const char *buf, uint buflen, uint32 *out, uint outlen);
+
+extern uint os_prepare_chars(uint32 *buf, uint buflen, char *out, uint outlen);
+
+void os_put_buffer(const char *buf, size_t len) {
+ uint *out;
+ uint outlen;
+
+ if (!len)
+ return;
+
+ out = new uint32[len + 1];
+ if (!out)
+ return;
+ outlen = len;
+
+ outlen = os_parse_chars(buf, len, out, outlen);
+
+ if (outlen)
+ g_vm->glk_put_buffer_uni(out, outlen);
+ else
+ g_vm->glk_put_buffer(buf, len);
+
+ delete[] out;
+}
+
+void os_get_buffer(char *buf, size_t len, size_t init) {
+ input = (uint32 *)malloc(sizeof(uint) * (len + 1));
+ max = len;
+
+ if (init)
+ os_parse_chars(buf, init + 1, input, len);
+
+ g_vm->glk_request_line_event_uni(mainwin, input, len - 1, init);
+}
+
+char *os_fill_buffer(char *buf, size_t len) {
+ uint res = os_prepare_chars(input, len, buf, max);
+ buf[res] = '\0';
+
+ free(input);
+ input = nullptr;
+ max = 0;
+
+ return buf;
+}
+
+#endif /* GLK_MODULE_UNICODE */
+
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/os_buffer.h b/engines/glk/tads/os_buffer.h
new file mode 100644
index 0000000..3fc2209
--- /dev/null
+++ b/engines/glk/tads/os_buffer.h
@@ -0,0 +1,48 @@
+/* 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.
+ */
+
+/* Text IO Buffering
+ */
+
+#ifndef GLK_TADS_OS_BUFFER
+#define GLK_TADS_OS_BUFFER
+
+namespace Glk {
+namespace TADS {
+
+extern void os_put_buffer(const char *buf, size_t len);
+
+extern void os_get_buffer(char *buf, size_t len, size_t init);
+
+extern char *os_fill_buffer(char *buf, size_t len);
+
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/os_frob_tads.cpp b/engines/glk/tads/os_frob_tads.cpp
new file mode 100644
index 0000000..8c8d3f1
--- /dev/null
+++ b/engines/glk/tads/os_frob_tads.cpp
@@ -0,0 +1,476 @@
+/* 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/os_frob_tads.h"
+#include "common/file.h"
+#include "common/memstream.h"
+
+namespace Glk {
+namespace TADS {
+
+static osfildef *openForReading(const char *fname) {
+ Common::File f;
+ if (f.open(fname))
+ return f.readStream(f.size());
+
+ Common::InSaveFile *save = g_system->getSavefileManager()->openForLoading(fname);
+ return save;
+}
+
+static osfildef *openForWriting(const char *fname) {
+ return g_system->getSavefileManager()->openForSaving(fname, false);
+}
+
+int osfacc(const char *fname) {
+ return Common::File::exists(fname) ? 1 : 0;
+}
+
+osfildef *osfoprt(const char *fname, os_filetype_t typ) {
+ return openForReading(fname);
+}
+
+osfildef *osfoprtv(const char *fname, os_filetype_t typ) {
+ return openForReading(fname);
+}
+
+osfildef *osfopwt(const char *fname, os_filetype_t typ) {
+ return openForWriting(fname);
+}
+
+osfildef *osfoprwt(const char *fname, os_filetype_t typ) {
+ warning("ScummVM files can't be opened for both reading and writing simultaneously");
+ return openForWriting(fname);
+}
+
+osfildef *osfoprwtt(const char *fname, os_filetype_t typ) {
+ warning("ScummVM files can't be opened for both reading and writing simultaneously");
+ return openForWriting(fname);
+}
+
+osfildef *osfopwb(const char *fname, os_filetype_t typ) {
+ return openForWriting(fname);
+}
+
+osfildef *osfoprs(const char *fname, os_filetype_t typ) {
+ return openForReading(fname);
+}
+
+osfildef *osfoprb(const char *fname, os_filetype_t typ) {
+ return openForReading(fname);
+}
+
+osfildef *osfoprbv(const char *fname, os_filetype_t typ) {
+ return openForReading(fname);
+}
+
+osfildef *osfoprwb(const char *fname, os_filetype_t typ) {
+ warning("ScummVM files can't be opened for both reading and writing simultaneously");
+ return openForWriting(fname);
+}
+
+osfildef *osfoprwtb(const char *fname, os_filetype_t typ) {
+ warning("ScummVM files can't be opened for both reading and writing simultaneously");
+ return openForWriting(fname);
+}
+
+osfildef *osfdup(osfildef *orig, const char *mode) {
+ Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(orig);
+ int32 currPos = rs->pos();
+
+ rs->seek(0);
+ osfildef *result = rs->readStream(rs->size());
+ rs->seek(currPos);
+
+ return result;
+}
+
+void os_settype(const char *f, os_filetype_t typ) {
+ // No implementation
+}
+
+char *osfgets(char *buf, size_t count, osfildef *fp) {
+ Common::ReadStream *rs = dynamic_cast<Common::ReadStream *>(fp);
+ char *ptr = buf;
+ char c;
+ while (!rs->eos() && --count > 0) {
+ c = rs->readByte();
+ if (c == '\n' || c == '\0')
+ break;
+ *ptr++ = c;
+ }
+
+ *ptr++ = '\0';
+ return buf;
+}
+
+int osfputs(const char *str, osfildef *fp) {
+ return dynamic_cast<Common::WriteStream *>(fp)->write(str, strlen(str)) == strlen(str) ? 0 : -1;
+}
+
+void os_fprintz(osfildef *fp, const char *str) {
+ dynamic_cast<Common::WriteStream *>(fp)->write(str, strlen(str));
+}
+
+void os_fprint(osfildef *fp, const char *str, size_t len) {
+ Common::String s(str, str + MIN(len, strlen(str)));
+ dynamic_cast<Common::WriteStream *>(fp)->write(s.c_str(), s.size());
+}
+
+int osfwb(osfildef *fp, const void *buf, size_t bufl) {
+ return dynamic_cast<Common::WriteStream *>(fp)->write(buf, bufl) == bufl ? 0 : 1;
+}
+
+int osfflush(osfildef *fp) {
+ return dynamic_cast<Common::WriteStream *>(fp)->flush() ? 0 : 1;
+}
+
+int osfgetc(osfildef *fp) {
+ return dynamic_cast<Common::ReadStream *>(fp)->readByte();
+}
+
+int osfrb(osfildef *fp, void *buf, size_t bufl) {
+ return dynamic_cast<Common::ReadStream *>(fp)->read(buf, bufl) == bufl ? 0 : 1;
+}
+
+size_t osfrbc(osfildef *fp, void *buf, size_t bufl) {
+ return dynamic_cast<Common::ReadStream *>(fp)->read(buf, bufl);
+}
+
+long osfpos(osfildef *fp) {
+ return dynamic_cast<Common::SeekableReadStream *>(fp)->pos();
+}
+
+int osfseek(osfildef *fp, long pos, int mode) {
+ return dynamic_cast<Common::SeekableReadStream *>(fp)->seek(pos, mode);
+}
+
+void osfcls(osfildef *fp) {
+ delete fp;
+}
+
+int osfdel(const char *fname) {
+ return g_system->getSavefileManager()->removeSavefile(fname) ? 0 : 1;
+}
+
+int os_rename_file(const char *oldname, const char *newname) {
+ return g_system->getSavefileManager()->renameSavefile(oldname, newname);
+}
+
+
+bool os_locate(const char *fname, int flen, const char *arg0, char *buf, size_t bufsiz) {
+ Common::String name = !flen ? Common::String(fname) : Common::String(fname, fname + flen);
+
+ if (!Common::File::exists(fname))
+ return false;
+
+ strncpy(buf, name.c_str(), bufsiz - 1);
+ buf[bufsiz - 1] = '\0';
+ return true;
+}
+
+osfildef *os_create_tempfile(const char *fname, char *buf) {
+ strcpy(buf, "tmpfile");
+ return new Common::MemoryReadWriteStream(DisposeAfterUse::YES);
+}
+
+int osfdel_temp(const char *fname) {
+ // Temporary files in ScummVM are just memory streams, so there isn't a file to delete
+ return 0;
+}
+
+void os_get_tmp_path(char *buf) {
+ strcpy(buf, "");
+}
+
+int os_gen_temp_filename(char *buf, size_t buflen) {
+ error("TODO: If results from this are being passed to file open methods, will need to do further work");
+}
+
+/* ------------------------------------------------------------------------ */
+
+void os_set_pwd(const char *dir) {
+ // No implementation
+}
+
+void os_set_pwd_file(const char *filename) {
+ // No implementation
+}
+
+bool os_mkdir(const char *dir, int create_parents) {
+ // Unsupported
+ return false;
+}
+
+bool os_rmdir(const char *dir) {
+ // Unsupported
+ return false;
+}
+
+/* ------------------------------------------------------------------------ */
+
+void os_defext(char *fname, const char *ext) {
+ if (!strchr(fname, '.'))
+ strcat(fname, ext);
+}
+
+void os_addext(char *fname, const char *ext) {
+ strcat(fname, ext);
+}
+
+void os_remext(char *fname) {
+ char *p = strchr(fname, '.');
+ if (p)
+ *p = '\0';
+}
+
+bool os_file_names_equal(const char *a, const char *b) {
+ return !strcmp(a, b);
+}
+
+const char *os_get_root_name(const char *buf) {
+ return buf;
+}
+
+bool os_is_file_absolute(const char *fname) {
+ return false;
+}
+
+void os_get_path_name(char *pathbuf, size_t pathbuflen, const char *fname) {
+ strcpy(pathbuf, "");
+}
+
+void os_build_full_path(char *fullpathbuf, size_t fullpathbuflen,
+ const char *path, const char *filename) {
+ strcpy(fullpathbuf, filename);
+}
+
+void os_combine_paths(char *fullpathbuf, size_t pathbuflen,
+ const char *path, const char *filename) {
+ strcpy(fullpathbuf, filename);
+}
+
+bool os_get_abs_filename(char *result_buf, size_t result_buf_size,
+ const char *filename) {
+ strcpy(result_buf, filename);
+ return true;
+}
+
+bool os_get_rel_path(char *result_buf, size_t result_buf_size,
+ const char *basepath, const char *filename) {
+ strcpy(result_buf, filename);
+ return true;
+}
+
+bool os_is_file_in_dir(const char *filename, const char *path,
+ bool include_subdirs, bool match_self) {
+ assert(!include_subdirs && !match_self);
+
+ return Common::File::exists(filename);
+}
+
+
+
+/* ------------------------------------------------------------------------ */
+/*
+* 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);
+
+
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/os_frob_tads.h b/engines/glk/tads/os_frob_tads.h
new file mode 100644
index 0000000..e8c0845
--- /dev/null
+++ b/engines/glk/tads/os_frob_tads.h
@@ -0,0 +1,1201 @@
+/* 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_OS_FROB_TADS
+#define GLK_TADS_OS_FROB_TADS
+
+#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
+ * Note that we need to define it as a Common::Stream since the type is used by
+ * TADS for both reading and writing files
+ */
+typedef Common::Stream 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
+
+/* 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)
+
+ /* ============= File Access ================ */
+
+ /*
+ * 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);
+
+
+ /*
+ * 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);
+
+/*
+ * 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.
+*/
+int 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, size_t 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, size_t 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);
+
+/* ------------------------------------------------------------------------ */
+/*
+ * 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.
+ */
+bool 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.
+*/
+bool 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.)
+*/
+bool 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
+*/
+bool 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'.
+*/
+const 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.
+*/
+bool 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.
+*/
+bool 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)
+*/
+bool 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.
+*/
+bool os_is_file_in_dir(const char *filename, const char *path,
+ bool include_subdirs, bool 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);
+
+
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/os_glk.cpp b/engines/glk/tads/os_glk.cpp
new file mode 100644
index 0000000..5565086
--- /dev/null
+++ b/engines/glk/tads/os_glk.cpp
@@ -0,0 +1,1002 @@
+/* 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/os_glk.h"
+#include "glk/tads/tads.h"
+#include "glk/tads/os_buffer.h"
+
+namespace Glk {
+namespace TADS {
+
+static void redraw_windows(void);
+static void os_status_redraw(void);
+extern void os_banners_redraw(void);
+
+static char lbuf[256], rbuf[256];
+static int curwin = 0;
+static int curattr = 0;
+
+winid_t mainwin;
+winid_t statuswin;
+
+uint mainfg;
+uint mainbg;
+
+uint statusfg;
+uint statusbg;
+
+/* ------------------------------------------------------------------------ */
+
+/*
+ * 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)
+{
+ mainwin = g_vm->glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
+ if (!mainwin)
+ error("fatal: could not open window!\n");
+
+ /* get default colors for main window */
+ if (!g_vm->glk_style_measure(mainwin, style_Normal, stylehint_TextColor, &mainfg))
+ mainfg = 0;
+
+ if (!g_vm->glk_style_measure(mainwin, style_Normal, stylehint_BackColor, &mainbg))
+ mainbg = 0;
+
+ /* get default colors for status window */
+ statuswin = g_vm->glk_window_open(mainwin,
+ winmethod_Above | winmethod_Fixed, 1,
+ wintype_TextGrid, 0);
+
+ if (!g_vm->glk_style_measure(statuswin, style_Normal, stylehint_TextColor, &statusfg))
+ statusfg = 0;
+
+ if (!g_vm->glk_style_measure(statuswin, style_Normal, stylehint_BackColor, &statusbg))
+ statusbg = 0;
+
+ /* close statuswin; reopened on request */
+ g_vm->glk_window_close(statuswin, 0);
+
+ statuswin = NULL;
+
+ g_vm->glk_set_window(mainwin);
+
+ strcpy(rbuf, "");
+
+ return 0;
+}
+
+/*
+ * 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)
+{
+}
+
+void os_term(int status) {
+ g_vm->quitGame();
+}
+
+void os_instbrk(int install) {
+ // No implementation
+}
+
+bool os_break() {
+ return false;
+}
+
+void os_sleep_ms(long delay_in_milliseconds) {
+ g_system->delayMillis(delay_in_milliseconds);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * 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) {
+ switch (code)
+ {
+ case SYSINFO_TEXT_HILITE:
+ *result = 1;
+ return true;
+ case SYSINFO_BANNERS:
+ *result = 1;
+ return true;
+ case SYSINFO_TEXT_COLORS:
+ *result = SYSINFO_TXC_RGB;
+ return true;
+
+#ifdef USE_HTML
+ case SYSINFO_INTERP_CLASS:
+ *result = SYSINFO_ICLASS_HTML;
+ return true;
+ case SYSINFO_HTML:
+ *result = 1;
+ return true;
+#else
+ case SYSINFO_INTERP_CLASS:
+ *result = SYSINFO_ICLASS_TEXTGUI;
+ return true;
+ case SYSINFO_HTML:
+ *result = 0;
+ return true;
+#endif
+
+ case SYSINFO_JPEG:
+ case SYSINFO_PNG:
+ case SYSINFO_WAV:
+ case SYSINFO_MIDI:
+ case SYSINFO_WAV_MIDI_OVL:
+ case SYSINFO_WAV_OVL:
+ case SYSINFO_PREF_IMAGES:
+ case SYSINFO_PREF_SOUNDS:
+ case SYSINFO_PREF_MUSIC:
+ case SYSINFO_PREF_LINKS:
+ case SYSINFO_MPEG:
+ case SYSINFO_MPEG1:
+ case SYSINFO_MPEG2:
+ case SYSINFO_MPEG3:
+ case SYSINFO_LINKS_HTTP:
+ case SYSINFO_LINKS_FTP:
+ case SYSINFO_LINKS_NEWS:
+ case SYSINFO_LINKS_MAILTO:
+ case SYSINFO_LINKS_TELNET:
+ case SYSINFO_PNG_TRANS:
+ case SYSINFO_PNG_ALPHA:
+ case SYSINFO_OGG:
+ *result = 0;
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * 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().
+ */
+
+/*
+ * 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) {
+ os_print(str, strlen(str));
+}
+
+void os_print(const char *str, size_t len) {
+ if (curwin == 0 && str)
+ os_put_buffer(str, len);
+
+ if (curwin == 1)
+ {
+ const char *p;
+ size_t rem, max;
+
+ /* The string requires some fiddling for the status window */
+ for (p = str, rem = len ; rem != 0 && *p == '\n'; p++, --rem)
+ ;
+ if (rem != 0 && p[rem-1] == '\n')
+ --rem;
+
+ /* if that leaves anything, update the statusline */
+ if (rem != 0)
+ {
+ max = sizeof(lbuf) - strlen(lbuf) - 1;
+ strncat(lbuf, p, rem > max ? max : rem);
+ os_status_redraw();
+ }
+ }
+}
+
+
+/*
+ * 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)
+{
+ curwin = stat;
+
+ if (stat == 1)
+ {
+ if (statuswin == NULL)
+ {
+ g_vm->glk_stylehint_set(wintype_TextGrid, style_User1, stylehint_ReverseColor, 1);
+ statuswin = g_vm->glk_window_open(mainwin,
+ winmethod_Above | winmethod_Fixed, 1,
+ wintype_TextGrid, 0);
+ }
+ strcpy(lbuf, "");
+ }
+}
+
+/* get the status line mode */
+int os_get_status()
+{
+ return curwin;
+}
+
+/*
+ * 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);
+}
+
+/* display a string in the score area in the status line */
+void os_strsc(const char *p)
+{
+ snprintf(rbuf, sizeof rbuf, "%s", p);
+ os_status_redraw();
+}
+
+static void os_status_redraw(void) {
+ char fmt[32];
+ char buf[256];
+ uint wid;
+ uint div;
+
+ if (!statuswin)
+ return;
+
+ g_vm->glk_window_get_size(statuswin, &wid, NULL);
+ div = wid - strlen(rbuf) - 3;
+
+ sprintf(fmt, " %%%ds %%s ", - (int)div);
+ sprintf(buf, fmt, lbuf, rbuf);
+
+ g_vm->glk_window_clear(statuswin);
+ g_vm->glk_set_window(statuswin);
+ g_vm->glk_set_style(style_User1);
+ os_put_buffer(buf, strlen(buf));
+ g_vm->glk_set_window(mainwin);
+}
+
+static void redraw_windows(void)
+{
+ os_status_redraw();
+ os_banners_redraw();
+}
+
+/* clear the screen */
+void oscls(void)
+{
+ g_vm->glk_window_clear(mainwin);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * 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)
+{
+ curattr = attr;
+ if (curattr & OS_ATTR_BOLD && curattr & OS_ATTR_ITALIC)
+ g_vm->glk_set_style(style_Alert);
+ else if (curattr & OS_ATTR_BOLD)
+ g_vm->glk_set_style(style_Subheader);
+ else if (curattr & OS_ATTR_ITALIC)
+ g_vm->glk_set_style(style_Emphasized);
+ else
+ g_vm->glk_set_style(style_Normal);
+}
+
+/*
+ * 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)
+{
+}
+
+/*
+ * 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)
+{
+#ifdef GARGLK
+ g_vm->garglk_set_story_title(title);
+#endif
+}
+
+/*
+ * 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()
+{
+ os_printz("\n[more]\n");
+ os_waitc();
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * 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)
+{
+ frefid_t fileref;
+ uint gprompt, gusage;
+
+ if (prompt_type == OS_AFP_OPEN)
+ gprompt = filemode_Read;
+ else
+ gprompt = filemode_ReadWrite;
+
+ if (file_type == OSFTSAVE || file_type == OSFTT3SAV)
+ gusage = fileusage_SavedGame;
+ else if (file_type == OSFTLOG || file_type == OSFTTEXT)
+ gusage = fileusage_Transcript;
+ else
+ gusage = fileusage_Data;
+
+ fileref = g_vm->glk_fileref_create_by_prompt(gusage, (FileMode)gprompt, 0);
+ if (fileref == NULL)
+ return OS_AFE_CANCEL;
+
+ strcpy(fname_buf, g_vm->garglk_fileref_get_name(fileref));
+
+ g_vm->glk_fileref_destroy(fileref);
+
+ return OS_AFE_SUCCESS;
+}
+
+/*
+ * 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 buflen)
+{
+ event_t event;
+ char *b = (char *)buf;
+
+ os_get_buffer(b, buflen, 0);
+
+ do
+ {
+ g_vm->glk_select(&event);
+ if (event.type == evtype_Arrange)
+ redraw_windows();
+ }
+ while (event.type != evtype_LineInput);
+
+ return (unsigned char *)os_fill_buffer(b, event.val1);
+}
+
+/*
+ * 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.)
+ */
+static char * timebuf = NULL;
+static size_t timelen = 0;
+
+int os_gets_timeout(unsigned char *buf, size_t bufl,
+ unsigned long timeout_in_milliseconds, int use_timeout)
+{
+#if defined GLK_TIMERS && defined GLK_MODULE_LINE_ECHO
+ int timer = use_timeout ? timeout_in_milliseconds : 0;
+ int timeout = 0;
+ int initlen = 0;
+ event_t event;
+
+ /* restore saved buffer contents */
+ if (timebuf)
+ {
+ assert(timelen && timelen <= bufl);
+ memcpy(buf, timebuf, timelen);
+ initlen = timelen - 1;
+ buf[initlen] = 0;
+ free(timebuf);
+ timebuf = 0;
+ }
+
+ /* start timer and turn off line echo */
+ if (timer)
+ {
+ g_vm->glk_request_timer_events(timer);
+ g_vm->glk_set_echo_line_event(mainwin, 0);
+ }
+
+ os_get_buffer(buf, bufl, initlen);
+
+ do
+ {
+ g_vm->glk_select(&event);
+ if (event.type == evtype_Arrange)
+ redraw_windows();
+ else if (event.type == evtype_Timer && (timeout = 1))
+ g_vm->glk_cancel_line_event(mainwin, &event);
+ }
+ while (event.type != evtype_LineInput);
+
+ char *res = os_fill_buffer(buf, event.val1);
+
+ /* stop timer and turn on line echo */
+ if (timer)
+ {
+ g_vm->glk_request_timer_events(0);
+ g_vm->glk_set_echo_line_event(mainwin, 1);
+ }
+
+ /* save or print buffer contents */
+ if (res && timer)
+ {
+ if (timeout)
+ {
+ timelen = strlen(buf) + 1;
+ timebuf = malloc(timelen);
+ memcpy(timebuf, buf, timelen);
+ }
+ else
+ {
+ g_vm->glk_set_style(style_Input);
+ os_print(buf, strlen(buf));
+ os_print("\n", 1);
+ g_vm->glk_set_style(style_Normal);
+ }
+ }
+
+ return timeout ? OS_EVT_TIMEOUT : res ? OS_EVT_LINE : OS_EVT_EOF;
+#else
+ return OS_EVT_NOTIMEOUT;
+#endif
+}
+
+/*
+ * 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)
+{
+#if defined GLK_TIMERS && defined GLK_MODULE_LINE_ECHO
+ if (timebuf)
+ {
+ g_vm->glk_set_style(style_Input);
+ os_print(timebuf, strlen(timebuf));
+ os_print("\n", 1);
+ g_vm->glk_set_style(style_Normal);
+
+ if (reset)
+ {
+ free(timebuf);
+ timebuf = 0;
+ }
+ }
+#endif
+}
+
+/*
+ * 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
+ * (CMD_CTRL + 'E' - 'A'), 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.
+ */
+static int glktotads(unsigned int key)
+{
+ if (key < 256)
+ return key;
+ switch (key)
+ {
+ case keycode_Up:
+ return CMD_UP;
+ case keycode_Down:
+ return CMD_DOWN;
+ case keycode_Left:
+ return CMD_LEFT;
+ case keycode_Right:
+ return CMD_RIGHT;
+ case keycode_PageUp:
+ return CMD_PGUP;
+ case keycode_PageDown:
+ return CMD_PGDN;
+ case keycode_Home:
+ return CMD_HOME;
+ case keycode_End:
+ return CMD_END;
+ case keycode_Func1:
+ return CMD_F1;
+ case keycode_Func2:
+ return CMD_F2;
+ case keycode_Func3:
+ return CMD_F3;
+ case keycode_Func4:
+ return CMD_F4;
+ case keycode_Func5:
+ return CMD_F5;
+ case keycode_Func6:
+ return CMD_F6;
+ case keycode_Func7:
+ return CMD_F7;
+ case keycode_Func8:
+ return CMD_F8;
+ case keycode_Func9:
+ return CMD_F9;
+ case keycode_Func10:
+ return CMD_F10;
+ default:
+ return 0;
+ }
+}
+
+static int bufchar = 0;
+static int waitchar = 0;
+static int timechar = 0;
+
+static int getglkchar(void)
+{
+ event_t event;
+
+ timechar = 0;
+
+ g_vm->glk_request_char_event(mainwin);
+
+ do
+ {
+ g_vm->glk_select(&event);
+ if (event.type == evtype_Arrange)
+ redraw_windows();
+ else if (event.type == evtype_Timer)
+ timechar = 1;
+ }
+ while (event.type != evtype_CharInput && event.type != evtype_Timer);
+
+ g_vm->glk_cancel_char_event(mainwin);
+
+ return timechar ? 0 : event.val1;
+}
+
+int os_getc(void)
+{
+ unsigned int c;
+
+ if (bufchar)
+ {
+ c = bufchar;
+ bufchar = 0;
+ return c;
+ }
+
+ c = waitchar ? waitchar : getglkchar();
+ waitchar = 0;
+
+ if (c == keycode_Return)
+ c = '\n';
+ else if (c == keycode_Tab)
+ c = '\t';
+ else if (c == keycode_Escape)
+ c = 27;
+
+ if (c < 256)
+ return c;
+
+ bufchar = glktotads(c);
+
+ return 0;
+}
+
+/*
+ * 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 CMD_CTRL 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)
+{
+ return os_getc();
+}
+
+/* wait for a character to become available from the keyboard */
+void os_waitc(void)
+{
+ waitchar = getglkchar();
+}
+
+/*
+ * 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.
+ */
+int os_get_event(unsigned long timeout_in_milliseconds, int use_timeout,
+ os_event_info_t *info)
+{
+#ifdef GLK_TIMERS
+ /* start timer */
+ int timer = use_timeout ? timeout_in_milliseconds : 0;
+ if (timer)
+ g_vm->glk_request_timer_events(timer);
+#else
+ /* we can't handle timeouts */
+ if (use_timeout)
+ return OS_EVT_NOTIMEOUT;
+#endif
+
+ /* get a key */
+ info->key[0] = os_getc_raw();
+ if (info->key[0] == 0 && timechar == 0)
+ info->key[1] = os_getc_raw();
+
+#ifdef GLK_TIMERS
+ /* stop timer */
+ if (timer)
+ g_vm->glk_request_timer_events(0);
+#endif
+
+ /* return the event */
+ return timechar ? OS_EVT_TIMEOUT : OS_EVT_KEY;
+}
+
+osfildef *os_exeseek(const char *argv0, const char *typ) {
+ return nullptr;
+}
+
+int os_get_str_rsc(int id, char *buf, size_t buflen) {
+ strcpy(buf, "");
+ return 0;
+}
+
+void os_dbg_printf(const char *fmt, ...) {
+ // No implementation, since ScummGlk doesn't yet implement a debugger
+}
+
+void os_dbg_vprintf(const char *fmt, va_list args) {
+ // No implementation, since ScummGlk doesn't yet implement a debugger
+}
+
+int os_vasprintf(char **bufptr, const char *fmt, va_list ap) {
+ Common::String s = Common::String::vformat(fmt, ap);
+
+ *bufptr = (char *)malloc(s.size() + 1);
+ strcpy(*bufptr, s.c_str());
+ return s.size();
+}
+
+int os_paramfile(char *buf) {
+ return false;
+}
+
+void os_rand(long *val) {
+ *val = g_vm->getRandomNumber(0x7fffffff);
+}
+
+long os_get_sys_clock_ms() {
+ return g_system->getMillis();
+}
+
+#ifndef os_tzset
+void os_tzset() {}
+#endif
+
+
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/os_glk.h b/engines/glk/tads/os_glk.h
index 75354ea..237502a 100644
--- a/engines/glk/tads/os_glk.h
+++ b/engines/glk/tads/os_glk.h
@@ -28,6 +28,9 @@
#ifndef GLK_TADS_OS_GLK
#define GLK_TADS_OS_GLK
+#include "common/scummsys.h"
+#include "glk/tads/os_frob_tads.h"
+
namespace Glk {
namespace TADS {
@@ -66,6 +69,3747 @@ namespace TADS {
#define oswp4(p, l) WRITE_LE_UINT32(p, l)
#define oswp4s(p, l) WRITE_LE_INT32(p, l)
+/* ------------------------------------------------------------------------ */
+
+typedef int32 int32_t;
+typedef uint32 uint32_t;
+
+/* ------------------------------------------------------------------------ */
+/*
+ * <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 int64
+# 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();
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * 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" */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * File "stat()" information - mode, size, time stamps
+ */
+
+/*
+ * 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);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * 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
+
+/*
+ * 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).
+ */
+bool 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
+
} // End of namespace TADS
} // End of namespace Glk
diff --git a/engines/glk/tads/osfrobtads.cpp b/engines/glk/tads/osfrobtads.cpp
deleted file mode 100644
index 6dcaadb..0000000
--- a/engines/glk/tads/osfrobtads.cpp
+++ /dev/null
@@ -1,100 +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/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;
-}
-
-osfildef *osfoprwtb(const char *fname, os_filetype_t typ) {
- Common::DumpFile *df = new Common::DumpFile();
- if (df->open(fname))
- return df;
- delete df;
- return nullptr;
-}
-
-int osfrb(osfildef *fp, void *buf, size_t count) {
- return dynamic_cast<Common::ReadStream *>(fp)->read(buf, count);
-}
-
-bool osfwb(osfildef *fp, const void *buf, size_t count) {
- return dynamic_cast<Common::WriteStream *>(fp)->write(buf, count) != count;
-}
-
-void osfflush(osfildef *fp) {
- dynamic_cast<Common::WriteStream *>(fp)->flush();
-}
-
-osfildef *osfopwt(const char *fname, os_filetype_t typ) {
- return osfoprwtb(fname, typ);
-}
-
-int osfseek(osfildef *fp, int ofs, int origin) {
- return dynamic_cast<Common::SeekableReadStream *>(fp)->seek(ofs, origin);
-}
-
-int osfpos(osfildef *fp) {
- return dynamic_cast<Common::SeekableReadStream *>(fp)->pos();
-}
-
-char *osfgets(char *buf, size_t count, osfildef *fp) {
- Common::ReadStream *rs = dynamic_cast<Common::ReadStream *>(fp);
- char *ptr = buf;
- char c;
- while (!rs->eos() && --count > 0) {
- c = rs->readByte();
- if (c == '\n' || c == '\0')
- break;
- *ptr++ = c;
- }
-
- *ptr++ = '\0';
- return buf;
-}
-
-int osfputs(const char *str, osfildef *fp) {
- return dynamic_cast<Common::WriteStream *>(fp)->write(str, strlen(str)) == strlen(str) ? 0 : -1;
-}
-
-bool os_locate(const char *fname, int flen, const char *arg0, char *buf, size_t bufsiz) {
- Common::String name = !flen ? Common::String(fname) : Common::String(fname, fname + flen);
-
- if (!Common::File::exists(fname))
- return false;
-
- strncpy(buf, name.c_str(), bufsiz - 1);
- buf[bufsiz - 1] = '\0';
- return true;
-}
-
-} // End of namespace TADS
-} // End of namespace Glk
diff --git a/engines/glk/tads/osfrobtads.h b/engines/glk/tads/osfrobtads.h
deleted file mode 100644
index d503a7b..0000000
--- a/engines/glk/tads/osfrobtads.h
+++ /dev/null
@@ -1,272 +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.
- *
- */
-
-/* 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
- * Note that we need to define it as a Common::Stream since the type is used by
- * TADS for both reading and writing files
- */
-typedef Common::Stream 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) osfoprb(fname,typ)
-
-/* Open text file for writing. */
-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. */
-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) osfoprwt
-
-/* Open binary file for writing. */
-#define osfopwb(fname,typ) osfoprwtb(fname, typ)
-
-/* Open source file for reading - use the appropriate text or binary
- * mode. */
-#define osfoprs osfoprb
-
-/* Open binary file for reading. */
-inline 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 writing. If the file already exists,
- * truncate the existing contents. Create a new file if it doesn't
- * already exist. */
-inline osfildef *osfoprwtb(const char *fname, os_filetype_t typ);
-
-/* Get a line of text from a text file. */
-char *osfgets(char *buf, size_t count, osfildef *fp);
-
-/* Write a line of text to a text file. */
-int osfputs(const char *str, osfildef *fp);
-
-/* Write bytes to file. */
-inline bool osfwb(osfildef *fp, const void *buf, size_t count);
-
-/* Flush buffered writes to a file. */
-inline void osfflush(osfildef *fp);
-
-/* 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) osfrb(fp,buf,bufl)
-
-/* Get the current seek location in the file. */
-inline int osfpos(osfildef *fp);
-
-/* Seek to a location in the file. */
-inline int osfseek(osfildef *fp, int ofs, int origin);
-
-/* 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)
-
-bool os_locate(const char *fname, int flen, const char *arg0, char *buf, size_t bufsiz);
-
-} // End of namespace TADS
-} // End of namespace Glk
-
-#endif
diff --git a/engines/glk/tads/tads.cpp b/engines/glk/tads/tads.cpp
index 5dc400b..61e13cd 100644
--- a/engines/glk/tads/tads.cpp
+++ b/engines/glk/tads/tads.cpp
@@ -21,44 +21,18 @@
*/
#include "glk/tads/tads.h"
+#include "glk/tads/os_glk.h"
#include "common/config-manager.h"
#include "common/translation.h"
namespace Glk {
namespace TADS {
-TADS::TADS(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc) {
- /*
- * GLK Initialization
- */
-
- // Open the story window
- story_win = glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
- if (!story_win)
- error("fatal: could not open window!\n");
-
- // get default colors for main window
- if (!glk_style_measure(story_win, style_Normal, stylehint_TextColor, &mainfg))
- mainfg = 0;
-
- if (!glk_style_measure(story_win, style_Normal, stylehint_BackColor, &mainbg))
- mainbg = 0;
-
- // get default colors for status window
- status_win = glk_window_open(story_win, winmethod_Above | winmethod_Fixed, 1,
- wintype_TextGrid, 0);
+TADS *g_vm;
- if (!glk_style_measure(status_win, style_Normal, stylehint_TextColor, &statusfg))
- statusfg = 0;
-
- if (!glk_style_measure(status_win, style_Normal, stylehint_BackColor, &statusbg))
- statusbg = 0;
-
- // close status window; reopened on request
- glk_window_close(status_win, 0);
- status_win = nullptr;
-
- glk_set_window(story_win);
+TADS::TADS(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc) {
+ g_vm = this;
+ os_init(nullptr, nullptr, 0, 0, 0);
}
Common::Error TADS::loadGameData(strid_t file) {
diff --git a/engines/glk/tads/tads.h b/engines/glk/tads/tads.h
index 49f59f5..e4640f0 100644
--- a/engines/glk/tads/tads.h
+++ b/engines/glk/tads/tads.h
@@ -59,6 +59,8 @@ public:
virtual Common::Error saveGameData(strid_t file, const Common::String &desc) override;
};
+extern TADS *g_vm;
+
} // End of namespace TADS
} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/appctx.h b/engines/glk/tads/tads2/appctx.h
index 3b3f499..5f32217 100644
--- a/engines/glk/tads/tads2/appctx.h
+++ b/engines/glk/tads/tads2/appctx.h
@@ -24,6 +24,7 @@
#define GLK_TADS_TADS2_APPCTX
#include "common/scummsys.h"
+#include "glk/tads/os_frob_tads.h"
namespace Glk {
namespace TADS {
diff --git a/engines/glk/tads/tads2/built_in.h b/engines/glk/tads/tads2/built_in.h
index f5807e6..5078eda 100644
--- a/engines/glk/tads/tads2/built_in.h
+++ b/engines/glk/tads/tads2/built_in.h
@@ -28,7 +28,9 @@
#ifndef GLK_TADS_TADS2_BUILT_IN
#define GLK_TADS_TADS2_BUILT_IN
+#include "glk/tads/tads2/built_in.h"
#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/list.h"
#include "glk/tads/tads2/run.h"
#include "glk/tads/tads2/text_io.h"
#include "glk/tads/tads2/regex.h"
@@ -65,7 +67,7 @@ struct bifcxdef {
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) */
+ const char *bifcxsavext; /* saved game extension (null by default) */
appctxdef *bifcxappctx; /* host application context */
re_context bifcxregex; /* regular expression searching context */
diff --git a/engines/glk/tads/tads2/character_map.cpp b/engines/glk/tads/tads2/character_map.cpp
index 9191835..82fe865 100644
--- a/engines/glk/tads/tads2/character_map.cpp
+++ b/engines/glk/tads/tads2/character_map.cpp
@@ -25,7 +25,7 @@
#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 "glk/tads/os_frob_tads.h"
#include "glk/tads/os_glk.h"
#include "common/algorithm.h"
diff --git a/engines/glk/tads/tads2/command_line.cpp b/engines/glk/tads/tads2/command_line.cpp
new file mode 100644
index 0000000..c6bd5e0
--- /dev/null
+++ b/engines/glk/tads/tads2/command_line.cpp
@@ -0,0 +1,87 @@
+/* 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/command_line.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* get a toggle argument */
+int cmdtog(errcxdef *ec, int prv, char *argp, int ofs,
+ void (*usagefn)(errcxdef *))
+{
+ switch(argp[ofs + 1])
+ {
+ case '+':
+ return(TRUE);
+
+ case '-':
+ return(FALSE);
+
+ case '\0':
+ return(!prv);
+
+ default:
+ /* invalid - display usage if we have a callback for it */
+ if (usagefn != 0)
+ (*usagefn)(ec);
+ NOTREACHEDV(int);
+ return 0;
+ }
+}
+
+/* get an argument to a switch */
+char *cmdarg(errcxdef *ec, char ***argpp, int *ip, int argc, int ofs,
+ void (*usagefn)(errcxdef *))
+{
+ char *ret;
+
+ /*
+ * check to see if the argument is appended directly to the option;
+ * if not, look at the next string
+ */
+ ret = (**argpp) + ofs + 1;
+ if (*ret == '\0')
+ {
+ /*
+ * it's not part of this string - get the argument from the next
+ * string in the vector
+ */
+ ++(*ip);
+ ++(*argpp);
+ ret = (*ip >= argc ? 0 : **argpp);
+ }
+
+ /*
+ * if we didn't find the argument, it's an error - display usage if
+ * we have a valid usage callback
+ */
+ if ((ret == 0 || *ret == 0) && usagefn != 0)
+ (*usagefn)(ec);
+
+ return ret;
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/command_line.h b/engines/glk/tads/tads2/command_line.h
new file mode 100644
index 0000000..a231fca
--- /dev/null
+++ b/engines/glk/tads/tads2/command_line.h
@@ -0,0 +1,70 @@
+/* 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.
+ *
+ */
+
+/* interface to command line option service routines
+ */
+
+#ifndef GLK_TADS_TADS2_COMMAND_LINE
+#define GLK_TADS_TADS2_COMMAND_LINE
+
+#include "glk/tads/tads2/error_handling.h"
+#include "glk/tads/os_frob_tads.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/*
+ * Get argument to an option. Option can be rammed up against option
+ * letter(s) with no space, or can be separated by a space. argp is a
+ * pointer to the pointer to the current position in the argv[] array;
+ * ip is a pointer to the index in the argv[] array. Both *argpp and
+ * *ip are incremented if the next word must be read. argc is the total
+ * number of arguments. ofs gives the number of characters (NOT
+ * including the '-') in this option flag; most options will have ofs==1
+ * since they are of the form '-x'. usagefn is a function to call if
+ * the parsing fails; it is not expected to return, but should signal an
+ * error instead.
+ */
+char *cmdarg(errcxdef *ec, char ***argpp, int *ip, int argc,
+ int ofs, void (*usagefn)(errcxdef*));
+
+
+/*
+ * Read a toggle argument. prv is the previous value (prior to this
+ * switch) of the parameter (TRUE or FALSE). argp is a pointer to the
+ * current argument word. ofs is the length of this option flag, NOT
+ * including the '-'; most options have ofs==1 since they are of the
+ * form '-x'. If the option is followed by '+', the value returned is
+ * TRUE; if it's followed by '-', the value is FALSE; if followed by
+ * nothing, the option is the logical inverse of the previous value. If
+ * it's followed by any other character, we call the usage callback,
+ * which is not expected to return, but should signal an error.
+ */
+int cmdtog(struct errcxdef *ec, int prv, char *argp, int ofs,
+ void (*usagefn)(errcxdef*));
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/error_handling.h b/engines/glk/tads/tads2/error_handling.h
index ab1f86e..7c56b2f 100644
--- a/engines/glk/tads/tads2/error_handling.h
+++ b/engines/glk/tads/tads2/error_handling.h
@@ -61,7 +61,7 @@
#define GLK_TADS_TADS2_ERROR_HANDLING
#include "glk/tads/tads2/lib.h"
-#include "glk/tads/osfrobtads.h"
+#include "glk/tads/os_frob_tads.h"
namespace Glk {
namespace TADS {
diff --git a/engines/glk/tads/tads2/error_message.cpp b/engines/glk/tads/tads2/error_message.cpp
new file mode 100644
index 0000000..3cfe8de
--- /dev/null
+++ b/engines/glk/tads/tads2/error_message.cpp
@@ -0,0 +1,86 @@
+/* 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_handling.h"
+#include "glk/tads/tads2/ltk.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+/*--------------------------------- lerini ---------------------------------*/
+/*
+ * lerini - allocate and initialize an error context. Returns a
+ * pointer to an initialized error context if successful, 0 otherwise.
+ */
+errcxdef *lerini() {
+ errcxdef *errcx; /* error context */
+
+ /* allocate an error context */
+ if (!(errcx = (errcxdef *)ltk_suballoc(sizeof(errcxdef))))
+ {
+ /* failure */
+ return((errcxdef *)0);
+ }
+
+ /* initialize the error context */
+ errcx->errcxfp = (osfildef *)0; /* no error file handle */
+ errcx->errcxofs = 0; /* no offset in argument buffer */
+ errcx->errcxlog = ltk_errlog; /* error logging routine */
+ errcx->errcxlgc = errcx; /* error logging context */
+
+ /* return the new context */
+ return(errcx);
+}
+
+
+/*--------------------------------- lerfre ---------------------------------*/
+/*
+ * lerfre - FREe error context allocated by errini.
+ */
+void lerfre(errcxdef *errcx) {
+ /* free the context */
+ ltk_subfree(errcx);
+}
+
+
+/*--------------------------------- errmsg ---------------------------------*/
+/*
+ * errmsg - format error message number 'err' into the given buffer.
+ */
+void errmsg(errcxdef *ctx, char *outbuf, int outbufl, uint err) {
+ sprintf(outbuf, "Error #%d occured.", err);
+}
+
+/*--------------------------------- errini ---------------------------------*/
+/*
+ * errini - initialize error system.
+ */
+void errini(errcxdef *ctx, char *arg0) {
+ VARUSED(ctx);
+ VARUSED(arg0);
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/execute_command.cpp b/engines/glk/tads/tads2/execute_command.cpp
new file mode 100644
index 0000000..d38d92b
--- /dev/null
+++ b/engines/glk/tads/tads2/execute_command.cpp
@@ -0,0 +1,3688 @@
+/* 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 Interpreter Execute user Command
+ *
+ * Function
+ * Executes a user command after it has been parsed
+ * Notes
+ * TADS 2.0 version
+ *
+ * This module contains the implementation of the entire "turn" sequence,
+ * which is:
+ *
+ * preCommand(actor, verb, dobj-list, prep, iobj)
+ * verb.verbAction(actor, do, prep, io)
+ * actor.actorAction( verb, do, prep, io )
+ * actor.location.roomAction( actor, verb, do, prep, io )
+ * if ( io )
+ * {
+ * io.iobjCheck(actor, verb, dobj, prep)
+ * if (io does not define verIo<Verb> directly)
+ * io.iobjGen(actor, verb, dobj, prep)
+ * do.dobjCheck(actor, verb, iobj, prep)
+ * if (do does not define do<Verb> directly)
+ * do.dobjGen(actor, verb, iobj, prep)
+ * io.verIo<Verb>( actor, do )
+ * if ( noOutput )
+ * {
+ * do.verDo<Verb>( actor, io )
+ * if ( noOutput ) io.io<Verb>( actor, do )
+ * }
+ * }
+ * else if ( do )
+ * {
+ * do.dobjCheck(actor, verb, nil, nil)
+ * if (do does not define do<Verb> directly)
+ * do.dobjGen(actor, verb, nil, nil)
+ * do.verDo<Verb>( actor )
+ * if ( noOutput )do.do<Verb>( actor )
+ * }
+ * else
+ * {
+ * verb.action( actor )
+ * }
+ * postAction(actor, verb, dobj, prep, iobj, error_code)
+ * daemons
+ * fuses
+ * endCommand(actor, verb, dobj-list, prep, iobj, error_code)
+ *
+ * If an 'exit' or 'exitobj' is encountered, we skip straight to the
+ * daemons. If an abort is encountered, we skip to endCommand. If
+ * askio, or askdo is encountered, we skip everything remaining. Under
+ * any of these exit scenarios, we return success to our caller.
+ *
+ * This module also contains code to set and remove fuses and daemons,
+ * since they are part of the player turn sequence.
+ * Returns
+ * 0 for success, other for failure.
+ */
+
+#include "glk/tads/tads2/built_in.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/memory_cache_heap.h"
+#include "glk/tads/tads2/run.h"
+#include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/os_glk.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* allocate and initialize a fuse/daemon/notifier array */
+void vocinialo(voccxdef *ctx, vocddef **what, int cnt) {
+ vocddef *p;
+
+ *what = (vocddef *)mchalo(ctx->voccxerr,
+ (cnt * sizeof(vocddef)), "vocinialo");
+
+ /* set all object/function entries to MCMONINV to indicate not-in-use */
+ for (p = *what ; cnt ; ++p, --cnt)
+ p->vocdfn = MCMONINV;
+}
+
+/* internal service routine to clear one set of fuses/deamons/alerters */
+static void vocdmn1clr(vocddef *dmn, uint cnt)
+{
+ for ( ; cnt ; --cnt, ++dmn) dmn->vocdfn = MCMONINV;
+}
+
+/* delete all fuses/daemons/alerters */
+void vocdmnclr(voccxdef *ctx)
+{
+ vocdmn1clr(ctx->voccxfus, ctx->voccxfuc);
+ vocdmn1clr(ctx->voccxdmn, ctx->voccxdmc);
+ vocdmn1clr(ctx->voccxalm, ctx->voccxalc);
+}
+
+/* save undo information for a daemon/fuse/notifier */
+static void vocdusav(voccxdef *ctx, vocddef *what)
+{
+ uchar *p;
+ objucxdef *uc = ctx->voccxundo;
+ ushort siz = sizeof(what) + sizeof(*what) + 1;
+
+ /* if we don't need to save undo, quit now */
+ if (uc == 0 || !objuok(uc))
+ return;
+
+ /* reserve space for our record */
+ p = objures(uc, OBJUCLI, siz);
+
+ /* set up our undo record */
+ *p = VOC_UNDO_DAEMON;
+ memcpy(p + 1, &what, (size_t)sizeof(what));
+ memcpy(p + 1 + sizeof(what), what, (size_t)sizeof(*what));
+
+ uc->objucxhead += siz;
+}
+
+/* apply undo information for a daemon/fuse/notifier */
+void vocdundo(void *ctx0, uchar *data)
+{
+ voccxdef *ctx = (voccxdef *)ctx0;
+ vocddef *daemon;
+ objnum objn;
+ ushort siz;
+ ushort wrdsiz;
+ uchar *p;
+ int sccnt;
+ objnum sc;
+ int len1, len2;
+ prpnum prp;
+ int flags;
+ uchar *wrd;
+
+ switch(*data)
+ {
+ case VOC_UNDO_DAEMON:
+ memcpy(&daemon, data + 1, (size_t)sizeof(daemon));
+ memcpy(daemon, data + 1 + sizeof(daemon), (size_t)sizeof(*daemon));
+ break;
+
+ case VOC_UNDO_NEWOBJ:
+ /* get the object number */
+ objn = osrp2(data + 1);
+
+ /* delete the object's inheritance and vocabulary records */
+ vocdel(ctx, objn);
+ vocidel(ctx, objn);
+
+ /* delete the object */
+ mcmfre(ctx->voccxmem, (mcmon)objn);
+ break;
+
+ case VOC_UNDO_DELOBJ:
+ /* get the object's number and size */
+ objn = osrp2(data + 1);
+ siz = osrp2(data + 3);
+ wrdsiz = osrp2(data + 5);
+
+ /* allocate the object with its original number */
+ p = mcmalonum(ctx->voccxmem, siz, (mcmon)objn);
+
+ /* copy the contents back to the object */
+ memcpy(p, data + 7, (size_t)siz);
+
+ /* get its superclass if it has one */
+ sccnt = objnsc(p);
+ if (sccnt) sc = osrp2(objsc(p));
+
+ /* unlock the object, and create its inheritance records */
+ mcmunlck(ctx->voccxmem, (mcmon)objn);
+ vociadd(ctx, objn, MCMONINV, sccnt, &sc, VOCIFNEW | VOCIFVOC);
+
+ /* restore the words as well */
+ data += 7 + siz;
+ while (wrdsiz)
+ {
+ /* get the lengths from the buffer */
+ len1 = osrp2(data + 2);
+ len2 = osrp2(data + 4);
+
+ /* add the word */
+ vocadd2(ctx, data[0], objn, data[1], data+6, len1,
+ data+6+len1, len2);
+
+ /* remove this object from the word size */
+ wrdsiz -= 6 + len1 + len2;
+ data += 6 + len1 + len2;
+ }
+ break;
+
+ case VOC_UNDO_ADDVOC:
+ case VOC_UNDO_DELVOC:
+ flags = *(data + 1);
+ prp = *(data + 2);
+ objn = osrp2(data + 3);
+ wrd = data + 5;
+ if (*data == VOC_UNDO_ADDVOC)
+ vocdel1(ctx, objn, (char *)wrd, prp, FALSE, FALSE, FALSE);
+ else
+ vocadd(ctx, prp, objn, flags, (char *)wrd);
+ break;
+
+ case VOC_UNDO_SETME:
+ ctx->voccxme = osrp2(data + 1);
+ break;
+ }
+}
+
+/* determine size of one of our client undo records */
+ushort OS_LOADDS vocdusz(void *ctx0, uchar *data)
+{
+ VARUSED(ctx0);
+
+ switch(*data)
+ {
+ case VOC_UNDO_DAEMON:
+ /* it's the size of the structures, plus one for the header */
+ return (ushort)((sizeof(vocddef *) + sizeof(vocddef)) + 1);
+
+ case VOC_UNDO_NEWOBJ:
+ /* 2 bytes for the objnum plus 1 for the header */
+ return 2 + 1;
+
+ case VOC_UNDO_DELOBJ:
+ /*
+ * 1 (header) + 2 (objnum) + 2 (size) + 2 (word size) + object
+ * data size + word size
+ */
+ return osrp2(data+3) + osrp2(data+5) + 1+2+2+2;
+
+ case VOC_UNDO_ADDVOC:
+ case VOC_UNDO_DELVOC:
+ /* 1 (header) + 2 (objnum) + 1 (flags) + 1 (type) + word size */
+ return osrp2(data + 5) + 5;
+
+ default:
+ return 0;
+ }
+}
+
+/* save undo for object creation */
+void vocdusave_newobj(voccxdef *ctx, objnum objn)
+{
+ objucxdef *uc = ctx->voccxundo;
+ uchar *p;
+
+ p = objures(uc, OBJUCLI, 3);
+ *p = VOC_UNDO_NEWOBJ;
+ oswp2(p+1, objn);
+
+ uc->objucxhead += 3;
+}
+
+/* save undo information for a change in the "Me" object */
+void vocdusave_me(voccxdef *ctx, objnum old_me)
+{
+ uchar *p;
+ objucxdef *uc = ctx->voccxundo;
+
+ /* if we don't need to save undo, there's nothing to do */
+ if (uc == 0 || !objuok(uc))
+ return;
+
+ /* reserve space for our record */
+ p = objures(uc, OBJUCLI, 3);
+ *p = VOC_UNDO_SETME;
+ oswp2(p+1, old_me);
+
+ /* absorb the space */
+ uc->objucxhead += 3;
+}
+
+/* callback context structure */
+struct delobj_cb_ctx
+{
+ uchar *p;
+};
+
+/*
+ * Iteration callback to write vocabulary words for an object being
+ * deleted to an undo stream, so that they can be restored if the
+ * deletion is undone.
+ */
+static void delobj_cb(void *ctx0, vocdef *voc, vocwdef *vocw)
+{
+ struct delobj_cb_ctx *ctx = (struct delobj_cb_ctx *)ctx0;
+ uchar *p = ctx->p;
+
+ /* write this object's header to the stream */
+ p[0] = vocw->vocwtyp;
+ p[1] = vocw->vocwflg;
+ oswp2(p+2, voc->voclen);
+ oswp2(p+4, voc->vocln2);
+
+ /* write the words as well */
+ memcpy(p+6, voc->voctxt, (size_t)(voc->voclen + voc->vocln2));
+
+ /* advance the pointer */
+ ctx->p += 6 + voc->voclen + voc->vocln2;
+}
+
+/* save undo for object deletion */
+void vocdusave_delobj(voccxdef *ctx, objnum objn)
+{
+ objucxdef *uc = ctx->voccxundo;
+ uchar *p;
+ uchar *objp;
+ uint siz;
+ int wrdsiz;
+ int wrdcnt;
+ struct delobj_cb_ctx fnctx;
+
+ /* figure out how much we need to save */
+ objp = mcmlck(ctx->voccxmem, (mcmon)objn);
+ siz = objfree(objp);
+
+ /* figure the word size */
+ voc_count(ctx, objn, 0, &wrdcnt, &wrdsiz);
+
+ /*
+ * we need to store an additional 6 bytes (2-length1, 2-length2,
+ * 1-type, 1-flags) for each word
+ */
+ wrdsiz += wrdcnt*6;
+
+ /* set up the undo header */
+ p = objures(uc, OBJUCLI, (ushort)(7 + siz + wrdsiz));
+ *p = VOC_UNDO_DELOBJ;
+ oswp2(p+1, objn);
+ oswp2(p+3, siz);
+ oswp2(p+5, wrdsiz);
+
+ /* save the object's data */
+ memcpy(p+7, objp, (size_t)siz);
+
+ /* write the words */
+ fnctx.p = p+7 + siz;
+ voc_iterate(ctx, objn, delobj_cb, &fnctx);
+
+ /* unlock the object and advance the undo pointer */
+ mcmunlck(ctx->voccxmem, (mcmon)objn);
+ uc->objucxhead += 7 + siz + wrdsiz;
+}
+
+/* save undo for word creation */
+void vocdusave_addwrd(voccxdef *ctx, objnum objn, prpnum typ, int flags,
+ char *wrd)
+{
+ ushort wrdsiz;
+ uchar *p;
+ objucxdef *uc = ctx->voccxundo;
+
+ /* figure out how much space we need, and reserve it */
+ wrdsiz = osrp2(wrd);
+ p = objures(uc, OBJUCLI, (ushort)(5 + wrdsiz));
+
+ *p = VOC_UNDO_ADDVOC;
+ *(p+1) = flags;
+ *(p+2) = (uchar)typ;
+ oswp2(p+3, objn);
+ memcpy(p+5, wrd, (size_t)wrdsiz);
+
+ uc->objucxhead += 5 + wrdsiz;
+}
+
+/* save undo for word deletion */
+void vocdusave_delwrd(voccxdef *ctx, objnum objn, prpnum typ, int flags,
+ char *wrd)
+{
+ ushort wrdsiz;
+ uchar *p;
+ objucxdef *uc = ctx->voccxundo;
+
+ /* figure out how much space we need, and reserve it */
+ wrdsiz = osrp2(wrd);
+ p = objures(uc, OBJUCLI, (ushort)(5 + wrdsiz));
+
+ *p = VOC_UNDO_DELVOC;
+ *(p+1) = flags;
+ *(p+2) = (uchar)typ;
+ oswp2(p+3, objn);
+ memcpy(p+5, wrd, (size_t)wrdsiz);
+
+ uc->objucxhead += 5 + wrdsiz;
+}
+
+
+
+/* set a fuse/daemon/notifier */
+void vocsetfd(voccxdef *ctx, vocddef *what, objnum func, prpnum prop,
+ uint tm, runsdef *val, int err)
+{
+ int slots = 0;
+
+ if (what == ctx->voccxdmn)
+ slots = ctx->voccxdmc;
+ else if (what == ctx->voccxalm)
+ slots = ctx->voccxalc;
+ else if (what == ctx->voccxfus)
+ slots = ctx->voccxfuc;
+ else
+ errsig(ctx->voccxerr, ERR_BADSETF);
+
+ /* find a free slot, and set up our fuse/daemon */
+ for ( ; slots ; ++what, --slots)
+ {
+ if (what->vocdfn == MCMONINV)
+ {
+ /* save an undo record for this slot before changing */
+ vocdusav(ctx, what);
+
+ /* record the information */
+ what->vocdfn = func;
+ if (val != 0)
+ OSCPYSTRUCT(what->vocdarg, *val);
+ what->vocdprp = prop;
+ what->vocdtim = tm;
+
+ /*
+ * the fuse/notifier/daemon is set - no need to look further
+ * for an open slot
+ */
+ return;
+ }
+ }
+
+ /* we didn't find an open slot - signal the appropriate error */
+ errsig(ctx->voccxerr, err);
+}
+
+/* remove a fuse/daemon/notifier */
+void vocremfd(voccxdef *ctx, vocddef *what, objnum func, prpnum prop,
+ runsdef *val, int err)
+{
+ int slots = 0;
+
+ if (what == ctx->voccxdmn) slots = ctx->voccxdmc;
+ else if (what == ctx->voccxalm) slots = ctx->voccxalc;
+ else if (what == ctx->voccxfus) slots = ctx->voccxfuc;
+ else errsig(ctx->voccxerr, ERR_BADREMF);
+
+ /* find the slot with this same fuse/daemon/notifier, and remove it */
+ for ( ; slots ; ++what, --slots)
+ {
+ if (what->vocdfn == func
+ && what->vocdprp == prop
+ && (!val || (val->runstyp == what->vocdarg.runstyp
+ && !memcmp(&val->runsv, &what->vocdarg.runsv,
+ (size_t)datsiz(val->runstyp,
+ &val->runsv)))))
+ {
+ /* save an undo record for this slot before changing */
+ vocdusav(ctx, what);
+
+ what->vocdfn = MCMONINV;
+ return;
+ }
+ }
+
+/* errsig(ctx->voccxerr, err); <<<harmless - don't signal it>>> */
+}
+
+/*
+ * Count one or more turns - burn all fuses down by the given number of
+ * turns. Execute any fuses that expire within the given interval, but
+ * not any that expire at the end of the last turn counted here. (If
+ * incrementing by one turn only, no fuses will be executed.) If the
+ * do_fuses flag is false, fuses are simply deleted if they burn down
+ * within the interval.
+ */
+void vocturn(voccxdef *ctx, int turncnt, int do_fuses)
+{
+ vocddef *p;
+ int i;
+ int do_exe;
+
+ while (turncnt--)
+ {
+ /* presume we won't find anything to execute */
+ do_exe = FALSE;
+
+ /* go through notifiers, looking for fuse-type notifiers */
+ for (i = ctx->voccxalc, p = ctx->voccxalm ; i ; ++p, --i)
+ {
+ if (p->vocdfn != MCMONINV
+ && p->vocdtim != VOCDTIM_EACH_TURN
+ && p->vocdtim != 0)
+ {
+ /* save an undo record for this slot before changing */
+ vocdusav(ctx, p);
+
+ if (--(p->vocdtim) == 0)
+ do_exe = TRUE;
+ }
+ }
+
+ /* now go through the fuses */
+ for (i = ctx->voccxfuc, p = ctx->voccxfus ; i ; ++p, --i)
+ {
+ if (p->vocdfn != MCMONINV && p->vocdtim != 0)
+ {
+ /* save an undo record for this slot before changing */
+ vocdusav(ctx, p);
+
+ if (--(p->vocdtim) == 0)
+ do_exe = TRUE;
+ }
+ }
+
+ /*
+ * if we'll be doing more, and anything burned down, run
+ * current fuses before going on to the next turn
+ */
+ if ((!do_fuses || turncnt) && do_exe)
+ exefuse(ctx, do_fuses);
+ }
+}
+
+/*
+ * display a default error message for a verb/dobj/iobj combo.
+ * The message is "I don't know how to <verb.sdesc> <dobj.thedesc>" if
+ * the dobj is present, and "I don't know how to <verb.sdesc> anything
+ * <prep.sdesc> <iobj.thedesc>" if the iobj is present. Such a message
+ * is displayed when the objects in the command don't handle the verb
+ * (i.e., don't have any methods for verification of the verb: they
+ * lack verDo<verb> or verIo<verb>).
+ */
+static void exeperr(voccxdef *ctx, objnum verb, objnum dobj,
+ objnum prep, objnum iobj)
+{
+ if (ctx->voccxper2 != MCMONINV)
+ {
+ runpobj(ctx->voccxrun, iobj);
+ runpobj(ctx->voccxrun, prep);
+ runpobj(ctx->voccxrun, dobj);
+ runpobj(ctx->voccxrun, verb);
+ runfn(ctx->voccxrun, ctx->voccxper2, 4);
+ return;
+ }
+
+ vocerr(ctx, VOCERR(110), "I don't know how to ");
+ runppr(ctx->voccxrun, verb, PRP_SDESC, 0);
+
+ if (dobj != MCMONINV)
+ {
+ vocerr(ctx, VOCERR(111), " ");
+ runppr(ctx->voccxrun, dobj, PRP_THEDESC, 0);
+ }
+ else
+ {
+ vocerr(ctx, VOCERR(112), " anything ");
+ if (prep != MCMONINV)
+ runppr(ctx->voccxrun, prep, PRP_SDESC, 0);
+ else
+ vocerr(ctx, VOCERR(113), "to");
+ vocerr(ctx, VOCERR(114), " ");
+ runppr(ctx->voccxrun, iobj, PRP_THEDESC, 0);
+ }
+ vocerr(ctx, VOCERR(115), ".");
+}
+
+
+/*
+ * Execute daemons
+ */
+void exedaem(voccxdef *ctx)
+{
+ runcxdef *rcx = ctx->voccxrun;
+ vocddef *daemon;
+ int i;
+ runsdef val;
+ int err;
+
+ for (i = ctx->voccxdmc, daemon = ctx->voccxdmn ; i ; ++daemon, --i)
+ {
+ if (daemon->vocdfn != MCMONINV)
+ {
+ objnum thisd = daemon->vocdfn;
+
+ ERRBEGIN(ctx->voccxerr)
+
+ OSCPYSTRUCT(val, daemon->vocdarg);
+ runpush(rcx, val.runstyp, &val);
+ runfn(rcx, thisd, 1);
+
+ ERRCATCH(ctx->voccxerr, err)
+ if (err != ERR_RUNEXIT && err != ERR_RUNEXITOBJ)
+ errrse(ctx->voccxerr);
+ ERREND(ctx->voccxerr)
+ }
+ }
+ for (i = ctx->voccxalc, daemon = ctx->voccxalm ; i ; ++daemon, --i)
+ {
+ if (daemon->vocdfn != MCMONINV
+ && daemon->vocdtim == VOCDTIM_EACH_TURN)
+ {
+ ERRBEGIN(ctx->voccxerr)
+
+ runppr(rcx, daemon->vocdfn, daemon->vocdprp, 0);
+
+ ERRCATCH(ctx->voccxerr, err)
+ if (err != ERR_RUNEXIT && err != ERR_RUNEXITOBJ)
+ errrse(ctx->voccxerr);
+ ERREND(ctx->voccxerr)
+ }
+ }
+}
+
+/*
+ * Execute any pending fuses. Return TRUE if any fuses were executed,
+ * FALSE otherwise.
+ */
+int exefuse(voccxdef *ctx, int do_run)
+{
+ runcxdef *rcx = ctx->voccxrun;
+ vocddef *daemon;
+ int i;
+ int found = FALSE;
+ runsdef val;
+ int err;
+
+ /* first, execute any expired function-based fuses */
+ for (i = ctx->voccxfuc, daemon = ctx->voccxfus ; i ; ++daemon, --i)
+ {
+ if (daemon->vocdfn != MCMONINV && daemon->vocdtim == 0)
+ {
+ objnum thisf = daemon->vocdfn;
+
+ found = TRUE;
+ ERRBEGIN(ctx->voccxerr)
+
+ /* save an undo record for this slot before changing */
+ vocdusav(ctx, daemon);
+
+ /* remove the fuse prior to running */
+ daemon->vocdfn = MCMONINV;
+
+ if (do_run)
+ {
+ OSCPYSTRUCT(val, daemon->vocdarg);
+ runpush(rcx, val.runstyp, &val);
+ runfn(rcx, thisf, 1);
+ }
+
+ ERRCATCH(ctx->voccxerr, err)
+ if (err != ERR_RUNEXIT && err != ERR_RUNEXITOBJ)
+ errrse(ctx->voccxerr);
+ ERREND(ctx->voccxerr)
+ }
+ }
+
+ /* next, execute any expired method-based notifier fuses */
+ for (i = ctx->voccxalc, daemon = ctx->voccxalm ; i ; ++daemon, --i)
+ {
+ if (daemon->vocdfn != MCMONINV && daemon->vocdtim == 0)
+ {
+ objnum thisa = daemon->vocdfn;
+
+ found = TRUE;
+ ERRBEGIN(ctx->voccxerr)
+
+ /* save an undo record for this slot before changing */
+ vocdusav(ctx, daemon);
+
+ /* delete it prior to running it */
+ daemon->vocdfn = MCMONINV;
+
+ if (do_run)
+ runppr(rcx, thisa, daemon->vocdprp, 0);
+
+ ERRCATCH(ctx->voccxerr, err)
+ if (err != ERR_RUNEXIT && err != ERR_RUNEXITOBJ)
+ errrse(ctx->voccxerr);
+ ERREND(ctx->voccxerr)
+ }
+ }
+
+ /* return true if we found any expired fuses */
+ return found;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Find the action routine template for a verb. Fills in *tplofs with
+ * the offset of the template property within the verb object, and fills
+ * in actofs with the offset of the "action" property within the verb
+ * object. Sets *tplofs to zero if there's no template, and sets
+ * *actofs to zero if there's no action routine.
+ */
+static void exe_get_tpl(voccxdef *ctx, objnum verb,
+ uint *tplofs, uint *actofs)
+{
+ /* look up the new-style template first */
+ *tplofs = objgetap(ctx->voccxmem, verb, PRP_TPL2, (objnum *)0, FALSE);
+
+ /* if there's no new-style template, look up the old-style template */
+ if (*tplofs == 0)
+ *tplofs = objgetap(ctx->voccxmem, verb, PRP_TPL, (objnum *)0, FALSE);
+
+ /* also look to see if the verb has an Action method */
+ *actofs = objgetap(ctx->voccxmem, verb, PRP_ACTION, (objnum *)0, FALSE);
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Execute fuses and daemons. Returns zero on success, or ERR_ABORT if
+ * 'abort' was thrown during execution.
+ */
+int exe_fuses_and_daemons(voccxdef *ctx, int err, int do_fuses,
+ objnum actor, objnum verb,
+ vocoldef *dobj_list, int dobj_cnt,
+ objnum prep, objnum iobj)
+{
+ int err2;
+
+ /* presume no error */
+ err2 = 0;
+
+ /* execute fuses and daemons if desired - trap any errors that occur */
+ if (do_fuses)
+ {
+ ERRBEGIN(ctx->voccxerr)
+ {
+ /* execute daemons */
+ exedaem(ctx);
+
+ /* execute fuses */
+ (void)exefuse(ctx, TRUE);
+ }
+ ERRCATCH(ctx->voccxerr, err2)
+ {
+ /*
+ * if 'abort' was invoked, ignore it, since it's now had the
+ * desired effect of skipping any remaining fuses and
+ * daemons; resignal any other error
+ */
+ if (err2 != ERR_RUNABRT)
+ errrse(ctx->voccxerr);
+
+ /* replace any previous error with the new error code */
+ err = err2;
+ }
+ ERREND(ctx->voccxerr);
+ }
+
+ /* execute endCommand if it's defined */
+ if (ctx->voccxendcmd != MCMONINV)
+ {
+ /* push the arguments */
+ runpnum(ctx->voccxrun, err);
+ runpobj(ctx->voccxrun, iobj);
+ runpobj(ctx->voccxrun, prep);
+ voc_push_vocoldef_list(ctx, dobj_list, dobj_cnt);
+ runpobj(ctx->voccxrun, verb);
+ runpobj(ctx->voccxrun, actor);
+
+ /* call endCommand */
+ runfn(ctx->voccxrun, ctx->voccxendcmd, 6);
+ }
+
+ /* return the error status */
+ return err;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * execute iobjGen/dobjGen methods, if appropriate
+ */
+static int exegen(voccxdef *ctx, objnum obj, prpnum genprop,
+ prpnum verprop, prpnum actprop)
+{
+ int hasgen; /* has xobjGen property */
+ objnum genobj; /* object with xobjGen property */
+ int hasver; /* has verXoVerb property */
+ objnum verobj; /* object with verXoVerb property */
+ int hasact; /* has xoVerb property */
+ objnum actobj; /* object with xoVerb property */
+
+ /* ignore it if there's no object here */
+ if (obj == MCMONINV) return(FALSE);
+
+ /* look up the xobjGen property, and ignore if not present */
+ hasgen = objgetap(ctx->voccxmem, obj, genprop, &genobj, FALSE);
+ if (!hasgen) return(FALSE);
+
+ /* look up the verXoVerb and xoVerb properties */
+ hasver = objgetap(ctx->voccxmem, obj, verprop, &verobj, FALSE);
+ hasact = objgetap(ctx->voccxmem, obj, actprop, &actobj, FALSE);
+
+ /* ignore if verXoVerb or xoVerb "overrides" xobjGen */
+ if ((hasver && !bifinh(ctx, vocinh(ctx, genobj), verobj))
+ || (hasact && !bifinh(ctx, vocinh(ctx, genobj), actobj)))
+ return FALSE;
+
+ /* all conditions are met - execute dobjGen */
+ return TRUE;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Save "again" information for a direct or indirect object
+ */
+static void exe_save_again_obj(vocoldef *againv, const vocoldef *objv,
+ char **bufp)
+{
+ /* if there's an object, save it */
+ if (objv != 0)
+ {
+ /* copy the object information structure */
+ memcpy(againv, objv, sizeof(*againv));
+
+ /* copy the original command words to the "again" buffer */
+ if (objv->vocolfst != 0 && objv->vocollst != 0)
+ {
+ size_t copylen;
+
+ /*
+ * Compute the length of the entire list. The words are
+ * arranged consecutively in the buffer, separated by null
+ * bytes, so we must copy everything from the first word to
+ * the start of the last word, plus the length of the last
+ * word, plus the last word's trailing null byte.
+ */
+ copylen = objv->vocollst - objv->vocolfst
+ + strlen(objv->vocollst) + 1;
+
+ /* copy the text */
+ memcpy(*bufp, objv->vocolfst, copylen);
+
+ /*
+ * set the new structure to point into the copy, not the
+ * original
+ */
+ againv->vocolfst = *bufp;
+ againv->vocollst = *bufp + (objv->vocollst - objv->vocolfst);
+
+ /* skip past the space we've consumed in the buffer */
+ *bufp += copylen;
+ }
+ }
+ else
+ {
+ /* there's nothing to save - just set the object ID to invalid */
+ againv->vocolobj = MCMONINV;
+ }
+}
+
+/*
+ * Restore an "again" object previously saved. Note that we must copy
+ * the saved data to our 2-element arrays so that we can insert a
+ * terminating element after each restored element - other code
+ * occasionally expects these structures to be stored in the standard
+ * object list array format. Returns a pointer to the restored object
+ * list, which is the same as the first argument.
+ */
+static vocoldef *exe_restore_again_obj(vocoldef again_array[2],
+ const vocoldef *saved_obj)
+{
+ /* copy the saved object into the first array element */
+ memcpy(&again_array[0], saved_obj, sizeof(again_array[0]));
+
+ /* clear the second element to indicate the end of the object list */
+ again_array[1].vocolobj = MCMONINV;
+ again_array[1].vocolflg = 0;
+
+ /* return a pointer to the first array element */
+ return &again_array[0];
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Execute a single command. 'recursive' indicates whether the routine
+ * is being called for normal command processing or as a recursive call
+ * from within the game; if this flag is true, we'll bypass certain
+ * operations that are only appropriate for normal direct player
+ * commands: we won't remember the command for "again" processing, we
+ * won't do end-of-turn processing, and we won't reset the system stack
+ * before each function invocation.
+ */
+static int exe1cmd(voccxdef *ctx, objnum actor, objnum verb, vocoldef *dobjv,
+ objnum *prepptr, vocoldef *iobjv, int endturn, uchar *tpl,
+ int newstyle, int recursive,
+ int validate_dobj, int validate_iobj,
+ vocoldef *dobj_list, int cur_dobj_idx, int dobj_cnt,
+ int show_multi_prefix, int multi_flags)
+{
+ objnum loc;
+ int err;
+ runcxdef *rcx = ctx->voccxrun;
+ objnum prep = *prepptr;
+ objnum dobj = (dobjv != 0 ? dobjv->vocolobj : MCMONINV);
+ objnum iobj = (iobjv != 0 ? iobjv->vocolobj : MCMONINV);
+ int tplflags;
+ int dobj_first;
+ objnum old_tio_actor;
+ vocoldef *old_ctx_dobj;
+ vocoldef *old_ctx_iobj;
+ objnum old_verb;
+ objnum old_actor;
+ objnum old_prep;
+ int do_fuses;
+ int do_postact;
+ vocoldef again_dobj[2];
+ vocoldef again_iobj[2];
+
+ /* presume no error will occur */
+ err = 0;
+
+ /*
+ * Presume we'll run fuses and daemons if this is the end of the
+ * turn. We only do fuses and daemons once per command, even if the
+ * command contains multiple objects; 'endturn' will be true only
+ * when this is the last object of the command.
+ */
+ do_fuses = endturn;
+
+ /* presume we will call postAction */
+ do_postact = TRUE;
+
+ /* remember the original tio-level actor setting */
+ old_tio_actor = tiogetactor(ctx->voccxtio);
+
+ /* remember the original command settings (in case this is recursive) */
+ old_actor = ctx->voccxactor;
+ old_verb = ctx->voccxverb;
+ old_prep = ctx->voccxprep;
+ old_ctx_dobj = ctx->voccxdobj;
+ old_ctx_iobj = ctx->voccxiobj;
+
+ /* the default actor is Me */
+ if (actor == MCMONINV)
+ actor = ctx->voccxme;
+
+ /* if command is "again", get information from previous command */
+ if (verb == ctx->voccxvag)
+ {
+ /* it's "again" - repeat the last command */
+ actor = ctx->voccxlsa;
+ verb = ctx->voccxlsv;
+ dobj = ctx->voccxlsd.vocolobj;
+ iobj = ctx->voccxlsi.vocolobj;
+ prep = ctx->voccxlsp;
+ tpl = ctx->voccxlst;
+ newstyle = ctx->voccxlssty;
+
+ /*
+ * If we have a direct or indirect object, restore the full
+ * object information structure pointers (in particular, this
+ * restores the word lists).
+ */
+ if (dobj != MCMONINV)
+ dobjv = exe_restore_again_obj(again_dobj, &ctx->voccxlsd);
+ if (iobj != MCMONINV)
+ iobjv = exe_restore_again_obj(again_iobj, &ctx->voccxlsi);
+
+ /*
+ * make sure the command is repeatable: there must have been a
+ * verb, and the objects specified must still be accessible
+ */
+ if (verb == MCMONINV)
+ {
+ /*
+ * if the last command was lost due to an object deletion,
+ * show the message "you can't repeat that command";
+ * otherwise, show the message "there's no command to
+ * repeat"
+ */
+ if ((ctx->voccxflg & VOCCXAGAINDEL) != 0)
+ vocerr(ctx, VOCERR(27), "You can't repeat that command.");
+ else
+ vocerr(ctx, VOCERR(26), "There's no command to repeat.");
+
+ /* flush the output and return failure */
+ tioflush(ctx->voccxtio);
+ return 0;
+ }
+ else if ((dobj != MCMONINV &&
+ !vocchkaccess(ctx, dobj, PRP_VALIDDO, 0, actor, verb))
+ || (iobj != MCMONINV &&
+ !vocchkaccess(ctx, iobj, PRP_VALIDIO, 0, actor, verb))
+ || !vocchkaccess(ctx, actor, PRP_VALIDACTOR, 0, actor, verb))
+ {
+ vocerr(ctx, VOCERR(27), "You can't repeat that command.");
+ tioflush(ctx->voccxtio);
+ return(0);
+ }
+ }
+ else
+ {
+ /* verify the direct object if present */
+ if (validate_dobj
+ && dobj != MCMONINV
+ && !vocchkaccess(ctx, dobj, PRP_VALIDDO, 0, actor, verb))
+ {
+ /* generate an appropriate message */
+ if (vocchkvis(ctx, dobj, actor))
+ {
+ /* it's visible but not accessible */
+ vocnoreach(ctx, &dobj, 1, actor, verb, prep,
+ PRP_DODEFAULT, FALSE, 0, 0, 1);
+ }
+ else
+ {
+ /* it's not even visible */
+ if (recursive)
+ vocerr(ctx, VOCERR(39), "You don't see that here.");
+ else
+ vocerr(ctx, VOCERR(38),
+ "You don't see that here any more.");
+ }
+
+ /* indicate the error */
+ return ERR_PRS_VAL_DO_FAIL;
+ }
+
+ /* verify the indirect object if present */
+ if (validate_iobj
+ && iobj != MCMONINV
+ && !vocchkaccess(ctx, iobj, PRP_VALIDIO, 0, actor, verb))
+ {
+ /* generate the error message */
+ if (vocchkvis(ctx, iobj, actor))
+ {
+ /* it's visible but not accessible */
+ vocnoreach(ctx, &iobj, 1, actor, verb, prep,
+ PRP_IODEFAULT, FALSE, 0, 0, 1);
+ }
+ else
+ {
+ /* it's not even visible */
+ if (recursive)
+ vocerr(ctx, VOCERR(39), "You don't see that here.");
+ else
+ vocerr(ctx, VOCERR(38),
+ "You don't see that here any more.");
+ }
+
+ /* indicate the error */
+ return ERR_PRS_VAL_IO_FAIL;
+ }
+
+ /*
+ * save the command, unless this is a recursive call from the
+ * game, so that we can repeat this command if the next is
+ * "again"
+ */
+ if (!recursive)
+ {
+ char *dst;
+
+ /* save the command parameters */
+ ctx->voccxlsa = actor;
+ ctx->voccxlsv = verb;
+ ctx->voccxlsp = prep;
+ ctx->voccxlssty = newstyle;
+ if (tpl != 0)
+ memcpy(ctx->voccxlst, tpl,
+ (size_t)(newstyle ? VOCTPL2SIZ : VOCTPLSIZ));
+
+ /* set up to write into the "again" word buffer */
+ dst = ctx->voccxagainbuf;
+
+ /* save the direct object information */
+ exe_save_again_obj(&ctx->voccxlsd, dobjv, &dst);
+
+ /* save the indirect object information */
+ exe_save_again_obj(&ctx->voccxlsi, iobjv, &dst);
+
+ /*
+ * clear the flag indicating that "again" was lost due to
+ * object deletion, because we obviously have a valid
+ * "again" at this point
+ */
+ ctx->voccxflg &= ~VOCCXAGAINDEL;
+ }
+ }
+
+ /* remember the flags */
+ tplflags = (tpl != 0 && newstyle ? voctplflg(tpl) : 0);
+ dobj_first = (tplflags & VOCTPLFLG_DOBJ_FIRST);
+
+ /* set up actor for tio subsystem - format strings need to know */
+ tiosetactor(ctx->voccxtio, actor);
+
+ /* store current dobj and iobj vocoldef's for later reference */
+ ctx->voccxdobj = dobjv;
+ ctx->voccxiobj = iobjv;
+
+ /* store the rest of the current command objects for reference */
+ ctx->voccxactor = actor;
+ ctx->voccxverb = verb;
+ ctx->voccxprep = prep;
+
+ ERRBEGIN(ctx->voccxerr)
+
+ /* reset the run-time context if this is a top-level call */
+ if (!recursive)
+ runrst(rcx);
+
+ /*
+ * if this is the first object, invoke the game's preCommand
+ * function, passing the list of all of the direct objects
+ */
+ if (cur_dobj_idx == 0 && ctx->voccxprecmd != MCMONINV)
+ {
+ /* push the arguments: actor, verb, dobj-list, prep, iobj */
+ runpobj(rcx, iobj);
+ runpobj(rcx, prep);
+ voc_push_vocoldef_list(ctx, dobj_list, dobj_cnt);
+ runpobj(rcx, verb);
+ runpobj(rcx, actor);
+
+ /* catch errors specially for preCommand */
+// ERRBEGIN(ctx->voccxerr)
+// {
+ /* invoke preCommand */
+ runfn(rcx, ctx->voccxprecmd, 5);
+// }
+// ERRCATCH(ctx->voccxerr, err)
+// {
+ /*
+ * if the error was 'exit', translate it to EXITPRECMD so
+ * that we handle the outer loop correctly (exiting from
+ * preCommand skips execution for all subsequent objects,
+ * but doesn't skip fuses and daemons)
+ */
+ if (err == ERR_RUNEXIT)
+ errsig(ctx->voccxerr, ERR_RUNEXITPRECMD);
+
+ /* no special handling - just resignal the error */
+ errrse(ctx->voccxerr);
+// }
+// ERREND(ctx->voccxerr);
+ }
+
+ /* show the pre-object prefix if the caller instructed us to do so */
+ voc_multi_prefix(ctx, dobj, show_multi_prefix, multi_flags,
+ cur_dobj_idx, dobj_cnt);
+
+ /*
+ * check to see if the verb has verbAction defined - if so, invoke
+ * the method
+ */
+ if (objgetap(ctx->voccxmem, verb, PRP_VERBACTION, (objnum *)0, FALSE))
+ {
+ /* call verb.verbAction(actor, dobj, prep, iobj) */
+ runpobj(rcx, iobj);
+ runpobj(rcx, prep);
+ runpobj(rcx, dobj);
+ runpobj(rcx, actor);
+ runppr(rcx, verb, PRP_VERBACTION, 4);
+ }
+
+ /* invoke cmdActor.actorAction(verb, dobj, prep, iobj) */
+ runpobj(rcx, iobj);
+ runpobj(rcx, prep);
+ runpobj(rcx, dobj);
+ runpobj(rcx, verb);
+ runppr(rcx, actor, PRP_ACTORACTION, 4);
+
+ /* reset the run-time context if this is a top-level call */
+ if (!recursive)
+ runrst(rcx);
+
+ /* invoke actor.location.roomAction(actor, verb, dobj, prep, iobj) */
+ runppr(rcx, actor, PRP_LOCATION, 0);
+ if (runtostyp(rcx) == DAT_OBJECT)
+ {
+ loc = runpopobj(rcx);
+
+ /* reset the run-time context if this is a top-level call */
+ if (!recursive)
+ runrst(rcx);
+
+ /* call roomAction */
+ runpobj(rcx, iobj);
+ runpobj(rcx, prep);
+ runpobj(rcx, dobj);
+ runpobj(rcx, verb);
+ runpobj(rcx, actor);
+ runppr(rcx, loc, PRP_ROOMACTION, 5);
+ }
+ else
+ {
+ /* the location isn't an object, so discard it */
+ rundisc(rcx);
+ }
+
+ /* if there's an indirect object, execute iobjCheck */
+ if (iobj != MCMONINV)
+ {
+ /* reset the run-time context if this is a top-level call */
+ if (!recursive)
+ runrst(rcx);
+
+ /* invoke iobjCheck */
+ runpobj(rcx, prep);
+ runpobj(rcx, dobj);
+ runpobj(rcx, verb);
+ runpobj(rcx, actor);
+ runppr(rcx, iobj, PRP_IOBJCHECK, 4);
+ }
+
+ /*
+ * If there's an indirect object, and the indirect object doesn't
+ * directly define io<Verb>, call iobj.iobjGen(actor, verb, dobj,
+ * prep)
+ */
+ if (iobj != MCMONINV
+ && exegen(ctx, iobj, PRP_IOBJGEN, voctplvi(tpl), voctplio(tpl)))
+ {
+ /* reset the run-time context if this is a top-level call */
+ if (!recursive)
+ runrst(rcx);
+
+ /* invoke iobjGen */
+ runpobj(rcx, prep);
+ runpobj(rcx, dobj);
+ runpobj(rcx, verb);
+ runpobj(rcx, actor);
+ runppr(rcx, iobj, PRP_IOBJGEN, 4);
+ }
+
+ /* if there's an direct object, execute dobjCheck */
+ if (dobj != MCMONINV)
+ {
+ /* reset the run-time context if this is a top-level call */
+ if (!recursive)
+ runrst(rcx);
+
+ /* invoke dobjCheck */
+ runpobj(rcx, prep);
+ runpobj(rcx, iobj);
+ runpobj(rcx, verb);
+ runpobj(rcx, actor);
+ runppr(rcx, dobj, PRP_DOBJCHECK, 4);
+ }
+
+ /*
+ * If there's a direct object, and the direct object doesn't
+ * directly define do<Verb>, call dobj.dobjGen(actor, verb, iobj,
+ * prep)
+ */
+ if (dobj != MCMONINV
+ && exegen(ctx, dobj, PRP_DOBJGEN, voctplvd(tpl), voctpldo(tpl)))
+ {
+ /* reset the run-time context if this is a top-level call */
+ if (!recursive)
+ runrst(rcx);
+
+ /* invoke dobjGen */
+ runpobj(rcx, prep);
+ runpobj(rcx, iobj);
+ runpobj(rcx, verb);
+ runpobj(rcx, actor);
+ runppr(rcx, dobj, PRP_DOBJGEN, 4);
+ }
+
+ /* reset the hidden-text flag */
+ tiohide(ctx->voccxtio);
+ tioshow(ctx->voccxtio);
+
+ /*
+ * Now do what needs to be done, depending on the sentence structure:
+ *
+ * No objects ==> cmdVerb.action( cmdActor )
+ *
+ * Direct object only ==> cmdDobj.verDo<Verb>( actor )
+ *. cmdDobj.do<Verb>( actor )
+ *
+ * Indirect + direct ==> cmdDobj.verDo<Verb>( actor, cmdIobj )
+ *. cmdIobj.verIo<Verb>( actor, cmdDobj )
+ *. cmdIobj.io<Verb>( actor, cmdDobj )
+ */
+ if (dobj == MCMONINV)
+ {
+ /* reset the stack for top-level calls */
+ if (!recursive)
+ runrst(rcx);
+
+ /* invoke verb.action */
+ runpobj(rcx, actor);
+ runppr(rcx, verb, PRP_ACTION, 1);
+ }
+ else if (iobj == MCMONINV)
+ {
+ if (!objgetap(ctx->voccxmem, dobj, voctplvd(tpl), (objnum *)0, FALSE))
+ {
+ /* display the error */
+ exeperr(ctx, verb, dobj, MCMONINV, MCMONINV);
+
+ /* note that verDoVerb failed */
+ err = ERR_PRS_NO_VERDO;
+
+ /* we're done with this command */
+ goto skipToFuses;
+ }
+
+ /* reset the stack for top-level calls */
+ if (!recursive)
+ runrst(rcx);
+
+ /* invoke dobj.verDoVerb */
+ runpobj(rcx, actor);
+ runppr(rcx, dobj, voctplvd(tpl), 1);
+
+ /* check for an error message from verDoVerb */
+ if (!tioshow(ctx->voccxtio))
+ {
+ /* reset the stack for top-level calls */
+ if (!recursive)
+ runrst(rcx);
+
+ /* dobj.verDoVerb displayed no output - process dobj.doVerb */
+ runpobj(rcx, actor);
+ runppr(rcx, dobj, voctpldo(tpl), 1);
+ }
+ else
+ {
+ /* note that verDoVerb failed */
+ err = ERR_PRS_VERDO_FAIL;
+ }
+ }
+ else
+ {
+ /* check to see if the verDoVerb and verIoVerb methods exist */
+ if (!objgetap(ctx->voccxmem, dobj, voctplvd(tpl), (objnum *)0, FALSE))
+ {
+ /* no verDoVerb method - show a default message */
+ exeperr(ctx, verb, dobj, MCMONINV, MCMONINV);
+
+ /* note the error */
+ err = ERR_PRS_NO_VERDO;
+
+ /* skip to the end of the turn */
+ goto skipToFuses;
+ }
+ else if (!objgetap(ctx->voccxmem, iobj, voctplvi(tpl), (objnum *)0,
+ FALSE))
+ {
+ /* no verIoVerb method - show a default mesage */
+ exeperr(ctx, verb, MCMONINV, prep, iobj);
+
+ /* note the error */
+ err = ERR_PRS_NO_VERIO;
+
+ /* skip to the end of the turn */
+ goto skipToFuses;
+ }
+
+ /* reset the stack for top-level calls */
+ if (!recursive)
+ runrst(rcx);
+
+ /* call verDoVerb(actor [,iobj]) */
+ if (!dobj_first)
+ runpobj(rcx, iobj);
+ runpobj(rcx, actor);
+ runppr(rcx, dobj, voctplvd(tpl), (dobj_first ? 1 : 2));
+
+ /* check for error output from verDoVerb */
+ if (!tioshow(ctx->voccxtio))
+ {
+ /* reset the stack for top-level calls */
+ if (!recursive)
+ runrst(rcx);
+
+ /* no error from verDoVerb - call verIoVerb(actor [,dobj]) */
+ if (dobj_first)
+ runpobj(rcx, dobj);
+ runpobj(rcx, actor);
+ runppr(rcx, iobj, voctplvi(tpl), (dobj_first ? 2 : 1));
+
+ /* check for error output from verIoVerb */
+ if (!tioshow(ctx->voccxtio))
+ {
+ /* reset the stack for top-level calls */
+ if (!recursive)
+ runrst(rcx);
+
+ /* no error from verDoVerb or verIoVerb - call ioVerb */
+ runpobj(rcx, dobj);
+ runpobj(rcx, actor);
+ runppr(rcx, iobj, voctplio(tpl), 2);
+ }
+ else
+ {
+ /* note the error */
+ err = ERR_PRS_VERIO_FAIL;
+ }
+ }
+ else
+ {
+ /* note the error */
+ err = ERR_PRS_VERDO_FAIL;
+ }
+ }
+
+ skipToFuses:
+ ERRCATCH(ctx->voccxerr, err)
+ {
+ /* if askIo was invoked, get the preposition from the error stack */
+ if (err == ERR_RUNASKI)
+ *prepptr = errargint(0);
+
+ /*
+ * If we executed 'abort', we'll skip straight to endCommand.
+ *
+ * If we executed askDo or askIo, we won't execute anything
+ * more, because the command is being interrupted.
+ *
+ * If 'exit' or 'exitobj' was executed, proceed through
+ * postAction and subsequent steps.
+ *
+ * If any error occurred other than 'exit' or 'exitobj' being
+ * invoked, resignal the error.
+ *
+ * We don't need to do anything more at this point if 'exit' was
+ * invoked, because 'exit' merely skips to the end-of-turn
+ * phase, which is where we'll go next from here.
+ *
+ * If 'exitobj' was invoked, we don't want to return an error at
+ * all, since we just want to skip the remainder of the normal
+ * processing for the current object and proceed to the next
+ * object (in a command with multiple direct objects).
+ */
+ if (err == ERR_RUNABRT)
+ {
+ /*
+ * aborting - we're going to call postAction, but we're not
+ * going to execute fuses and daemons
+ */
+ do_fuses = FALSE;
+ endturn = TRUE;
+ }
+ else if (err == ERR_RUNASKD || err == ERR_RUNASKI)
+ {
+ /* we're going to skip all end-of-turn action */
+ do_fuses = FALSE;
+ do_postact = FALSE;
+ endturn = FALSE;
+ }
+ else if (err == ERR_RUNEXIT)
+ {
+ /*
+ * Proceed with the remainder of the processing for this
+ * turn, but retain the error code to return to our caller,
+ * so they know that the rest of the turn is to be skipped.
+ *
+ * In addition, set 'do_fuses' to true, since we want to go
+ * directly to the fuse and daemon processing for this turn,
+ * regardless of whether any other objects are present
+ * (because we'll skip any remaining objects).
+ */
+ endturn = TRUE;
+ do_fuses = TRUE;
+ }
+ else if (err == ERR_RUNEXITPRECMD)
+ {
+ /*
+ * exited from preCommand - end the turn, but do not run the
+ * postAction routine
+ */
+ do_fuses = TRUE;
+ do_postact = FALSE;
+ endturn = TRUE;
+ }
+ else if (err == ERR_RUNEXITOBJ)
+ {
+ /*
+ * Proceed with the remainder of processing for this turn -
+ * we want to proceed to the next object, if any, and
+ * process it as normal. We don't need to update 'endturn'
+ * or 'do_fuses', since we want to do all of those in the
+ * normal fashion.
+ */
+ }
+ else
+ {
+ /*
+ * We can't handle any other errors. Restore the enclosing
+ * command context, and resignal the error.
+ */
+
+ /* restore the previous tio actor setting */
+ tiosetactor(ctx->voccxtio, old_tio_actor);
+
+ /* restore the original context iobj and dobj settings */
+ ctx->voccxdobj = old_ctx_dobj;
+ ctx->voccxiobj = old_ctx_iobj;
+
+ /* restore the original context command objects */
+ ctx->voccxactor = old_actor;
+ ctx->voccxverb = old_verb;
+ ctx->voccxprep = old_prep;
+
+ /* resignal the error */
+ errrse(ctx->voccxerr);
+ }
+ }
+ ERREND(ctx->voccxerr);
+
+ /*
+ * If desired, call postAction(actor, verb, dobj, prep, iobj,
+ * error_status).
+ */
+ if (do_postact && ctx->voccxpostact != MCMONINV)
+ {
+ int err2;
+
+ ERRBEGIN(ctx->voccxerr)
+ {
+ /* push the arguments */
+ runpnum(rcx, err);
+ runpobj(rcx, iobj);
+ runpobj(rcx, prep);
+ runpobj(rcx, dobj);
+ runpobj(rcx, verb);
+ runpobj(rcx, actor);
+
+ /* invoke postAction */
+ runfn(rcx, ctx->voccxpostact, 6);
+ }
+ ERRCATCH(ctx->voccxerr, err2)
+ {
+ /* remember the new error condition */
+ err = err2;
+
+ /* if we're aborting, skip fuses and daemons */
+ if (err == ERR_RUNABRT)
+ {
+ endturn = TRUE;
+ do_fuses = FALSE;
+ }
+ }
+ ERREND(ctx->voccxerr);
+ }
+
+ /* restore the original context iobj and dobj settings */
+ ctx->voccxdobj = old_ctx_dobj;
+ ctx->voccxiobj = old_ctx_iobj;
+
+ /* restore the original context command objects */
+ ctx->voccxverb = old_verb;
+ ctx->voccxprep = old_prep;
+
+ /* reset the stack for top-level calls */
+ if (!recursive)
+ runrst(rcx);
+
+ /*
+ * If this is the end of the turn, execute fuses and daemons. Skip
+ * fuses on recursive calls, since we want to count them as part of
+ * the enclosing turn.
+ */
+ if (endturn && !recursive)
+ {
+ /* catch errors so that we can restore the actor globals */
+ ERRBEGIN(ctx->voccxerr)
+ {
+ /* run fuses, daemons, and endCommand */
+ err = exe_fuses_and_daemons(ctx, err, do_fuses, actor, verb,
+ dobj_list, dobj_cnt, prep, iobj);
+ }
+ ERRCLEAN(ctx->voccxerr)
+ {
+ /* restore the previous actor globals */
+ ctx->voccxactor = old_actor;
+ tiosetactor(ctx->voccxtio, old_tio_actor);
+ }
+ ERRENDCLN(ctx->voccxerr);
+ }
+
+ /* restore the previous actor globals */
+ ctx->voccxactor = old_actor;
+ tiosetactor(ctx->voccxtio, old_tio_actor);
+
+ /* success */
+ return err;
+}
+
+
+/*
+ * saveit stores the current direct object list in 'it' or 'them'.
+ */
+static void exesaveit(voccxdef *ctx, vocoldef *dolist)
+{
+ int cnt;
+ int i;
+ int dbg = ctx->voccxflg & VOCCXFDBG;
+ runcxdef *rcx = ctx->voccxrun;
+
+ cnt = voclistlen(dolist);
+ if (cnt == 1)
+ {
+ /*
+ * check to make sure they're not referring to a number or a
+ * string; if so, it doesn't make any sense to save it
+ */
+ if (dolist[0].vocolflg == VOCS_STR
+ || dolist[0].vocolflg == VOCS_NUM)
+ {
+ /*
+ * As of 2.5.11, don't clear 'it' on a number or string;
+ * rather, just leave it as it was from the prior command.
+ * Players will almost never expect a number or string to have
+ * anything to do with pronoun antecedents, and in fact some
+ * players reported finding it confusing to have the antecedant
+ * implied by the second-most-recent command disappear when the
+ * most recent command used a number of string.
+ */
+#if 0
+ /* save a nil 'it' */
+ ctx->voccxit = MCMONINV;
+ if (dbg)
+ tioputs(ctx->voccxtio,
+ ".. setting 'it' to nil (strObj/numObj)\\n");
+#endif
+
+ /* we're done */
+ return;
+ }
+
+ /* save 'it' */
+ ctx->voccxit = dolist[0].vocolobj;
+ ctx->voccxthc = 0;
+
+ if (dbg)
+ {
+ tioputs(ctx->voccxtio, ".. setting it: ");
+ runppr(rcx, ctx->voccxit, PRP_SDESC, 0);
+ tioputs(ctx->voccxtio, "\\n");
+ }
+
+ /* set "him" if appropriate */
+ runppr(rcx, ctx->voccxit, PRP_ISHIM, 0);
+ if (runtostyp(rcx) == DAT_TRUE)
+ {
+ ctx->voccxhim = ctx->voccxit;
+ if (dbg)
+ tioputs(ctx->voccxtio,
+ "... [setting \"him\" to same object]\\n");
+ }
+ rundisc(rcx);
+
+ /* set "her" if appropriate */
+ runppr(rcx, ctx->voccxit, PRP_ISHER, 0);
+ if (runtostyp(rcx) == DAT_TRUE)
+ {
+ ctx->voccxher = ctx->voccxit;
+ if (dbg)
+ tioputs(ctx->voccxtio,
+ "... [setting \"her\" to same object]\\n");
+ }
+ rundisc(rcx);
+ }
+ else if (cnt > 1)
+ {
+ ctx->voccxthc = cnt;
+ ctx->voccxit = MCMONINV;
+ if (dbg) tioputs(ctx->voccxtio, ".. setting \"them\": [");
+ for (i = 0 ; i < cnt ; ++i)
+ {
+ ctx->voccxthm[i] = dolist[i].vocolobj;
+ if (dbg)
+ {
+ static char *STR1 = ", ";
+ static char *STR2 = "]\\n";
+
+ runppr(rcx, dolist[i].vocolobj, PRP_SDESC, 0);
+ tioputs(ctx->voccxtio, i+1 < cnt ? STR1 : STR2);
+ }
+ }
+ }
+}
+
+/* display a multiple-object prefix */
+void voc_multi_prefix(voccxdef *ctx, objnum objn,
+ int show_prefix, int multi_flags,
+ int cur_index, int count)
+{
+ runcxdef *rcx = ctx->voccxrun;
+
+ /* if the object is invalid, ignore it */
+ if (objn == MCMONINV)
+ return;
+
+ /*
+ * if there's a prefixdesc method defined, call it rather than the
+ * older multisdesc (or even older sdesc) approach
+ */
+ if (objgetap(ctx->voccxmem, objn, PRP_PREFIXDESC,
+ (objnum *)0, FALSE) != 0)
+ {
+ runsdef val;
+
+ /* push the word flags */
+ runpnum(rcx, multi_flags);
+
+ /*
+ * push the object count and the current index (adjusted to a
+ * 1-based value)
+ */
+ runpnum(rcx, count);
+ runpnum(rcx, cur_index + 1);
+
+ /* push the 'show' flag */
+ val.runstyp = runclog(show_prefix);
+ runpush(rcx, val.runstyp, &val);
+
+ /* call the method */
+ runppr(rcx, objn, PRP_PREFIXDESC, 4);
+
+ /* we're done */
+ return;
+ }
+
+ /*
+ * if we're not showing the prefix, don't use the multisdesc/sdesc
+ * display
+ */
+ if (!show_prefix)
+ return;
+
+ /*
+ * use multisdesc if defined (for compatibility with older games,
+ * use sdesc if multisdesc doesn't exist for this object)
+ */
+ if (objgetap(ctx->voccxmem, objn, PRP_MULTISDESC,
+ (objnum *)0, FALSE) == 0)
+ {
+ /* there's no multisdesc defined - use the plain sdesc */
+ runppr(rcx, objn, PRP_SDESC, 0);
+ }
+ else
+ {
+ /* multisdesc is defined - use it */
+ runppr(rcx, objn, PRP_MULTISDESC, 0);
+ }
+
+ /* show the colon */
+ vocerr_info(ctx, VOCERR(120), ": ");
+}
+
+/* execute command for each object in direct object list */
+static int exeloop(voccxdef *ctx, objnum actor, objnum verb,
+ vocoldef *dolist, objnum *prep, vocoldef *iobj,
+ int multi_flags, uchar *tpl, int newstyle)
+{
+ runcxdef *rcx = ctx->voccxrun;
+ int err;
+ int i;
+ int dobj_cnt;
+ int exec_cnt;
+ vocoldef *dobj;
+
+ /*
+ * count the direct objects; we'll iterate over the direct objects,
+ * so we execute the command once per direct object
+ */
+ exec_cnt = dobj_cnt = (dolist != 0 ? voclistlen(dolist) : 0);
+
+ /*
+ * if there are no direct objects, we still must execute the command
+ * once
+ */
+ if (exec_cnt < 1)
+ exec_cnt = 1;
+
+ /*
+ * If we have multiple direct objects, or we're using "all" with
+ * just one direct object, check with the verb to see if multiple
+ * words are acceptable: call verb.rejectMultiDobj, and see what it
+ * returns; if it returns true, don't allow multiple words, and
+ * expect that rejectMultiDobj displayed an error message.
+ * Otherwise, proceed.
+ */
+ if (((multi_flags & VOCS_ALL) != 0 || dobj_cnt > 1)
+ && dolist && dolist[0].vocolobj != MCMONINV)
+ {
+ int typ;
+
+ ERRBEGIN(ctx->voccxerr)
+ runrst(rcx);
+ if (!prep || *prep == MCMONINV)
+ runpnil(rcx);
+ else
+ runpobj(rcx, *prep);
+ runppr(rcx, verb, PRP_REJECTMDO, 1);
+ typ = runtostyp(rcx);
+ rundisc(rcx);
+ ERRCATCH(ctx->voccxerr, err)
+ if (err == ERR_RUNEXIT || err == ERR_RUNEXITOBJ
+ || err == ERR_RUNABRT)
+ return err;
+ else
+ errrse(ctx->voccxerr);
+ ERREND(ctx->voccxerr)
+
+ /* if they returned 'true', don't bother continuing */
+ if (typ == DAT_TRUE)
+ return 0;
+ }
+
+ /*
+ * execute the command the required number of times
+ */
+ for (i = 0 ; i < exec_cnt ; ++i)
+ {
+ int show_multi_prefix;
+
+ /* get the current direct object, if we have one */
+ dobj = (dolist != 0 ? &dolist[i] : 0);
+
+ /*
+ * If we have a number or string, set the current one in
+ * numObj/strObj
+ */
+ if (dolist != 0)
+ {
+ if (dolist[i].vocolflg == VOCS_STR)
+ {
+ /* it's a string - set strObj.value */
+ vocsetobj(ctx, ctx->voccxstr, DAT_SSTRING,
+ dolist[i].vocolfst + 1, &dolist[i], &dolist[i]);
+ }
+ else if (dolist[i].vocolflg == VOCS_NUM)
+ {
+ long v1, v2;
+
+ /* it's a number - set numObj.value */
+ v1 = atol(dolist[i].vocolfst);
+ oswp4s(&v2, v1);
+ vocsetobj(ctx, ctx->voccxnum, DAT_NUMBER, &v2,
+ &dolist[i], &dolist[i]);
+ }
+ }
+
+ /*
+ * For cases where we have a bunch of direct objects (or even
+ * one when "all" was used), we want to preface the output from
+ * each iteration with the name of the object we're acting on
+ * currently. In other cases, there is no prefix.
+ */
+ show_multi_prefix = ((multi_flags != 0 || dobj_cnt > 1) && dobj != 0);
+
+ /*
+ * Execute the command for this object. For every object except
+ * the first, re-validate the direct and indirect objects.
+ * There's no need to re-validate the objects on the first
+ * object in a command, because that will already have been done
+ * during object resolution.
+ */
+ err = exe1cmd(ctx, actor, verb, dobj, prep, iobj,
+ (i + 1 == exec_cnt), tpl, newstyle, FALSE,
+ i != 0, i != 0, dolist, i, dobj_cnt,
+ show_multi_prefix, multi_flags);
+
+ /* check the error - ignore any verification failures */
+ switch(err)
+ {
+ case ERR_PRS_VERDO_FAIL:
+ case ERR_PRS_VERIO_FAIL:
+ case ERR_PRS_NO_VERDO:
+ case ERR_PRS_NO_VERIO:
+ case ERR_RUNEXITOBJ:
+ case ERR_RUNEXIT:
+ /* ignore the error and continue */
+ err = 0;
+ break;
+
+ case ERR_RUNEXITPRECMD:
+ /*
+ * exited from preCommand - skip execution of subsequent
+ * objects, but return success
+ */
+ return 0;
+
+ case 0:
+ /* no error; continue */
+ break;
+
+ default:
+ /* anything else stops this command */
+ return err;
+ }
+
+ /* flush output */
+ tioflush(ctx->voccxtio);
+ }
+
+ /* success */
+ return 0;
+}
+
+/*
+ * Execute a command recursively. Game code can call this routine
+ * (indirectly through a built-in function) to execute a command, using
+ * all of the same steps that would be applied for the command if the
+ * player had typed it.
+ */
+int execmd_recurs(voccxdef *ctx, objnum actor, objnum verb,
+ objnum dobj, objnum prep, objnum iobj,
+ int validate_dobj, int validate_iobj)
+{
+ int err;
+ int newstyle;
+ uchar tpl[VOCTPL2SIZ];
+ vocoldef dobjv;
+ vocoldef iobjv;
+ voccxdef ctx_copy;
+ runsdef *orig_sp;
+ runsdef *orig_bp;
+
+ /*
+ * Save the stack and base pointers as they are on entry. Since
+ * exe1cmd() is being called recursively, it won't automatically clear
+ * the stack after it's done as it would at the top level; this means
+ * that an aborted frame can be left on the stack if we throw an
+ * 'exit' or 'abort' in the course of executing the command. To make
+ * sure we don't leave any aborted frames on the stack before
+ * returning to our caller, we simply need to restore the stack and
+ * frame pointers on the way out as they were on the way in.
+ */
+ orig_sp = ctx->voccxrun->runcxsp;
+ orig_bp = ctx->voccxrun->runcxbp;
+
+ /* make a copy of the voc context, so that changes aren't permanent */
+ ctx_copy = *ctx;
+ ctx = &ctx_copy;
+
+ /*
+ * there are no unknown words in the recursive command, since the
+ * command was prepared directly from resolved objects
+ */
+ ctx->voccxunknown = 0;
+
+ /* set up the vocoldef structure for the direct object, if present */
+ if (dobj != MCMONINV)
+ {
+ dobjv.vocolobj = dobj;
+ dobjv.vocolfst = dobjv.vocollst = "";
+ dobjv.vocolflg = 0;
+ }
+
+ /* set up the vocoldef structure for the indirect object, if present */
+ if (iobj != MCMONINV)
+ {
+ iobjv.vocolobj = iobj;
+ iobjv.vocolfst = iobjv.vocollst = "";
+ iobjv.vocolflg = 0;
+ }
+
+ /* figure out which template we need, based on the objects provided */
+ if (dobj == MCMONINV)
+ {
+ uint actofs;
+ uint tplofs;
+
+ /*
+ * No objects were provided - use the verb's "action" method.
+ * Make sure that there is in fact an "action" method.
+ */
+ exe_get_tpl(ctx, verb, &tplofs, &actofs);
+ if (actofs != 0)
+ {
+ /* execute the "action" method */
+ err = exe1cmd(ctx, actor, verb, 0, &prep, 0, FALSE,
+ 0, FALSE, TRUE, validate_dobj, validate_iobj,
+ 0, 0, 0, FALSE, 0);
+ }
+ else
+ {
+ /* indicate that the sentence structure wasn't understood */
+ err = ERR_PRS_SENT_UNK;
+ }
+ }
+ else if (iobj == MCMONINV)
+ {
+ /*
+ * No indirect object was provided, but a direct object is
+ * present - use the one-object template. First, look up the
+ * template.
+ */
+ if (voctplfnd(ctx, verb, MCMONINV, tpl, &newstyle))
+ {
+ /* execute the command */
+ err = exe1cmd(ctx, actor, verb, &dobjv, &prep, 0, FALSE,
+ tpl, newstyle, TRUE, validate_dobj, validate_iobj,
+ &dobjv, 0, 1, FALSE, 0);
+ }
+ else
+ {
+ /* indicate that the sentence structure wasn't understood */
+ err = ERR_PRS_SENT_UNK;
+ }
+ }
+ else
+ {
+ /*
+ * Both a direct and indirect object were provided - find the
+ * two-object template for the given preposition.
+ */
+ if (voctplfnd(ctx, verb, prep, tpl, &newstyle))
+ {
+ /* execute the command */
+ err = exe1cmd(ctx, actor, verb, &dobjv, &prep, &iobjv, FALSE,
+ tpl, newstyle, TRUE, validate_dobj, validate_iobj,
+ &dobjv, 0, 1, FALSE, 0);
+ }
+ else
+ {
+ /* indicate that the sentence structure wasn't understood */
+ err = ERR_PRS_SENT_UNK;
+ }
+ }
+
+ /*
+ * if the error was EXITPRECMD, change it to EXIT - EXITPRECMD is a
+ * special flag indicating that we exited from a preCommand
+ * function, which is different than normal exiting internally but
+ * not to the game
+ */
+ if (err == ERR_RUNEXITPRECMD)
+ err = ERR_RUNEXIT;
+
+ /*
+ * restore the original stack and base pointers, to ensure that we
+ * don't leave any aborted frames on the stack
+ */
+ ctx->voccxrun->runcxsp = orig_sp;
+ ctx->voccxrun->runcxbp = orig_bp;
+
+ /* return the result code */
+ return err;
+}
+
+
+/*
+ * Check for ALL, ANY, or THEM in the list - use multi-mode if found,
+ * even if we have only one object. Returns a combination of any of the
+ * VOCS_ALL, VOCS_ANY, or VOCS_THEM flags that we find.
+ */
+static int check_for_multi(vocoldef *dolist)
+{
+ int dolen;
+ int i;
+ int result;
+
+ /* presume we won't find any flags */
+ result = 0;
+
+ /*
+ * scan the list for ALL, ANY, or THEM flags, combining any such
+ * flags we find into the result
+ */
+ dolen = voclistlen(dolist);
+ for (i = 0 ; i < dolen ; ++i)
+ result |= (dolist[i].vocolflg & (VOCS_ALL | VOCS_ANY | VOCS_THEM));
+
+ /* return the result */
+ return result;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Try running the preparseCmd user function. Returns 0 if the
+ * function doesn't exist or returns 'true', ERR_PREPRSCMDCAN if it
+ * returns 'nil' (and thus wants to cancel the command), and
+ * ERR_PREPRSCMDREDO if it returns a list (and thus wants to redo the
+ * command).
+ */
+int try_preparse_cmd(voccxdef *ctx, char **cmd, int wrdcnt,
+ uchar **preparse_list)
+{
+ uchar listbuf[VOCBUFSIZ + 2 + 3*VOCBUFSIZ];
+ int i;
+ uchar *p;
+ size_t len;
+ runsdef val;
+ int typ;
+ int err;
+
+ /* if there's no preparseCmd, keep processing */
+ if (ctx->voccxppc == MCMONINV)
+ return 0;
+
+ /* build a list of the words */
+ for (p = listbuf + 2, i = 0 ; i < wrdcnt ; ++i)
+ {
+ char *src;
+ int add_quote;
+
+ /* check for strings - they require special handling */
+ if (cmd[i][0] == '"')
+ {
+ /*
+ * it's a string - what follows is a run-time style string,
+ * with a length prefix followed by the text of the string
+ */
+ len = osrp2(cmd[i] + 1) - 2;
+ src = cmd[i] + 3;
+
+ /* add quotes to the result */
+ add_quote = TRUE;
+ }
+ else
+ {
+ /* ordinary word - copy directly */
+ src = (char *)cmd[i];
+
+ /* it's a null-terminated string */
+ len = strlen(src);
+
+ /* don't add quotes to the result */
+ add_quote = FALSE;
+ }
+
+ /* write the type prefix */
+ *p++ = DAT_SSTRING;
+
+ /* write the length prefix */
+ oswp2(p, len + 2 + (add_quote ? 2 : 0));
+ p += 2;
+
+ /* add an open quote if necessary */
+ if (add_quote)
+ *p++ = '"';
+
+ /* copy the text */
+ memcpy(p, src, len);
+ p += len;
+
+ /* add the closing quote if necessary */
+ if (add_quote)
+ *p++ = '"';
+ }
+
+ /* set the length of the whole list */
+ len = p - listbuf;
+ oswp2(listbuf, len);
+
+ /* push the list as the argument, and call the user's preparseCmd */
+ val.runstyp = DAT_LIST;
+ val.runsv.runsvstr = listbuf;
+ runpush(ctx->voccxrun, DAT_LIST, &val);
+
+ /* presume that no error will occur */
+ err = 0;
+
+ /* catch errors that occur within preparseCmd */
+ ERRBEGIN(ctx->voccxerr)
+ {
+ /* call preparseCmd */
+ runfn(ctx->voccxrun, ctx->voccxppc, 1);
+ }
+ ERRCATCH(ctx->voccxerr, err)
+ {
+ /*
+ * if it's abort/exit/exitobj, just return it; for any other
+ * errors, just re-throw the same error
+ */
+ switch(err)
+ {
+ case ERR_RUNABRT:
+ case ERR_RUNEXIT:
+ case ERR_RUNEXITOBJ:
+ /* simply return these errors to the caller */
+ break;
+
+ default:
+ /* re-throw anything else */
+ errrse(ctx->voccxerr);
+ }
+ }
+ ERREND(ctx->voccxerr);
+
+ /* if an error occurred, return the error code */
+ if (err != 0)
+ return err;
+
+ /* get the result */
+ typ = runtostyp(ctx->voccxrun);
+
+ /* if they returned a list, it's a new command to execute */
+ if (typ == DAT_LIST)
+ {
+ /* get the list and give it to the caller */
+ *preparse_list = runpoplst(ctx->voccxrun);
+
+ /*
+ * indicate that the command is to be reparsed with the new word
+ * list
+ */
+ return ERR_PREPRSCMDREDO;
+ }
+
+ /* for any other type, we don't need the value, so discard it */
+ rundisc(ctx->voccxrun);
+
+ /* if the result is nil, don't process this command further */
+ if (typ == DAT_NIL)
+ return ERR_PREPRSCMDCAN;
+ else
+ return 0;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Call parseAskobjIndirect
+ */
+static void voc_askobj_indirect(voccxdef *ctx, vocoldef *dolist,
+ objnum actor, objnum verb, objnum prep)
+{
+ int cnt;
+ int i;
+ size_t len;
+ uchar *lstp;
+
+ /*
+ * Generate the direct object list argument. This argument is a
+ * list of lists. For each noun phrase, we generate one sublist in
+ * the main list. Each sublist itself consists of three
+ * sub-sublists: first, a list of strings giving the words in the
+ * noun phrase; second, a list of the objects matching the noun
+ * phrase; third, a list of the flags for the matching objects.
+ *
+ * So, if the player typed "put red box and blue ball", we might
+ * generate a list something like this:
+ *
+ * [ [ ['red', 'box'], [redBox1, redBox2], [0, 0] ], [ ['blue',
+ * 'ball'], [blueBall], [0, 0] ] ]
+ */
+
+ /*
+ * First, figure out how much space we need for this list of lists
+ * of lists. Scan the direct object list for distinct noun phrases
+ * - we need one sublist for each distinct noun phrase.
+ */
+ cnt = voclistlen(dolist);
+ for (len = 0, i = 0 ; i < cnt ; )
+ {
+ char *p;
+ size_t curlen;
+ int j;
+
+ /*
+ * we need the sublist type prefix (one byte) plus the sublist
+ * length prefix (two bytes), plus the type and length prefixes
+ * (one plus two bytes) for each of the three sub-sublist
+ */
+ len += (1+2) + 3*(1+2);
+
+ /*
+ * we need space to store the strings for the words in this noun
+ * phrase
+ */
+ for (p = dolist[i].vocolfst ; p != 0 && p <= dolist[i].vocollst ;
+ p += curlen + 1)
+ {
+ /*
+ * add in the space needed for this string element in the
+ * sub-sublist - we need one byte for the type prefix, two
+ * bytes for the length prefix, and the bytes for the string
+ * itself
+ */
+ curlen = strlen(p);
+ len += (1+2) + curlen;
+ }
+
+ /*
+ * scan each object for this same noun phrase (i.e., for which
+ * the vocabulary words are the same)
+ */
+ for (j = i ; j < cnt && dolist[j].vocolfst == dolist[i].vocolfst ;
+ ++j)
+ {
+ /*
+ * Add in space for this object in the sub-sublist for the
+ * current noun phrase. If this object is nil, we need only
+ * one byte for the type; otherwise, we need one byte for
+ * the type prefix plus two bytes for the object ID.
+ */
+ if (dolist[i].vocolobj == MCMONINV)
+ len += 1;
+ else
+ len += (1 + 2);
+
+ /*
+ * Add in space for the flags sub-sublist for the current
+ * object. We need one byte for the type and four for the
+ * integer value.
+ */
+ len += (1 + 4);
+ }
+
+ /* skip to the next distinct noun phrase */
+ i = j;
+ }
+
+ /* allocate the list */
+ lstp = voc_push_list_siz(ctx, len);
+
+ /*
+ * Go through our object array again, and this time actually build
+ * the list.
+ */
+ for (i = 0 ; i < cnt ; )
+ {
+ char *p;
+ uchar *subp;
+ uchar *subsubp;
+ size_t curlen;
+ int j;
+
+ /* start the sublist with the type prefix */
+ *lstp++ = DAT_LIST;
+
+ /* leave a placeholder for our length prefix */
+ subp = lstp;
+ lstp += 2;
+
+ /* start the sub-sublist with the word strings */
+ *lstp++ = DAT_LIST;
+ subsubp = lstp;
+ lstp += 2;
+
+ /* store the word strings in the sub-sublist */
+ for (p = dolist[i].vocolfst ; p != 0 && p <= dolist[i].vocollst ;
+ p += curlen + 1)
+ {
+ /* get this string's length */
+ curlen = strlen(p);
+
+ /* store the type and length prefixes */
+ *lstp++ = DAT_SSTRING;
+ oswp2(lstp, curlen + 2);
+ lstp += 2;
+
+ /* store the string */
+ memcpy(lstp, p, curlen);
+ lstp += curlen;
+ }
+
+ /* fix up the string sub-sublist length */
+ oswp2(subsubp, lstp - subsubp);
+
+ /* start the second sub-sublist, for the objects */
+ *lstp++ = DAT_LIST;
+ subsubp = lstp;
+ lstp += 2;
+
+ /* write each object */
+ for (j = i ; j < cnt && dolist[j].vocolfst == dolist[i].vocolfst ;
+ ++j)
+ {
+ /*
+ * if this object isn't nil, write it to the sub-sublist;
+ * otherwise, just put nil in the sub-sublist
+ */
+ if (dolist[j].vocolobj != MCMONINV)
+ {
+ *lstp++ = DAT_OBJECT;
+ oswp2(lstp, dolist[j].vocolobj);
+ lstp += 2;
+ }
+ else
+ {
+ /* no object - just store nil */
+ *lstp++ = DAT_NIL;
+ }
+ }
+
+ /* fix up the object sub-sublist length */
+ oswp2(subsubp, lstp - subsubp);
+
+ /* start the third sub-sublist, for the flags */
+ *lstp++ = DAT_LIST;
+ subsubp = lstp;
+ lstp += 2;
+
+ /* write each object's flags */
+ for (j = i ; j < cnt && dolist[j].vocolfst == dolist[i].vocolfst ;
+ ++j)
+ {
+ /* write the flags */
+ *lstp++ = DAT_NUMBER;
+ oswp4s(lstp, dolist[j].vocolflg);
+ lstp += 4;
+ }
+
+ /* fix up the flag sub-sublist length */
+ oswp2(subsubp, lstp - subsubp);
+
+ /* skip to the start of the next distinct noun phrase */
+ i = j;
+
+ /* fix up the sublist length */
+ oswp2(subp, lstp - subp);
+ }
+
+ /* push the prep, verb, and actor arguments */
+ runpobj(ctx->voccxrun, prep);
+ runpobj(ctx->voccxrun, verb);
+ runpobj(ctx->voccxrun,
+ (objnum)(actor == MCMONINV ? ctx->voccxme : actor));
+
+ /* call the function */
+ runfn(ctx->voccxrun, ctx->voccxpask3, 4);
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * execmd() - executes a user's command given the verb's verb and
+ * preposition words, a list of nouns to be used as indirect objects,
+ * and a list to be used for direct objects. The globals cmdActor and
+ * cmdPrep should already be set. This routine tries to find a template
+ * for the verb which matches the player's command. If no template
+ * matches, we try (using default objects and, if that fails, requests
+ * to the player for objects) to fill in any missing information in the
+ * player's command. If that still fails, we will say we don't
+ * understand the sentence and leave it at that.
+ */
+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_word)
+{
+ objnum verb;
+ objnum iobj;
+ int multi_flags = 0;
+ vocwdef *n;
+ int cnt;
+ vocoldef *newnoun;
+ int next;
+ char *exenewcmd;
+ char *donewcmd;
+ char *ionewcmd;
+ char *exenewbuf;
+ char *donewbuf;
+ char *ionewbuf;
+ char **exenewlist;
+ char **donewlist;
+ char **ionewlist;
+ int *exenewtype;
+ int *donewtype;
+ int *ionewtype;
+ vocoldef *dolist1;
+ vocoldef *iolist1;
+ uchar tpl[VOCTPL2SIZ];
+ int foundtpl; /* used to determine success of tpl searches */
+ runcxdef *rcx = ctx->voccxrun;
+ uint tplofs; /* offset of template object */
+ uint actofs; /* offset of 'action' property */
+ int askflags; /* flag for what we need to ask user */
+ int newstyle; /* flag indicating new-style template definitions */
+ int tplflags;
+ int err;
+ uchar *save_sp;
+
+ /* run preparseCmd */
+ switch(try_preparse_cmd(ctx, cmd, wrdcnt, preparse_list))
+ {
+ case 0:
+ /* proceed with the command */
+ break;
+
+ case ERR_PREPRSCMDCAN:
+ /* command cancelled */
+ return 0;
+
+ case ERR_RUNEXIT:
+ case ERR_RUNABRT:
+ case ERR_RUNEXITOBJ:
+ /* abort/exit/exitobj - treat this the same as command cancellation */
+ return 0;
+
+ case ERR_PREPRSCMDREDO:
+ /* redo the command - so indicate to the caller */
+ return ERR_PREPRSCMDREDO;
+ }
+
+ /* look up the verb based on the verb and verb-prep */
+ n = vocffw(ctx, vverb, (int)strlen(vverb),
+ vprep, (vprep ? (int)strlen(vprep) : 0), PRP_VERB,
Commit: bc87d740f55cfd0d283c6342abf641dfd0ff2e42
https://github.com/scummvm/scummvm/commit/bc87d740f55cfd0d283c6342abf641dfd0ff2e42
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00
Commit Message:
GLK: TADS2: Yet more volumous code additions
Changed paths:
A engines/glk/tads/os_banners.cpp
A engines/glk/tads/os_banners.h
A engines/glk/tads/os_parse.cpp
A engines/glk/tads/os_parse.h
A engines/glk/tads/tads2/get_string.cpp
engines/glk/module.mk
engines/glk/tads/os_buffer.cpp
engines/glk/tads/os_glk.cpp
engines/glk/tads/os_glk.h
engines/glk/tads/tads2/debug.h
engines/glk/tads/tads2/error_message.cpp
engines/glk/tads/tads2/file_io.cpp
engines/glk/tads/tads2/ltk.cpp
engines/glk/tads/tads2/output.cpp
engines/glk/tads/tads2/post_compilation.cpp
engines/glk/tads/tads2/post_compilation.h
engines/glk/tads/tads2/runtime_driver.cpp
engines/glk/tads/tads2/text_io.h
diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index 1ce2458..db930f2 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -92,9 +92,11 @@ MODULE_OBJS := \
scott/detection.o \
scott/scott.o \
tads/detection.o \
+ tads/os_banners.o \
tads/os_buffer.o \
tads/os_glk.o \
tads/os_frob_tads.o \
+ tads/os_parse.o \
tads/tads.o \
tads/tads2/built_in.o \
tads/tads2/character_map.o \
@@ -106,6 +108,7 @@ MODULE_OBJS := \
tads/tads2/error_message.o \
tads/tads2/execute_command.o \
tads/tads2/file_io.o \
+ tads/tads2/get_string.o \
tads/tads2/line_source_file.o \
tads/tads2/list.o \
tads/tads2/ltk.o \
diff --git a/engines/glk/tads/os_banners.cpp b/engines/glk/tads/os_banners.cpp
new file mode 100644
index 0000000..577e9f3
--- /dev/null
+++ b/engines/glk/tads/os_banners.cpp
@@ -0,0 +1,812 @@
+/* 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/os_banners.h"
+#include "glk/tads/os_buffer.h"
+#include "glk/tads/tads.h"
+#include "glk/glk_api.h"
+#include "glk/window_text_buffer.h"
+
+namespace Glk {
+namespace TADS {
+
+struct os_banner_t;
+struct banner_contents_t;
+typedef os_banner_t *osbanid_t;
+typedef banner_contents_t *contentid_t;
+
+/* for tracking banner windows */
+struct os_banner_t {
+ uint id; /* unique identifier */
+ uint valid; /* banner status */
+
+ osbanid_t prev; /* previous sibling */
+ osbanid_t next; /* next sibling */
+ osbanid_t children; /* child's descendents */
+ osbanid_t parent; /* child's parent */
+
+ uint method; /* glk window method */
+ uint size; /* glk window size */
+ uint type; /* glk window type */
+ uint status; /* glk status style */
+
+ uint cheight; /* glk char height */
+ uint cwidth; /* glk char width */
+
+ uint fgcolor; /* foreground color */
+ uint bgcolor; /* background color */
+ uint fgcustom; /* custom colors */
+ uint bgcustom;
+ uint bgtrans;
+
+ contentid_t contents; /* window contents */
+ uint style; /* active Glk style value */
+ uint newline; /* active newline */
+ uint move, x, y; /* active cursor position */
+
+ winid_t win; /* glk window object */
+};
+
+/* for reprinting banner contents */
+struct banner_contents_t {
+ osbanid_t banner; /* content owner */
+ contentid_t next; /* next set of contents */
+
+ uint style; /* stored contents style */
+ uint newline; /* stored newline */
+ uint move, x, y; /* stored cursor position */
+
+ char *chars;
+ uint len;
+};
+
+static osbanid_t os_banners = NULL;
+static uint os_banner_count = 999;
+
+extern winid_t mainwin;
+extern winid_t statuswin;
+
+extern uint mainfg;
+extern uint mainbg;
+
+extern uint statusfg;
+extern uint statusbg;
+
+void banner_contents_display(contentid_t contents);
+
+/* Implementation-specific functions for managing banner windows */
+/*
+ os_banner_init();
+ os_banner_insert();
+ os_banner_styles_apply();
+ os_banner_styles_reset();
+ os_banners_close();
+ os_banners_open();
+ os_banners_redraw();
+*/
+
+osbanid_t os_banner_init() {
+ osbanid_t instance;
+ instance = (osbanid_t)malloc(sizeof(os_banner_t));
+ if (!instance)
+ return 0;
+
+ instance->id = ++ os_banner_count;
+ instance->valid = 1;
+
+ instance->prev = 0;
+ instance->next = 0;
+ instance->children = 0;
+ instance->parent = 0;
+
+ instance->method = 0;
+ instance->size = 0;
+ instance->type = 0;
+ instance->status = 0;
+
+ instance->cheight = 0;
+ instance->cwidth = 0;
+
+ instance->contents = 0;
+ instance->style = style_Normal;
+ instance->newline = 0;
+ instance->move = 0;
+ instance->x = 0;
+ instance->y = 0;
+
+ instance->win = 0;
+
+ return instance;
+}
+
+osbanid_t os_banner_insert(osbanid_t parent, uint operation, osbanid_t other,
+ uint method, uint size, uint type, uint status)
+{
+ if (!parent || !(parent->valid))
+ return 0;
+
+ if (operation == OS_BANNER_BEFORE || operation == OS_BANNER_AFTER)
+ if (!other || !(other->valid) || !(other->parent == parent))
+ operation = OS_BANNER_LAST;
+
+ osbanid_t baby = os_banner_init();
+ if (!baby)
+ return 0;
+ baby->parent = parent;
+
+ if (!(parent->children))
+ {
+ parent->children = baby;
+ }
+ else
+ {
+ osbanid_t child = parent->children;
+
+ switch (operation)
+ {
+ case OS_BANNER_FIRST:
+ parent->children = baby;
+ baby->next = child;
+ child->prev = baby;
+ break;
+
+ case OS_BANNER_BEFORE:
+ while (child != other && child->next)
+ child = child->next;
+
+ if (child->prev)
+ {
+ child->prev->next = baby;
+ baby->prev = child->prev;
+ }
+ else
+ {
+ parent->children = baby;
+ }
+
+ baby->next = child;
+ child->prev = baby;
+ break;
+
+ case OS_BANNER_LAST:
+ while(child->next)
+ child = child->next;
+
+ baby->prev = child;
+ child->next = baby;
+ break;
+
+ case OS_BANNER_AFTER:
+ while (child != other && child->next)
+ child = child->next;
+
+ if (child->next)
+ {
+ child->next->prev = baby;
+ baby->next = child->next;
+ }
+
+ baby->prev = child;
+ child->next = baby;
+ break;
+
+ default: break;
+ }
+ }
+
+ baby->method = method;
+ baby->size = size;
+ baby->type = type;
+ baby->status = status;
+
+ return baby;
+}
+
+void os_banner_styles_apply (osbanid_t banner)
+{
+ if (!banner || !(banner->valid))
+ return;
+
+ uint propval = banner->status ? 0 : (banner->type == wintype_TextGrid ? 0 : 1);
+ uint bgcustom = banner->bgtrans ? banner->bgcolor : banner->bgcustom;
+
+ /* font style: monospace for text grid and tab aligned buffers, else proportional */
+ g_vm->glk_stylehint_set(banner->type, style_Alert, stylehint_Proportional, propval);
+ g_vm->glk_stylehint_set(banner->type, style_Subheader, stylehint_Proportional, propval);
+ g_vm->glk_stylehint_set(banner->type, style_Emphasized, stylehint_Proportional, propval);
+ g_vm->glk_stylehint_set(banner->type, style_Normal, stylehint_Proportional, propval);
+ g_vm->glk_stylehint_set(banner->type, style_User1, stylehint_Proportional, propval);
+ g_vm->glk_stylehint_set(banner->type, style_User2, stylehint_Proportional, propval);
+
+ /* foreground color: user1 reverse, user2 custom */
+ g_vm->glk_stylehint_set(banner->type, style_Alert, stylehint_TextColor, banner->fgcolor);
+ g_vm->glk_stylehint_set(banner->type, style_Subheader, stylehint_TextColor, banner->fgcolor);
+ g_vm->glk_stylehint_set(banner->type, style_Emphasized, stylehint_TextColor, banner->fgcolor);
+ g_vm->glk_stylehint_set(banner->type, style_Normal, stylehint_TextColor, banner->fgcolor);
+ g_vm->glk_stylehint_set(banner->type, style_User1, stylehint_TextColor, banner->bgcolor);
+ g_vm->glk_stylehint_set(banner->type, style_User2, stylehint_TextColor, banner->fgcustom);
+
+ /* background color: user1 reverse, user2 custom */
+ g_vm->glk_stylehint_set(banner->type, style_Alert, stylehint_BackColor, banner->bgcolor);
+ g_vm->glk_stylehint_set(banner->type, style_Subheader, stylehint_BackColor, banner->bgcolor);
+ g_vm->glk_stylehint_set(banner->type, style_Emphasized, stylehint_BackColor, banner->bgcolor);
+ g_vm->glk_stylehint_set(banner->type, style_Normal, stylehint_BackColor, banner->bgcolor);
+ g_vm->glk_stylehint_set(banner->type, style_User1, stylehint_BackColor, banner->fgcolor);
+ g_vm->glk_stylehint_set(banner->type, style_User2, stylehint_BackColor, bgcustom);
+
+}
+
+void os_banner_styles_reset (void)
+{
+ g_vm->glk_stylehint_clear(wintype_AllTypes, style_Alert, stylehint_Proportional);
+ g_vm->glk_stylehint_clear(wintype_AllTypes, style_Subheader, stylehint_Proportional);
+ g_vm->glk_stylehint_clear(wintype_AllTypes, style_Emphasized, stylehint_Proportional);
+ g_vm->glk_stylehint_clear(wintype_AllTypes, style_Normal, stylehint_Proportional);
+ g_vm->glk_stylehint_clear(wintype_AllTypes, style_User1, stylehint_Proportional);
+ g_vm->glk_stylehint_clear(wintype_AllTypes, style_User2, stylehint_Proportional);
+
+ g_vm->glk_stylehint_clear(wintype_AllTypes, style_Alert, stylehint_TextColor);
+ g_vm->glk_stylehint_clear(wintype_AllTypes, style_Subheader, stylehint_TextColor);
+ g_vm->glk_stylehint_clear(wintype_AllTypes, style_Emphasized, stylehint_TextColor);
+ g_vm->glk_stylehint_clear(wintype_AllTypes, style_Normal, stylehint_TextColor);
+ g_vm->glk_stylehint_clear(wintype_AllTypes, style_User1, stylehint_TextColor);
+ g_vm->glk_stylehint_clear(wintype_AllTypes, style_User2, stylehint_TextColor);
+
+ g_vm->glk_stylehint_clear(wintype_AllTypes, style_Alert, stylehint_BackColor);
+ g_vm->glk_stylehint_clear(wintype_AllTypes, style_Subheader, stylehint_BackColor);
+ g_vm->glk_stylehint_clear(wintype_AllTypes, style_Emphasized, stylehint_BackColor);
+ g_vm->glk_stylehint_clear(wintype_AllTypes, style_Normal, stylehint_BackColor);
+ g_vm->glk_stylehint_clear(wintype_AllTypes, style_User1, stylehint_BackColor);
+ g_vm->glk_stylehint_clear(wintype_AllTypes, style_User2, stylehint_BackColor);
+
+#ifdef GARGLK
+ /* reset our default colors with a superfluous hint */
+ g_vm->glk_stylehint_set(wintype_AllTypes, style_Normal, stylehint_TextColor, mainfg);
+ g_vm->glk_stylehint_set(wintype_AllTypes, style_Normal, stylehint_BackColor, mainbg);
+#endif /* GARGLK */
+}
+
+void os_banners_close(osbanid_t banner)
+{
+ if (!banner)
+ return;
+
+ os_banners_close(banner->children);
+ os_banners_close(banner->next);
+
+ if (banner->win && (banner->win != mainwin))
+ {
+ g_vm->glk_window_close(banner->win, 0);
+ banner->win = 0;
+ }
+}
+
+void os_banners_open(osbanid_t banner)
+{
+ if (!banner)
+ return;
+
+ if (banner->valid)
+ {
+ if (banner->size && banner->parent && banner->parent->win)
+ {
+ os_banner_styles_apply(banner);
+ banner->win = g_vm->glk_window_open(banner->parent->win, banner->method,
+ banner->size, banner->type, banner->id);
+ banner_contents_display(banner->contents);
+ }
+ os_banners_open(banner->children);
+ }
+
+ os_banners_open(banner->next);
+}
+
+void os_banners_redraw()
+{
+ if (!os_banners)
+ return;
+
+ os_banners_close(os_banners);
+ os_banners_open(os_banners);
+ os_banner_styles_reset();
+}
+
+/* Implementation-specific functions for managing banner contents */
+/*
+ banner_contents_init();
+ banner_contents_insert();
+ banner_contents_display();
+ banner_contents_clear();
+*/
+
+contentid_t banner_contents_init(void)
+{
+ contentid_t instance;
+ instance = (contentid_t)malloc(sizeof(banner_contents_t));
+ if (!instance)
+ return 0;
+
+ instance->banner = 0;
+ instance->next = 0;
+
+ instance->style = style_Normal;
+ instance->newline = 0;
+ instance->move = 0;
+ instance->x = 0;
+ instance->y = 0;
+
+ instance->chars = 0;
+ instance->len = 0;
+
+ return instance;
+}
+
+void banner_contents_insert(contentid_t contents, const char *txt, size_t len)
+{
+ if (!contents)
+ return;
+
+ contents->chars = (char *)malloc(sizeof(char) * (len + 1));
+ if (!(contents->chars))
+ return;
+
+ memcpy(contents->chars, txt, len);
+
+ contents->chars[len] = '\0';
+ contents->len = len;
+}
+
+void banner_contents_display(contentid_t contents)
+{
+ if (!contents || !(contents->banner))
+ return;
+
+ winid_t win = contents->banner->win;
+ uint len = contents->len;
+
+ g_vm->glk_set_window(win);
+
+ if (contents->newline)
+ {
+ char ch = '\n';
+ os_put_buffer(&ch, 1);
+ }
+
+ if (len && (contents->chars[len-1] == '\n'))
+ {
+ len --;
+ contents->banner->newline = 1;
+ }
+ else
+ {
+ contents->banner->newline = 0;
+ }
+
+ if (contents->move)
+ {
+ g_vm->glk_window_move_cursor(win, contents->x, contents->y);
+ contents->banner->move = 0;
+ contents->banner->x = 0;
+ contents->banner->y = 0;
+ }
+
+ g_vm->glk_set_style(contents->style);
+ os_put_buffer(contents->chars, len);
+ g_vm->glk_set_window(mainwin);
+ banner_contents_display(contents->next);
+}
+
+void banner_contents_clear(contentid_t contents)
+{
+ if (!contents)
+ return;
+
+ banner_contents_clear(contents->next);
+
+ if (contents->chars)
+ free(contents->chars);
+
+ free(contents);
+}
+
+/* Banner API functions */
+
+void *os_banner_create(void *parent, int where, void *other, int wintype,
+ int align, int siz, int siz_units,
+ unsigned long style)
+{
+ osbanid_t gparent = (osbanid_t)parent;
+ osbanid_t gbanner;
+ uint gwinmeth = 0;
+ uint gwinsize = siz;
+ uint gwintype = 0;
+ uint gstatus = (style & OS_BANNER_STYLE_TAB_ALIGN);
+
+ if (gparent && !(gparent->valid))
+ return 0;
+
+ if (!os_banners)
+ {
+ os_banners = os_banner_init();
+ if (!os_banners)
+ return 0;
+ os_banners->win = mainwin;
+ }
+
+ if (!gparent)
+ gparent = os_banners;
+
+ switch (wintype)
+ {
+ case OS_BANNER_TYPE_TEXT: gwintype = wintype_TextBuffer; break;
+ case OS_BANNER_TYPE_TEXTGRID: gwintype = wintype_TextGrid; break;
+ default: gwintype = wintype_TextGrid; break;
+ }
+
+ switch (align)
+ {
+ case OS_BANNER_ALIGN_TOP: gwinmeth = winmethod_Above; break;
+ case OS_BANNER_ALIGN_BOTTOM: gwinmeth = winmethod_Below; break;
+ case OS_BANNER_ALIGN_LEFT: gwinmeth = winmethod_Left; break;
+ case OS_BANNER_ALIGN_RIGHT: gwinmeth = winmethod_Right; break;
+ default: gwinmeth = winmethod_Above; break;
+ }
+
+ switch (siz_units)
+ {
+ case OS_BANNER_SIZE_PCT: gwinmeth |= winmethod_Proportional; break;
+ case OS_BANNER_SIZE_ABS: gwinmeth |= winmethod_Fixed; break;
+ default: gwinmeth |= winmethod_Fixed; break;
+ }
+
+ gbanner = os_banner_insert(gparent, where, (osbanid_t)other, gwinmeth, gwinsize, gwintype, gstatus);
+
+ if (gbanner)
+ {
+ gbanner->fgcolor = gstatus ? statusbg : mainfg;
+ gbanner->bgcolor = gstatus ? statusfg : mainbg;
+ gbanner->fgcustom = gbanner->fgcolor;
+ gbanner->bgcustom = gbanner->bgcolor;
+ gbanner->bgtrans = 1;
+ }
+
+ os_banners_redraw();
+
+ return gbanner;
+}
+
+void os_banner_set_size(void *banner_handle, int siz, int siz_units, int is_advisory)
+{
+ osbanid_t banner = (osbanid_t)banner_handle;
+
+ if (!banner || !banner->valid)
+ return;
+
+ uint gwinsize = siz;
+ uint gwinmeth = 0;
+
+ gwinmeth = banner->method &
+ (winmethod_Above | winmethod_Below |
+ winmethod_Left | winmethod_Right);
+
+ switch (siz_units)
+ {
+ case OS_BANNER_SIZE_PCT: gwinmeth |= winmethod_Proportional; break;
+ case OS_BANNER_SIZE_ABS: gwinmeth |= winmethod_Fixed; break;
+ default: gwinmeth |= winmethod_Fixed; break;
+ }
+
+ banner->method = gwinmeth;
+ banner->size = gwinsize;
+
+ os_banners_redraw();
+}
+
+void os_banner_size_to_contents(void *banner_handle)
+{
+ osbanid_t banner = (osbanid_t)banner_handle;
+
+ if (!banner || !banner->valid || !banner->win)
+ return;
+
+#ifdef GARGLK
+ if (banner->type == wintype_TextBuffer)
+ {
+ TextBufferWindow *win = dynamic_cast<TextBufferWindow *>(banner->win);
+ assert(win);
+ int size = win->_scrollMax;
+ if (win->_numChars)
+ size++;
+ os_banner_set_size(banner, size, OS_BANNER_SIZE_ABS, 0);
+ }
+#endif /* GARGLK */
+}
+
+void os_banner_delete(void *banner_handle)
+{
+ osbanid_t banner = (osbanid_t)banner_handle;
+ if (!banner || !(banner->valid))
+ return;
+
+ banner->valid = 0;
+ os_banners_redraw();
+
+ if (banner->parent && banner->parent->children == banner)
+ banner->parent->children = banner->next;
+
+ if (banner->next)
+ banner->next->prev = banner->prev;
+
+ if (banner->prev)
+ banner->prev->next = banner->next;
+
+ banner_contents_clear(banner->contents);
+
+ free(banner);
+}
+
+void os_banner_orphan(void *banner_handle)
+{
+ os_banner_delete(banner_handle);
+}
+
+int os_banner_getinfo(void *banner_handle, os_banner_info_t *info)
+{
+ osbanid_t banner = (osbanid_t)banner_handle;
+ if (!banner || !banner->valid || !banner->win)
+ return 1;
+
+ //winid_t win = banner->win;
+ uint gwintype = banner->type;
+ uint gwinmeth = banner->method;
+ uint gstyletab = banner->status;
+
+ if (gwinmeth & winmethod_Above)
+ info->align = OS_BANNER_ALIGN_TOP;
+ if (gwinmeth & winmethod_Below)
+ info->align = OS_BANNER_ALIGN_BOTTOM;
+ if (gwinmeth & winmethod_Left)
+ info->align = OS_BANNER_ALIGN_LEFT;
+ if (gwinmeth & winmethod_Right)
+ info->align = OS_BANNER_ALIGN_RIGHT;
+
+ info->style = gstyletab ? OS_BANNER_STYLE_TAB_ALIGN : 0;
+
+ g_vm->glk_window_get_size(banner->win, &(banner->cwidth), &(banner->cheight));
+
+ info->rows = banner->cheight;
+ info->columns = banner->cwidth;
+
+ info->pix_width = 0;
+ info->pix_height = 0;
+
+ info->os_line_wrap = gstyletab ? 0 : (gwintype == wintype_TextBuffer);
+
+ return 1;
+}
+
+int os_banner_get_charwidth(void *banner_handle)
+{
+ osbanid_t banner = (osbanid_t)banner_handle;
+ if (!banner || !banner->valid || !banner->win)
+ return 0;
+
+ g_vm->glk_window_get_size(banner->win, &(banner->cwidth), &(banner->cheight));
+
+ return banner->cwidth;
+}
+
+int os_banner_get_charheight(void *banner_handle)
+{
+ osbanid_t banner = (osbanid_t)banner_handle;
+ if (!banner || !banner->valid || !banner->win)
+ return 0;
+
+ g_vm->glk_window_get_size(banner->win, &(banner->cwidth), &(banner->cheight));
+
+ return banner->cheight;
+}
+
+void os_banner_clear(void *banner_handle)
+{
+ osbanid_t banner = (osbanid_t)banner_handle;
+ if (!banner || !banner->valid)
+ return;
+
+ if (banner->win)
+ {
+ winid_t win = banner->win;
+ g_vm->glk_window_clear(win);
+ }
+
+ banner_contents_clear(banner->contents);
+ banner->contents = 0;
+ banner->newline = 0;
+ banner->move = 0;
+ banner->x = 0;
+ banner->y = 0;
+}
+
+void os_banner_disp(void *banner_handle, const char *txt, size_t len)
+{
+ osbanid_t banner = (osbanid_t)banner_handle;
+ if (!banner || !banner->valid || !banner->win)
+ return;
+
+ contentid_t update = banner_contents_init();
+ if (!update)
+ return;
+ update->banner = banner;
+
+ if (!(banner->contents))
+ {
+ banner->contents = update;
+ }
+ else
+ {
+ contentid_t contents = banner->contents;
+ while (contents->next)
+ contents = contents->next;
+ contents->next = update;
+ }
+
+ update->style = banner->style;
+ update->newline = banner->newline;
+ update->move = banner->move;
+ update->x = banner->x;
+ update->y = banner->y;
+
+ banner_contents_insert(update, txt, len);
+ banner_contents_display(update);
+}
+
+void os_banner_goto(void *banner_handle, int row, int col)
+{
+ osbanid_t banner = (osbanid_t)banner_handle;
+ if (!banner || !banner->valid || !banner->win)
+ return;
+
+ if (banner->type == wintype_TextGrid)
+ {
+ banner->move = 1;
+ banner->x = col;
+ banner->y = row;
+ }
+}
+
+void os_banner_set_attr(void *banner_handle, int attr)
+{
+ osbanid_t banner = (osbanid_t)banner_handle;
+ if (!banner || !banner->valid)
+ return;
+
+ if (attr & OS_ATTR_BOLD && attr & OS_ATTR_ITALIC)
+ banner->style = style_Alert;
+ else if (attr & OS_ATTR_BOLD)
+ banner->style = style_Subheader;
+ else if (attr & OS_ATTR_ITALIC)
+ banner->style = style_Emphasized;
+ else
+ banner->style = style_Normal;
+}
+
+void os_banner_set_color(void *banner_handle, os_color_t fg, os_color_t bg)
+{
+ osbanid_t banner = (osbanid_t)banner_handle;
+ if (!banner || !banner->valid)
+ return;
+
+ uint reversed = 0;
+ uint normal = 0;
+ uint transparent = 0;
+
+ /* evaluate parameters */
+
+ if (os_color_is_param(fg))
+ {
+ switch(fg)
+ {
+ case OS_COLOR_P_TEXTBG:
+ case OS_COLOR_P_STATUSBG:
+ reversed = 1;
+ break;
+
+ case OS_COLOR_P_TEXT:
+ case OS_COLOR_P_STATUSLINE:
+ case OS_COLOR_P_INPUT:
+ normal = 1;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (os_color_is_param(bg))
+ {
+ switch (bg)
+ {
+ case OS_COLOR_P_TRANSPARENT:
+ transparent = 1;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* choose a style */
+
+ if (normal && transparent)
+ banner->style = style_Normal;
+ else if (reversed)
+ banner->style = style_User1;
+ else
+ banner->style = style_User2;
+
+ /* process our custom color */
+
+ if (banner->style == style_User2)
+ {
+ /* store current settings */
+ uint oldfg = banner->fgcustom;
+ uint oldbg = banner->bgcustom;
+ uint oldtr = banner->bgtrans;
+
+ /* reset custom color parameters */
+ banner->fgcustom = banner->fgcolor;
+ banner->bgcustom = banner->bgcolor;
+ banner->bgtrans = 1;
+
+ if (!normal)
+ banner->fgcustom = fg;
+
+ if (!transparent)
+ {
+ banner->bgcustom = bg;
+ banner->bgtrans = 0;
+ }
+
+ if (!(banner->fgcustom == oldfg
+ && banner->bgcustom == oldbg
+ && banner->bgtrans == oldtr))
+ os_banners_redraw();
+ }
+}
+
+void os_banner_set_screen_color(void *banner_handle, os_color_t color) {
+ osbanid_t banner = (osbanid_t)banner_handle;
+ if (!banner || !banner->valid)
+ return;
+
+ if (!(os_color_is_param(color)))
+ banner->bgcolor = color;
+
+ os_banners_redraw();
+}
+
+void os_banner_flush(void *banner_handle) {}
+void os_banner_start_html(void *banner_handle) {}
+void os_banner_end_html(void *banner_handle) {}
+
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/os_banners.h b/engines/glk/tads/os_banners.h
new file mode 100644
index 0000000..f518d8e
--- /dev/null
+++ b/engines/glk/tads/os_banners.h
@@ -0,0 +1,631 @@
+/* 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_BANNERS
+#define GLK_TADS_OS_BANNERS
+
+#include "common/scummsys.h"
+#include "glk/tads/os_glk.h"
+
+namespace Glk {
+namespace TADS {
+
+/* ------------------------------------------------------------------------ */
+/*
+ * 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);
+
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/os_buffer.cpp b/engines/glk/tads/os_buffer.cpp
index 0f00e9d..539a379 100644
--- a/engines/glk/tads/os_buffer.cpp
+++ b/engines/glk/tads/os_buffer.cpp
@@ -21,6 +21,7 @@
*/
#include "glk/tads/os_buffer.h"
+#include "glk/tads/os_parse.h"
#include "glk/tads/tads.h"
namespace Glk {
@@ -50,10 +51,6 @@ char *os_fill_buffer(char *buf, size_t len)
static uint32 *input = 0;
static uint max = 0;
-extern uint os_parse_chars(const char *buf, uint buflen, uint32 *out, uint outlen);
-
-extern uint os_prepare_chars(uint32 *buf, uint buflen, char *out, uint outlen);
-
void os_put_buffer(const char *buf, size_t len) {
uint *out;
uint outlen;
@@ -66,7 +63,7 @@ void os_put_buffer(const char *buf, size_t len) {
return;
outlen = len;
- outlen = os_parse_chars(buf, len, out, outlen);
+ outlen = os_parse_chars((const unsigned char *)buf, len, out, outlen);
if (outlen)
g_vm->glk_put_buffer_uni(out, outlen);
@@ -81,13 +78,13 @@ void os_get_buffer(char *buf, size_t len, size_t init) {
max = len;
if (init)
- os_parse_chars(buf, init + 1, input, len);
+ os_parse_chars((const unsigned char *)buf, init + 1, input, len);
g_vm->glk_request_line_event_uni(mainwin, input, len - 1, init);
}
char *os_fill_buffer(char *buf, size_t len) {
- uint res = os_prepare_chars(input, len, buf, max);
+ uint res = os_prepare_chars(input, len, (unsigned char *)buf, max);
buf[res] = '\0';
free(input);
diff --git a/engines/glk/tads/os_glk.cpp b/engines/glk/tads/os_glk.cpp
index 5565086..884703c 100644
--- a/engines/glk/tads/os_glk.cpp
+++ b/engines/glk/tads/os_glk.cpp
@@ -44,6 +44,11 @@ uint mainbg;
uint statusfg;
uint statusbg;
+int G_os_pagelength;
+int G_os_linewidth;
+int G_os_moremode;
+char G_os_gamename[OSFNMAX];
+
/* ------------------------------------------------------------------------ */
/*
@@ -993,10 +998,106 @@ long os_get_sys_clock_ms() {
return g_system->getMillis();
}
+void os_xlat_html4(unsigned int html4_char, char *result, size_t result_len) {
+ /* Return all standard Latin-1 characters as-is */
+ if (html4_char <= 128 || (html4_char >= 160 && html4_char <= 255))
+ result[0] = (unsigned char)html4_char;
+ else {
+ switch (html4_char) {
+ case 130: /* single back quote */
+ result[0] = '`'; break;
+ case 132: /* double back quote */
+ result[0] = '\"'; break;
+ case 153: /* trade mark */
+ strcpy(result, "(tm)"); return;
+ case 140: /* OE ligature */
+ case 338: /* OE ligature */
+ strcpy(result, "OE"); return;
+ case 339: /* oe ligature */
+ strcpy(result, "oe"); return;
+ case 159: /* Yuml */
+ result[0] = (char)255;
+ case 376: /* Y with diaresis */
+ result[0] = 'Y'; break;
+ case 352: /* S with caron */
+ result[0] = 'S'; break;
+ case 353: /* s with caron */
+ result[0] = 's'; break;
+ case 150: /* en dash */
+ case 8211: /* en dash */
+ result[0] = '-'; break;
+ case 151: /* em dash */
+ case 8212: /* em dash */
+ strcpy(result, "--"); return;
+ case 145: /* left single quote */
+ case 8216: /* left single quote */
+ result[0] = '`'; break;
+ case 146: /* right single quote */
+ case 8217: /* right single quote */
+ case 8218: /* single low-9 quote */
+ result[0] = '\''; break;
+ case 147: /* left double quote */
+ case 148: /* right double quote */
+ case 8220: /* left double quote */
+ case 8221: /* right double quote */
+ case 8222: /* double low-9 quote */
+ result[0] = '\"'; break;
+ case 8224: /* dagger */
+ case 8225: /* double dagger */
+ case 8240: /* per mille sign */
+ result[0] = ' '; break;
+ case 139: /* single left-pointing angle quote */
+ case 8249: /* single left-pointing angle quote */
+ result[0] = '<'; break;
+ case 155: /* single right-pointing angle quote */
+ case 8250: /* single right-pointing angle quote */
+ result[0] = '>'; break;
+ case 8482: /* small tilde */
+ result[0] = '~'; break;
+
+ default:
+ /* unmappable character - return space */
+ result[0] = (unsigned char)' ';
+ }
+ }
+ result[1] = 0;
+}
+
#ifndef os_tzset
void os_tzset() {}
#endif
+void os_nonstop_mode(int flag) {}
+
+void os_advise_load_charmap(const char *id, const char *ldesc, const char *sysinfo) {}
+
+void os_gen_charmap_filename(char *filename, char *internal_id, char *argv0) {}
+
+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) {
+ // CUrrently unsupported
+ return 0;
+}
+
+void os_flush() {
+ g_vm->glk_tick();
+}
+
+char *os_strlwr(char *s) {
+ for (char *p = s; *p; ++p)
+ *p = tolower(*p);
+ return s;
+}
+
+void os_expause() {
+#ifdef USE_EXPAUSE
+ os_printz("(Strike any key to exit...)");
+ os_flush();
+ os_waitc();
+#endif /* USE_EXPAUSE */
+}
+
+void os_plain(void) {}
} // End of namespace TADS
} // End of namespace Glk
diff --git a/engines/glk/tads/os_glk.h b/engines/glk/tads/os_glk.h
index 237502a..7b76876 100644
--- a/engines/glk/tads/os_glk.h
+++ b/engines/glk/tads/os_glk.h
@@ -42,6 +42,7 @@ namespace TADS {
#define OSPATHURL "/"
#define OSPATHSEP ':'
#define OS_NEWLINE_SEQ "\n"
+#define DBG_OFF
/* maximum width (in characters) of a line of text */
#define OS_MAXWIDTH 255
@@ -2802,8 +2803,7 @@ void os_xlat_html4(unsigned int html4_char,
* 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);
+void os_gen_charmap_filename(char *filename, char *internal_id, char *argv0);
/*
* Receive notification that a character mapping file has been loaded.
@@ -2912,599 +2912,6 @@ void os_get_charmap(char *mapname, int charmap_id);
*/
#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
diff --git a/engines/glk/tads/os_parse.cpp b/engines/glk/tads/os_parse.cpp
new file mode 100644
index 0000000..d722d14
--- /dev/null
+++ b/engines/glk/tads/os_parse.cpp
@@ -0,0 +1,989 @@
+/* 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/os_parse.h"
+#include "glk/glk_types.h"
+
+namespace Glk {
+namespace TADS {
+
+#ifdef GLK_MODULE_UNICODE
+
+enum CHARMAPS { OS_UTF8, OS_CP1251, OS_CP1252, OS_MACROMAN, OS_UNKNOWN };
+
+static uint os_charmap = OS_UTF8;
+
+uint is_cyrillic(unsigned char ch)
+{
+ if (ch >= 0xBC)
+ return 1;
+
+ switch (ch)
+ {
+ case 0x80:
+ case 0x81:
+ case 0x83:
+ case 0x8A:
+ case 0x8C:
+ case 0x8D:
+ case 0x8E:
+ case 0x8F:
+ case 0x90:
+ case 0x9A:
+ case 0x9C:
+ case 0x9D:
+ case 0x9E:
+ case 0x9F:
+ case 0xA1:
+ case 0xA2:
+ case 0xA3:
+ case 0xA5:
+ case 0xA8:
+ case 0xAA:
+ case 0xAF:
+ case 0xB2:
+ case 0xB3:
+ case 0xB4:
+ case 0xB8:
+ case 0xBA:
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+uint is_macroman (unsigned char ch)
+{
+ switch (ch)
+ {
+ /* trademarks */
+ case 0xA8:
+ case 0xAA:
+ return 1;
+
+ /* dashes and right quotes */
+ case 0xD0:
+ case 0xD1:
+ case 0xD3:
+ case 0xD5:
+ return 1;
+
+ /* accents */
+ case 0x8E:
+ case 0x8F:
+ case 0x9A:
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+uint is_cp1252 (unsigned char ch)
+{
+ switch (ch)
+ {
+ /* trademarks */
+ case 0x99:
+ case 0xAE:
+ return 1;
+
+ /* dashes and right quotes */
+ case 0x92:
+ case 0x94:
+ case 0x96:
+ case 0x97:
+ return 1;
+
+ /* accents */
+ case 0xE8:
+ case 0xE9:
+ case 0xF6:
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+uint identify_chars(const unsigned char *buf, uint buflen, uint32 *out, uint outlen) {
+ uint pos = 0;
+ uint val = 0;
+ uint count_macroman = 0;
+ uint count_cp1252 = 0;
+ uint wordlen = 0;
+ uint cyrilen = 0;
+ uint charmap = OS_UNKNOWN;
+
+ while (pos < buflen)
+ {
+ val = buf[pos++];
+
+ count_macroman += is_macroman(val);
+ count_cp1252 += is_cp1252(val);
+
+ if (val != 0x20)
+ {
+ wordlen++;
+ cyrilen += is_cyrillic(val);
+ }
+ else
+ {
+ if (wordlen == cyrilen)
+ {
+ charmap = OS_CP1251;
+ break;
+ }
+ wordlen = 0;
+ cyrilen = 0;
+ }
+ }
+
+ if (charmap == OS_CP1251)
+ os_charmap = OS_CP1251;
+ else if (count_cp1252 >= count_macroman)
+ os_charmap = OS_CP1252;
+ else
+ os_charmap = OS_MACROMAN;
+
+ return os_parse_chars(buf, buflen, out, outlen);
+}
+
+uint parse_utf8(const unsigned char *buf, uint buflen, uint32 *out, uint outlen)
+{
+ uint pos = 0;
+ uint outpos = 0;
+ uint res;
+ uint val0, val1, val2, val3;
+
+ while (outpos < outlen)
+ {
+ if (pos >= buflen)
+ break;
+
+ val0 = buf[pos++];
+
+ if (val0 < 0x80)
+ {
+ res = val0;
+ out[outpos++] = res;
+ continue;
+ }
+
+ if ((val0 & 0xe0) == 0xc0)
+ {
+ if (pos+1 > buflen)
+ {
+ //printf("incomplete two-byte character\n");
+ return identify_chars(buf, buflen, out, outlen);
+ }
+ val1 = buf[pos++];
+ if ((val1 & 0xc0) != 0x80)
+ {
+ //printf("malformed two-byte character\n");
+ return identify_chars(buf, buflen, out, outlen);
+ }
+ res = (val0 & 0x1f) << 6;
+ res |= (val1 & 0x3f);
+ out[outpos++] = res;
+ continue;
+ }
+
+ if ((val0 & 0xf0) == 0xe0)
+ {
+ if (pos+2 > buflen)
+ {
+ //printf("incomplete three-byte character\n");
+ return identify_chars(buf, buflen, out, outlen);
+ }
+ val1 = buf[pos++];
+ val2 = buf[pos++];
+ if ((val1 & 0xc0) != 0x80)
+ {
+ //printf("malformed three-byte character\n");
+ return identify_chars(buf, buflen, out, outlen);
+ }
+ if ((val2 & 0xc0) != 0x80)
+ {
+ //printf("malformed three-byte character\n");
+ return identify_chars(buf, buflen, out, outlen);
+ }
+ res = (((val0 & 0xf)<<12) & 0x0000f000);
+ res |= (((val1 & 0x3f)<<6) & 0x00000fc0);
+ res |= (((val2 & 0x3f)) & 0x0000003f);
+ out[outpos++] = res;
+ continue;
+ }
+
+ if ((val0 & 0xf0) == 0xf0)
+ {
+ if ((val0 & 0xf8) != 0xf0)
+ {
+ //printf("malformed four-byte character\n");
+ return identify_chars(buf, buflen, out, outlen);
+ }
+ if (pos+3 > buflen)
+ {
+ //printf("incomplete four-byte character\n");
+ return identify_chars(buf, buflen, out, outlen);
+ }
+ val1 = buf[pos++];
+ val2 = buf[pos++];
+ val3 = buf[pos++];
+ if ((val1 & 0xc0) != 0x80)
+ {
+ //printf("malformed four-byte character\n");
+ return identify_chars(buf, buflen, out, outlen);
+ }
+ if ((val2 & 0xc0) != 0x80)
+ {
+ //printf("malformed four-byte character\n");
+ return identify_chars(buf, buflen, out, outlen);
+ }
+ if ((val3 & 0xc0) != 0x80)
+ {
+ //printf("malformed four-byte character\n");
+ return identify_chars(buf, buflen, out, outlen);
+ }
+ res = (((val0 & 0x7)<<18) & 0x1c0000);
+ res |= (((val1 & 0x3f)<<12) & 0x03f000);
+ res |= (((val2 & 0x3f)<<6) & 0x000fc0);
+ res |= (((val3 & 0x3f)) & 0x00003f);
+ out[outpos++] = res;
+ continue;
+ }
+
+ //printf("malformed character\n");
+ return identify_chars(buf, buflen, out, outlen);
+ }
+
+ return outpos;
+}
+
+uint prepare_utf8(const uint32 *buf, uint buflen, unsigned char *out, uint outlen)
+{
+ uint i=0, k=0;
+
+ /*convert UTF-32 to UTF-8 */
+ while (i < buflen && k < outlen)
+ {
+ if ((buf[i] < 0x80))
+ {
+ out[k] = buf[i];
+ k++;
+ }
+ else if ((buf[i] < 0x800) && (k < outlen - 1))
+ {
+ out[k ] = (0xC0 | ((buf[i] & 0x7C0) >> 6));
+ out[k+1] = (0x80 | (buf[i] & 0x03F) );
+ k = k + 2;
+ }
+ else if ((buf[i] < 0x10000) && (k < outlen - 2))
+ {
+ out[k ] = (0xE0 | ((buf[i] & 0xF000) >> 12));
+ out[k+1] = (0x80 | ((buf[i] & 0x0FC0) >> 6));
+ out[k+2] = (0x80 | (buf[i] & 0x003F) );
+ k = k + 3;
+ }
+ else if ((buf[i] < 0x200000) && (k < outlen - 3))
+ {
+ out[k ] = (0xF0 | ((buf[i] & 0x1C0000) >> 18));
+ out[k+1] = (0x80 | ((buf[i] & 0x03F000) >> 12));
+ out[k+2] = (0x80 | ((buf[i] & 0x000FC0) >> 6));
+ out[k+3] = (0x80 | (buf[i] & 0x00003F) );
+ k = k + 4;
+ }
+ else
+ {
+ out[k] = '?';
+ k++;
+ }
+ i++;
+ }
+
+ return k;
+}
+
+/* http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1251.TXT */
+
+static const uint CP1251ToUnicode[128] = {
+ 0x0402, 0x0403, 0x201A, 0x0453, 0x201E, 0x2026, 0x2020, 0x2021, 0x20AC, 0x2030,
+ 0x0409, 0x2039, 0x040A, 0x040C, 0x040B, 0x040F, 0x0452, 0x2018, 0x2019, 0x201C,
+ 0x201D, 0x2022, 0x2013, 0x2014, 0x003F, 0x2122, 0x0459, 0x203A, 0x045A, 0x045C,
+ 0x045B, 0x045F, 0x00A0, 0x040E, 0x045E, 0x0408, 0x00A4, 0x0490, 0x00A6, 0x00A7,
+ 0x0401, 0x00A9, 0x0404, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0407, 0x00B0, 0x00B1,
+ 0x0406, 0x0456, 0x0491, 0x00B5, 0x00B6, 0x00B7, 0x0451, 0x2116, 0x0454, 0x00BB,
+ 0x0458, 0x0405, 0x0455, 0x0457, 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415,
+ 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
+ 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429,
+ 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, 0x0430, 0x0431, 0x0432, 0x0433,
+ 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D,
+ 0x043E, 0x043F, 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
+ 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F
+
+};
+
+uint parse_cp1251(const unsigned char *buf, uint buflen, uint32 *out, uint outlen) {
+ uint pos = 0;
+ uint outpos = 0;
+ uint res;
+
+ while (outpos < outlen)
+ {
+ if (pos >= buflen)
+ break;
+
+ res = buf[pos++];
+
+ if (res < 0x80)
+ out[outpos++] = res;
+ else
+ out[outpos++] = CP1251ToUnicode[res - 0x80];
+ }
+
+ return outpos;
+}
+
+uint prepare_cp1251(const uint *buf, uint buflen,
+ unsigned char *out, uint outlen)
+{
+ uint pos = 0;
+ uint outpos = 0;
+ uint res;
+
+ while (outpos < outlen)
+ {
+ if (pos >= buflen)
+ break;
+
+ res = buf[pos++];
+
+ if (res < 0x80)
+ {
+ out[outpos++] = res;
+ continue;
+ }
+
+ switch (res)
+ {
+ case 0x0402: out[outpos++] = 0x80; break;
+ case 0x0403: out[outpos++] = 0x81; break;
+ case 0x201A: out[outpos++] = 0x82; break;
+ case 0x0453: out[outpos++] = 0x83; break;
+ case 0x201E: out[outpos++] = 0x84; break;
+ case 0x2026: out[outpos++] = 0x85; break;
+ case 0x2020: out[outpos++] = 0x86; break;
+ case 0x2021: out[outpos++] = 0x87; break;
+ case 0x20AC: out[outpos++] = 0x88; break;
+ case 0x2030: out[outpos++] = 0x89; break;
+ case 0x0409: out[outpos++] = 0x8A; break;
+ case 0x2039: out[outpos++] = 0x8B; break;
+ case 0x040A: out[outpos++] = 0x8C; break;
+ case 0x040C: out[outpos++] = 0x8D; break;
+ case 0x040B: out[outpos++] = 0x8E; break;
+ case 0x040F: out[outpos++] = 0x8F; break;
+
+ case 0x0452: out[outpos++] = 0x90; break;
+ case 0x2018: out[outpos++] = 0x91; break;
+ case 0x2019: out[outpos++] = 0x92; break;
+ case 0x201C: out[outpos++] = 0x93; break;
+ case 0x201D: out[outpos++] = 0x94; break;
+ case 0x2022: out[outpos++] = 0x95; break;
+ case 0x2013: out[outpos++] = 0x96; break;
+ case 0x2014: out[outpos++] = 0x97; break;
+ case 0x2122: out[outpos++] = 0x99; break;
+ case 0x0459: out[outpos++] = 0x9A; break;
+ case 0x203A: out[outpos++] = 0x9B; break;
+ case 0x045A: out[outpos++] = 0x9C; break;
+ case 0x045C: out[outpos++] = 0x9D; break;
+ case 0x045B: out[outpos++] = 0x9E; break;
+ case 0x045F: out[outpos++] = 0x9F; break;
+
+ case 0x00A0: out[outpos++] = 0xA0; break;
+ case 0x040E: out[outpos++] = 0xA1; break;
+ case 0x045E: out[outpos++] = 0xA2; break;
+ case 0x0408: out[outpos++] = 0xA3; break;
+ case 0x00A4: out[outpos++] = 0xA4; break;
+ case 0x0490: out[outpos++] = 0xA5; break;
+ case 0x00A6: out[outpos++] = 0xA6; break;
+ case 0x00A7: out[outpos++] = 0xA7; break;
+ case 0x0401: out[outpos++] = 0xA8; break;
+ case 0x00A9: out[outpos++] = 0xA9; break;
+ case 0x0404: out[outpos++] = 0xAA; break;
+ case 0x00AB: out[outpos++] = 0xAB; break;
+ case 0x00AC: out[outpos++] = 0xAC; break;
+ case 0x00AD: out[outpos++] = 0xAD; break;
+ case 0x00AE: out[outpos++] = 0xAE; break;
+ case 0x0407: out[outpos++] = 0xAF; break;
+
+ case 0x00B0: out[outpos++] = 0xB0; break;
+ case 0x00B1: out[outpos++] = 0xB1; break;
+ case 0x0406: out[outpos++] = 0xB2; break;
+ case 0x0456: out[outpos++] = 0xB3; break;
+ case 0x0491: out[outpos++] = 0xB4; break;
+ case 0x00B5: out[outpos++] = 0xB5; break;
+ case 0x00B6: out[outpos++] = 0xB6; break;
+ case 0x00B7: out[outpos++] = 0xB7; break;
+ case 0x0451: out[outpos++] = 0xB8; break;
+ case 0x2116: out[outpos++] = 0xB9; break;
+ case 0x0454: out[outpos++] = 0xBA; break;
+ case 0x00BB: out[outpos++] = 0xBB; break;
+ case 0x0458: out[outpos++] = 0xBC; break;
+ case 0x0405: out[outpos++] = 0xBD; break;
+ case 0x0455: out[outpos++] = 0xBE; break;
+ case 0x0457: out[outpos++] = 0xBF; break;
+
+ case 0x0410: out[outpos++] = 0xC0; break;
+ case 0x0411: out[outpos++] = 0xC1; break;
+ case 0x0412: out[outpos++] = 0xC2; break;
+ case 0x0413: out[outpos++] = 0xC3; break;
+ case 0x0414: out[outpos++] = 0xC4; break;
+ case 0x0415: out[outpos++] = 0xC5; break;
+ case 0x0416: out[outpos++] = 0xC6; break;
+ case 0x0417: out[outpos++] = 0xC7; break;
+ case 0x0418: out[outpos++] = 0xC8; break;
+ case 0x0419: out[outpos++] = 0xC9; break;
+ case 0x041A: out[outpos++] = 0xCA; break;
+ case 0x041B: out[outpos++] = 0xCB; break;
+ case 0x041C: out[outpos++] = 0xCC; break;
+ case 0x041D: out[outpos++] = 0xCD; break;
+ case 0x041E: out[outpos++] = 0xCE; break;
+ case 0x041F: out[outpos++] = 0xCF; break;
+
+ case 0x0420: out[outpos++] = 0xD0; break;
+ case 0x0421: out[outpos++] = 0xD1; break;
+ case 0x0422: out[outpos++] = 0xD2; break;
+ case 0x0423: out[outpos++] = 0xD3; break;
+ case 0x0424: out[outpos++] = 0xD4; break;
+ case 0x0425: out[outpos++] = 0xD5; break;
+ case 0x0426: out[outpos++] = 0xD6; break;
+ case 0x0427: out[outpos++] = 0xD7; break;
+ case 0x0428: out[outpos++] = 0xD8; break;
+ case 0x0429: out[outpos++] = 0xD9; break;
+ case 0x042A: out[outpos++] = 0xDA; break;
+ case 0x042B: out[outpos++] = 0xDB; break;
+ case 0x042C: out[outpos++] = 0xDC; break;
+ case 0x042D: out[outpos++] = 0xDD; break;
+ case 0x042E: out[outpos++] = 0xDE; break;
+ case 0x042F: out[outpos++] = 0xDF; break;
+
+ case 0x0430: out[outpos++] = 0xE0; break;
+ case 0x0431: out[outpos++] = 0xE1; break;
+ case 0x0432: out[outpos++] = 0xE2; break;
+ case 0x0433: out[outpos++] = 0xE3; break;
+ case 0x0434: out[outpos++] = 0xE4; break;
+ case 0x0435: out[outpos++] = 0xE5; break;
+ case 0x0436: out[outpos++] = 0xE6; break;
+ case 0x0437: out[outpos++] = 0xE7; break;
+ case 0x0438: out[outpos++] = 0xE8; break;
+ case 0x0439: out[outpos++] = 0xE9; break;
+ case 0x043A: out[outpos++] = 0xEA; break;
+ case 0x043B: out[outpos++] = 0xEB; break;
+ case 0x043C: out[outpos++] = 0xEC; break;
+ case 0x043D: out[outpos++] = 0xED; break;
+ case 0x043E: out[outpos++] = 0xEE; break;
+ case 0x043F: out[outpos++] = 0xEF; break;
+
+ case 0x0440: out[outpos++] = 0xF0; break;
+ case 0x0441: out[outpos++] = 0xF1; break;
+ case 0x0442: out[outpos++] = 0xF2; break;
+ case 0x0443: out[outpos++] = 0xF3; break;
+ case 0x0444: out[outpos++] = 0xF4; break;
+ case 0x0445: out[outpos++] = 0xF5; break;
+ case 0x0446: out[outpos++] = 0xF6; break;
+ case 0x0447: out[outpos++] = 0xF7; break;
+ case 0x0448: out[outpos++] = 0xF8; break;
+ case 0x0449: out[outpos++] = 0xF9; break;
+ case 0x044A: out[outpos++] = 0xFA; break;
+ case 0x044B: out[outpos++] = 0xFB; break;
+ case 0x044C: out[outpos++] = 0xFC; break;
+ case 0x044D: out[outpos++] = 0xFD; break;
+ case 0x044E: out[outpos++] = 0xFE; break;
+ case 0x044F: out[outpos++] = 0xFF; break;
+
+ default:
+ /* undefined */
+ out[outpos++] = '?';
+ break;
+ }
+
+ continue;
+ }
+
+ return outpos;
+}
+
+/* http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT */
+
+static const uint CP1252ToUnicode[128] = {
+ 0x20AC, 0x003F, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030,
+ 0x0160, 0x2039, 0x0152, 0x003F, 0x017D, 0x003F, 0x003F, 0x2018, 0x2019, 0x201C,
+ 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x003F,
+ 0x017E, 0x0178, 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
+ 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, 0x00B0, 0x00B1,
+ 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x00B9, 0x00BA, 0x00BB,
+ 0x00BC, 0x00BD, 0x00BE, 0x00BF, 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5,
+ 0x00C6, 0x00C7, 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
+ 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, 0x00D8, 0x00D9,
+ 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF, 0x00E0, 0x00E1, 0x00E2, 0x00E3,
+ 0x00E4, 0x00E5, 0x00E6, 0x00E7, 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED,
+ 0x00EE, 0x00EF, 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
+ 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
+};
+
+uint parse_cp1252(const unsigned char *buf, uint buflen, uint32 *out, uint outlen) {
+ uint pos = 0;
+ uint outpos = 0;
+ uint res;
+
+ while (outpos < outlen)
+ {
+ if (pos >= buflen)
+ break;
+
+ res = buf[pos++];
+
+ if (res < 0x80)
+ out[outpos++] = res;
+ else
+ out[outpos++] = CP1252ToUnicode[res - 0x80];
+ }
+
+ return outpos;
+}
+
+uint prepare_cp1252(const uint *buf, uint buflen,
+ unsigned char *out, uint outlen)
+{
+ uint pos = 0;
+ uint outpos = 0;
+ uint res;
+
+ while (outpos < outlen)
+ {
+ if (pos >= buflen)
+ break;
+
+ res = buf[pos++];
+
+ if (res < 0x80)
+ {
+ out[outpos++] = res;
+ continue;
+ }
+
+ switch (res)
+ {
+ case 0x20AC: out[outpos++] = 0x80; break;
+ case 0x201A: out[outpos++] = 0x82; break;
+ case 0x0192: out[outpos++] = 0x83; break;
+ case 0x201E: out[outpos++] = 0x84; break;
+ case 0x2026: out[outpos++] = 0x85; break;
+ case 0x2020: out[outpos++] = 0x86; break;
+ case 0x2021: out[outpos++] = 0x87; break;
+ case 0x02C6: out[outpos++] = 0x88; break;
+ case 0x2030: out[outpos++] = 0x89; break;
+ case 0x0160: out[outpos++] = 0x8A; break;
+ case 0x2039: out[outpos++] = 0x8B; break;
+ case 0x0152: out[outpos++] = 0x8C; break;
+ case 0x017D: out[outpos++] = 0x8E; break;
+
+ case 0x2018: out[outpos++] = 0x91; break;
+ case 0x2019: out[outpos++] = 0x92; break;
+ case 0x201C: out[outpos++] = 0x93; break;
+ case 0x201D: out[outpos++] = 0x94; break;
+ case 0x2022: out[outpos++] = 0x95; break;
+ case 0x2013: out[outpos++] = 0x96; break;
+ case 0x2014: out[outpos++] = 0x97; break;
+ case 0x02DC: out[outpos++] = 0x98; break;
+ case 0x2122: out[outpos++] = 0x99; break;
+ case 0x0161: out[outpos++] = 0x9A; break;
+ case 0x203A: out[outpos++] = 0x9B; break;
+ case 0x0153: out[outpos++] = 0x9C; break;
+ case 0x017E: out[outpos++] = 0x9E; break;
+ case 0x0178: out[outpos++] = 0x9F; break;
+
+ case 0x00A0: out[outpos++] = 0xA0; break;
+ case 0x00A1: out[outpos++] = 0xA1; break;
+ case 0x00A2: out[outpos++] = 0xA2; break;
+ case 0x00A3: out[outpos++] = 0xA3; break;
+ case 0x00A4: out[outpos++] = 0xA4; break;
+ case 0x00A5: out[outpos++] = 0xA5; break;
+ case 0x00A6: out[outpos++] = 0xA6; break;
+ case 0x00A7: out[outpos++] = 0xA7; break;
+ case 0x00A8: out[outpos++] = 0xA8; break;
+ case 0x00A9: out[outpos++] = 0xA9; break;
+ case 0x00AA: out[outpos++] = 0xAA; break;
+ case 0x00AB: out[outpos++] = 0xAB; break;
+ case 0x00AC: out[outpos++] = 0xAC; break;
+ case 0x00AD: out[outpos++] = 0xAD; break;
+ case 0x00AE: out[outpos++] = 0xAE; break;
+ case 0x00AF: out[outpos++] = 0xAF; break;
+
+ case 0x00B0: out[outpos++] = 0xB0; break;
+ case 0x00B1: out[outpos++] = 0xB1; break;
+ case 0x00B2: out[outpos++] = 0xB2; break;
+ case 0x00B3: out[outpos++] = 0xB3; break;
+ case 0x00B4: out[outpos++] = 0xB4; break;
+ case 0x00B5: out[outpos++] = 0xB5; break;
+ case 0x00B6: out[outpos++] = 0xB6; break;
+ case 0x00B7: out[outpos++] = 0xB7; break;
+ case 0x00B8: out[outpos++] = 0xB8; break;
+ case 0x00B9: out[outpos++] = 0xB9; break;
+ case 0x00BA: out[outpos++] = 0xBA; break;
+ case 0x00BB: out[outpos++] = 0xBB; break;
+ case 0x00BC: out[outpos++] = 0xBC; break;
+ case 0x00BD: out[outpos++] = 0xBD; break;
+ case 0x00BE: out[outpos++] = 0xBE; break;
+ case 0x00BF: out[outpos++] = 0xBF; break;
+
+ case 0x00C0: out[outpos++] = 0xC0; break;
+ case 0x00C1: out[outpos++] = 0xC1; break;
+ case 0x00C2: out[outpos++] = 0xC2; break;
+ case 0x00C3: out[outpos++] = 0xC3; break;
+ case 0x00C4: out[outpos++] = 0xC4; break;
+ case 0x00C5: out[outpos++] = 0xC5; break;
+ case 0x00C6: out[outpos++] = 0xC6; break;
+ case 0x00C7: out[outpos++] = 0xC7; break;
+ case 0x00C8: out[outpos++] = 0xC8; break;
+ case 0x00C9: out[outpos++] = 0xC9; break;
+ case 0x00CA: out[outpos++] = 0xCA; break;
+ case 0x00CB: out[outpos++] = 0xCB; break;
+ case 0x00CC: out[outpos++] = 0xCC; break;
+ case 0x00CD: out[outpos++] = 0xCD; break;
+ case 0x00CE: out[outpos++] = 0xCE; break;
+ case 0x00CF: out[outpos++] = 0xCF; break;
+
+ case 0x00D0: out[outpos++] = 0xD0; break;
+ case 0x00D1: out[outpos++] = 0xD1; break;
+ case 0x00D2: out[outpos++] = 0xD2; break;
+ case 0x00D3: out[outpos++] = 0xD3; break;
+ case 0x00D4: out[outpos++] = 0xD4; break;
+ case 0x00D5: out[outpos++] = 0xD5; break;
+ case 0x00D6: out[outpos++] = 0xD6; break;
+ case 0x00D7: out[outpos++] = 0xD7; break;
+ case 0x00D8: out[outpos++] = 0xD8; break;
+ case 0x00D9: out[outpos++] = 0xD9; break;
+ case 0x00DA: out[outpos++] = 0xDA; break;
+ case 0x00DB: out[outpos++] = 0xDB; break;
+ case 0x00DC: out[outpos++] = 0xDC; break;
+ case 0x00DD: out[outpos++] = 0xDD; break;
+ case 0x00DE: out[outpos++] = 0xDE; break;
+ case 0x00DF: out[outpos++] = 0xDF; break;
+
+ case 0x00E0: out[outpos++] = 0xE0; break;
+ case 0x00E1: out[outpos++] = 0xE1; break;
+ case 0x00E2: out[outpos++] = 0xE2; break;
+ case 0x00E3: out[outpos++] = 0xE3; break;
+ case 0x00E4: out[outpos++] = 0xE4; break;
+ case 0x00E5: out[outpos++] = 0xE5; break;
+ case 0x00E6: out[outpos++] = 0xE6; break;
+ case 0x00E7: out[outpos++] = 0xE7; break;
+ case 0x00E8: out[outpos++] = 0xE8; break;
+ case 0x00E9: out[outpos++] = 0xE9; break;
+ case 0x00EA: out[outpos++] = 0xEA; break;
+ case 0x00EB: out[outpos++] = 0xEB; break;
+ case 0x00EC: out[outpos++] = 0xEC; break;
+ case 0x00ED: out[outpos++] = 0xED; break;
+ case 0x00EE: out[outpos++] = 0xEE; break;
+ case 0x00EF: out[outpos++] = 0xEF; break;
+
+ case 0x00F0: out[outpos++] = 0xF0; break;
+ case 0x00F1: out[outpos++] = 0xF1; break;
+ case 0x00F2: out[outpos++] = 0xF2; break;
+ case 0x00F3: out[outpos++] = 0xF3; break;
+ case 0x00F4: out[outpos++] = 0xF4; break;
+ case 0x00F5: out[outpos++] = 0xF5; break;
+ case 0x00F6: out[outpos++] = 0xF6; break;
+ case 0x00F7: out[outpos++] = 0xF7; break;
+ case 0x00F8: out[outpos++] = 0xF8; break;
+ case 0x00F9: out[outpos++] = 0xF9; break;
+ case 0x00FA: out[outpos++] = 0xFA; break;
+ case 0x00FB: out[outpos++] = 0xFB; break;
+ case 0x00FC: out[outpos++] = 0xFC; break;
+ case 0x00FD: out[outpos++] = 0xFD; break;
+ case 0x00FE: out[outpos++] = 0xFE; break;
+ case 0x00FF: out[outpos++] = 0xFF; break;
+
+ default:
+ /* undefined */
+ out[outpos++] = '?';
+ break;
+ }
+
+ continue;
+ }
+
+ return outpos;
+}
+
+/* http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/ROMAN.TXT */
+
+static const uint MacRomanToUnicode[128] = {
+ 0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1, 0x00E0, 0x00E2,
+ 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8, 0x00EA, 0x00EB, 0x00ED, 0x00EC,
+ 0x00EE, 0x00EF, 0x00F1, 0x00F3, 0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9,
+ 0x00FB, 0x00FC, 0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF,
+ 0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8, 0x221E, 0x00B1,
+ 0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211, 0x220F, 0x03C0, 0x222B, 0x00AA,
+ 0x00BA, 0x03A9, 0x00E6, 0x00F8, 0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248,
+ 0x2206, 0x00AB, 0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153,
+ 0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA, 0x00FF, 0x0178,
+ 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02, 0x2021, 0x00B7, 0x201A, 0x201E,
+ 0x2030, 0x00C2, 0x00CA, 0x00C1, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC,
+ 0x00D3, 0x00D4, 0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC,
+ 0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7
+};
+
+uint parse_mac(const unsigned char *buf, uint buflen, uint32 *out, uint outlen) {
+ uint pos = 0;
+ uint outpos = 0;
+ uint res;
+
+ while (outpos < outlen)
+ {
+ if (pos >= buflen)
+ break;
+
+ res = buf[pos++];
+
+ if (res < 0x80)
+ out[outpos++] = res;
+ else
+ out[outpos++] = MacRomanToUnicode[res - 0x80];
+ }
+
+ return outpos;
+}
+
+uint prepare_mac(const uint *buf, uint buflen, unsigned char *out, uint outlen) {
+ uint pos = 0;
+ uint outpos = 0;
+ uint res;
+
+ while (outpos < outlen)
+ {
+ if (pos >= buflen)
+ break;
+
+ res = buf[pos++];
+
+ if (res < 0x80)
+ {
+ out[outpos++] = res;
+ continue;
+ }
+
+ switch (res)
+ {
+ case 0x00C4: out[outpos++] = 0x80; break;
+ case 0x00C5: out[outpos++] = 0x81; break;
+ case 0x00C7: out[outpos++] = 0x82; break;
+ case 0x00C9: out[outpos++] = 0x83; break;
+ case 0x00D1: out[outpos++] = 0x84; break;
+ case 0x00D6: out[outpos++] = 0x85; break;
+ case 0x00DC: out[outpos++] = 0x86; break;
+ case 0x00E1: out[outpos++] = 0x87; break;
+ case 0x00E0: out[outpos++] = 0x88; break;
+ case 0x00E2: out[outpos++] = 0x89; break;
+ case 0x00E4: out[outpos++] = 0x8A; break;
+ case 0x00E3: out[outpos++] = 0x8B; break;
+ case 0x00E5: out[outpos++] = 0x8C; break;
+ case 0x00E7: out[outpos++] = 0x8D; break;
+ case 0x00E9: out[outpos++] = 0x8E; break;
+ case 0x00E8: out[outpos++] = 0x8F; break;
+
+ case 0x00EA: out[outpos++] = 0x90; break;
+ case 0x00EB: out[outpos++] = 0x91; break;
+ case 0x00ED: out[outpos++] = 0x92; break;
+ case 0x00EC: out[outpos++] = 0x93; break;
+ case 0x00EE: out[outpos++] = 0x94; break;
+ case 0x00EF: out[outpos++] = 0x95; break;
+ case 0x00F1: out[outpos++] = 0x96; break;
+ case 0x00F3: out[outpos++] = 0x97; break;
+ case 0x00F2: out[outpos++] = 0x98; break;
+ case 0x00F4: out[outpos++] = 0x99; break;
+ case 0x00F6: out[outpos++] = 0x9A; break;
+ case 0x00F5: out[outpos++] = 0x9B; break;
+ case 0x00FA: out[outpos++] = 0x9C; break;
+ case 0x00F9: out[outpos++] = 0x9D; break;
+ case 0x00FB: out[outpos++] = 0x9E; break;
+ case 0x00FC: out[outpos++] = 0x9F; break;
+
+ case 0x2020: out[outpos++] = 0xA0; break;
+ case 0x00B0: out[outpos++] = 0xA1; break;
+ case 0x00A2: out[outpos++] = 0xA2; break;
+ case 0x00A3: out[outpos++] = 0xA3; break;
+ case 0x00A7: out[outpos++] = 0xA4; break;
+ case 0x2022: out[outpos++] = 0xA5; break;
+ case 0x00B6: out[outpos++] = 0xA6; break;
+ case 0x00DF: out[outpos++] = 0xA7; break;
+ case 0x00AE: out[outpos++] = 0xA8; break;
+ case 0x00A9: out[outpos++] = 0xA9; break;
+ case 0x2122: out[outpos++] = 0xAA; break;
+ case 0x00B4: out[outpos++] = 0xAB; break;
+ case 0x00A8: out[outpos++] = 0xAC; break;
+ case 0x2260: out[outpos++] = 0xAD; break;
+ case 0x00C6: out[outpos++] = 0xAE; break;
+ case 0x00D8: out[outpos++] = 0xAF; break;
+
+ case 0x221E: out[outpos++] = 0xB0; break;
+ case 0x00B1: out[outpos++] = 0xB1; break;
+ case 0x2264: out[outpos++] = 0xB2; break;
+ case 0x2265: out[outpos++] = 0xB3; break;
+ case 0x00A5: out[outpos++] = 0xB4; break;
+ case 0x00B5: out[outpos++] = 0xB5; break;
+ case 0x2202: out[outpos++] = 0xB6; break;
+ case 0x2211: out[outpos++] = 0xB7; break;
+ case 0x220F: out[outpos++] = 0xB8; break;
+ case 0x03C0: out[outpos++] = 0xB9; break;
+ case 0x222B: out[outpos++] = 0xBA; break;
+ case 0x00AA: out[outpos++] = 0xBB; break;
+ case 0x00BA: out[outpos++] = 0xBC; break;
+ case 0x03A9: out[outpos++] = 0xBD; break;
+ case 0x00E6: out[outpos++] = 0xBE; break;
+ case 0x00F8: out[outpos++] = 0xBF; break;
+
+ case 0x00BF: out[outpos++] = 0xC0; break;
+ case 0x00A1: out[outpos++] = 0xC1; break;
+ case 0x00AC: out[outpos++] = 0xC2; break;
+ case 0x221A: out[outpos++] = 0xC3; break;
+ case 0x0192: out[outpos++] = 0xC4; break;
+ case 0x2248: out[outpos++] = 0xC5; break;
+ case 0x2206: out[outpos++] = 0xC6; break;
+ case 0x00AB: out[outpos++] = 0xC7; break;
+ case 0x00BB: out[outpos++] = 0xC8; break;
+ case 0x2026: out[outpos++] = 0xC9; break;
+ case 0x00A0: out[outpos++] = 0xCA; break;
+ case 0x00C0: out[outpos++] = 0xCB; break;
+ case 0x00C3: out[outpos++] = 0xCC; break;
+ case 0x00D5: out[outpos++] = 0xCD; break;
+ case 0x0152: out[outpos++] = 0xCE; break;
+ case 0x0153: out[outpos++] = 0xCF; break;
+
+ case 0x2013: out[outpos++] = 0xD0; break;
+ case 0x2014: out[outpos++] = 0xD1; break;
+ case 0x201C: out[outpos++] = 0xD2; break;
+ case 0x201D: out[outpos++] = 0xD3; break;
+ case 0x2018: out[outpos++] = 0xD4; break;
+ case 0x2019: out[outpos++] = 0xD5; break;
+ case 0x00F7: out[outpos++] = 0xD6; break;
+ case 0x25CA: out[outpos++] = 0xD7; break;
+ case 0x00FF: out[outpos++] = 0xD8; break;
+ case 0x0178: out[outpos++] = 0xD9; break;
+ case 0x2044: out[outpos++] = 0xDA; break;
+ case 0x20AC: out[outpos++] = 0xDB; break;
+ case 0x2039: out[outpos++] = 0xDC; break;
+ case 0x203A: out[outpos++] = 0xDD; break;
+ case 0xFB01: out[outpos++] = 0xDE; break;
+ case 0xFB02: out[outpos++] = 0xDF; break;
+
+ case 0x2021: out[outpos++] = 0xE0; break;
+ case 0x00B7: out[outpos++] = 0xE1; break;
+ case 0x201A: out[outpos++] = 0xE2; break;
+ case 0x201E: out[outpos++] = 0xE3; break;
+ case 0x2030: out[outpos++] = 0xE4; break;
+ case 0x00C2: out[outpos++] = 0xE5; break;
+ case 0x00CA: out[outpos++] = 0xE6; break;
+ case 0x00C1: out[outpos++] = 0xE7; break;
+ case 0x00CB: out[outpos++] = 0xE8; break;
+ case 0x00C8: out[outpos++] = 0xE9; break;
+ case 0x00CD: out[outpos++] = 0xEA; break;
+ case 0x00CE: out[outpos++] = 0xEB; break;
+ case 0x00CF: out[outpos++] = 0xEC; break;
+ case 0x00CC: out[outpos++] = 0xED; break;
+ case 0x00D3: out[outpos++] = 0xEE; break;
+ case 0x00D4: out[outpos++] = 0xEF; break;
+
+ case 0xF8FF: out[outpos++] = 0xF0; break;
+ case 0x00D2: out[outpos++] = 0xF1; break;
+ case 0x00DA: out[outpos++] = 0xF2; break;
+ case 0x00DB: out[outpos++] = 0xF3; break;
+ case 0x00D9: out[outpos++] = 0xF4; break;
+ case 0x0131: out[outpos++] = 0xF5; break;
+ case 0x02C6: out[outpos++] = 0xF6; break;
+ case 0x02DC: out[outpos++] = 0xF7; break;
+ case 0x00AF: out[outpos++] = 0xF8; break;
+ case 0x02D8: out[outpos++] = 0xF9; break;
+ case 0x02D9: out[outpos++] = 0xFA; break;
+ case 0x02DA: out[outpos++] = 0xFB; break;
+ case 0x00B8: out[outpos++] = 0xFC; break;
+ case 0x02DD: out[outpos++] = 0xFD; break;
+ case 0x02DB: out[outpos++] = 0xFE; break;
+ case 0x02C7: out[outpos++] = 0xFF; break;
+
+ default:
+ /* undefined */
+ out[outpos++] = '?';
+ break;
+ }
+
+ continue;
+ }
+
+ return outpos;
+}
+
+uint os_parse_chars(const unsigned char *buf, uint buflen, uint32 *out, uint outlen) {
+ switch (os_charmap)
+ {
+ case OS_UTF8:
+ return parse_utf8(buf, buflen, out, outlen);
+
+ case OS_CP1251:
+ return parse_cp1251(buf, buflen, out, outlen);
+
+ case OS_CP1252:
+ return parse_cp1252(buf, buflen, out, outlen);
+
+ case OS_MACROMAN:
+ return parse_mac(buf, buflen, out, outlen);
+
+ default:
+ return 0;
+ }
+}
+
+uint os_prepare_chars(const uint32 *buf, uint buflen, unsigned char *out, uint outlen) {
+ switch (os_charmap) {
+ case OS_UTF8:
+ return prepare_utf8(buf, buflen, out, outlen);
+
+ case OS_CP1251:
+ return prepare_cp1251(buf, buflen, out, outlen);
+
+ case OS_CP1252:
+ return prepare_cp1252(buf, buflen, out, outlen);
+
+ case OS_MACROMAN:
+ return prepare_mac(buf, buflen, out, outlen);
+
+ default:
+ return 0;
+ }
+}
+
+#endif /* GLK_MODULE_UNICODE */
+
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/os_parse.h b/engines/glk/tads/os_parse.h
new file mode 100644
index 0000000..05f88c2
--- /dev/null
+++ b/engines/glk/tads/os_parse.h
@@ -0,0 +1,43 @@
+/* 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_PARSE
+#define GLK_TADS_OS_PARSE
+
+#include "common/scummsys.h"
+
+namespace Glk {
+namespace TADS {
+
+extern uint os_parse_chars(const unsigned char *buf, uint buflen, uint32 *out, uint outlen);
+
+extern uint os_prepare_chars(const uint32 *buf, uint buflen, unsigned char *out, uint outlen);
+
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/debug.h b/engines/glk/tads/tads2/debug.h
index 52695cf..0e7369e 100644
--- a/engines/glk/tads/tads2/debug.h
+++ b/engines/glk/tads/tads2/debug.h
@@ -25,6 +25,7 @@
#include "glk/tads/tads2/lib.h"
#include "glk/tads/tads2/object.h"
+#include "glk/tads/os_glk.h"
namespace Glk {
namespace TADS {
@@ -181,7 +182,7 @@ void dbgclin(struct tokcxdef *tokctx, objnum objn, uint ofs);
* the program define the appropriate implementation through the linker.
*/
#ifdef DBG_OFF
-#define dbgpresent() (FALSE)
+#define dbgpresent() (false)
#else
int dbgpresent();
#endif
@@ -545,22 +546,22 @@ void trcsho(void);
*/
#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)
+#define dbgenter(ctx, bp, self, target, prop, binum, argc)
+#define dbgleave(ctx, exittype)
+#define dbgdump(ctx) ((void)0)
+#define dbgrst(ctx) ((void)0)
+#define dbgframe(ctx, frofs, linofs)
+#define dbgssi(ctx, ofs, instr, err, p) ((void)0)
#else /* DBG_OFF */
-# define dbgenter(ctx, bp, self, target, prop, binum, argc) \
+#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) \
+#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)
+#define dbgssi(ctx, ofs, instr, err, p) dbgss(ctx, ofs, instr, err, p)
#endif /* DBG_OFF */
/* ======================================================================== */
diff --git a/engines/glk/tads/tads2/error_message.cpp b/engines/glk/tads/tads2/error_message.cpp
index 3cfe8de..ace3613 100644
--- a/engines/glk/tads/tads2/error_message.cpp
+++ b/engines/glk/tads/tads2/error_message.cpp
@@ -27,58 +27,42 @@ namespace Glk {
namespace TADS {
namespace TADS2 {
-
-/*--------------------------------- lerini ---------------------------------*/
-/*
- * lerini - allocate and initialize an error context. Returns a
- * pointer to an initialized error context if successful, 0 otherwise.
- */
errcxdef *lerini() {
- errcxdef *errcx; /* error context */
+ errcxdef *errcx; /* error context */
- /* allocate an error context */
- if (!(errcx = (errcxdef *)ltk_suballoc(sizeof(errcxdef))))
- {
- /* failure */
- return((errcxdef *)0);
- }
+ // allocate an error context
+ if (!(errcx = (errcxdef *)ltk_suballoc(sizeof(errcxdef)))) {
+ // failure
+ return((errcxdef *)0);
+ }
- /* initialize the error context */
- errcx->errcxfp = (osfildef *)0; /* no error file handle */
- errcx->errcxofs = 0; /* no offset in argument buffer */
- errcx->errcxlog = ltk_errlog; /* error logging routine */
- errcx->errcxlgc = errcx; /* error logging context */
+ // initialize the error context
+ errcx->errcxfp = (osfildef *)0; /* no error file handle */
+ errcx->errcxofs = 0; /* no offset in argument buffer */
+ errcx->errcxlog = ltk_errlog; /* error logging routine */
+ errcx->errcxlgc = errcx; /* error logging context */
- /* return the new context */
- return(errcx);
+ // return the new context
+ return errcx;
}
-
-/*--------------------------------- lerfre ---------------------------------*/
-/*
- * lerfre - FREe error context allocated by errini.
- */
-void lerfre(errcxdef *errcx) {
- /* free the context */
- ltk_subfree(errcx);
+void errini(errcxdef *ctx, char *arg0) {
+ VARUSED(ctx);
+ VARUSED(arg0);
}
+void errini(errcxdef *ctx, osfildef *fp) {
+ VARUSED(ctx);
+ VARUSED(fp);
+}
-/*--------------------------------- errmsg ---------------------------------*/
-/*
- * errmsg - format error message number 'err' into the given buffer.
- */
-void errmsg(errcxdef *ctx, char *outbuf, int outbufl, uint err) {
- sprintf(outbuf, "Error #%d occured.", err);
+void lerfre(errcxdef *errcx) {
+ // free the context
+ ltk_subfree(errcx);
}
-/*--------------------------------- errini ---------------------------------*/
-/*
- * errini - initialize error system.
- */
-void errini(errcxdef *ctx, char *arg0) {
- VARUSED(ctx);
- VARUSED(arg0);
+void errmsg(errcxdef *ctx, char *outbuf, uint outbufl, uint err) {
+ sprintf(outbuf, "Error #%d occured.", err);
}
} // End of namespace TADS2
diff --git a/engines/glk/tads/tads2/file_io.cpp b/engines/glk/tads/tads2/file_io.cpp
index bef91f1..10b8bca 100644
--- a/engines/glk/tads/tads2/file_io.cpp
+++ b/engines/glk/tads/tads2/file_io.cpp
@@ -1769,6 +1769,12 @@ ret_error:
return TRUE;
}
+void fioxor(uchar *p, uint siz, uint seed, uint inc)
+{
+ for (; siz; seed += inc, --siz)
+ *p++ ^= (uchar)seed;
+}
+
} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/get_string.cpp b/engines/glk/tads/tads2/get_string.cpp
new file mode 100644
index 0000000..69982fc
--- /dev/null
+++ b/engines/glk/tads/tads2/get_string.cpp
@@ -0,0 +1,173 @@
+/* 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/text_io.h"
+#include "glk/tads/tads2/os.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+/*
+ * Global variable with the current command logging file. If this is
+ * not null, we'll log each command that we read to this file.
+ */
+osfildef *cmdfile;
+
+/*
+ * External global with the current script input file. If this is
+ * non-null, we'll read commands from this file rather than from the
+ * keyboard.
+ */
+extern osfildef *scrfp;
+
+/*
+ * External global indicating script echo status. If we're reading from
+ * a script input file (i.e., scrfp is non-null), and this variable is
+ * true, it indicates that we're in "quiet" mode reading the script, so
+ * we will not echo commands that we read from the script file to the
+ * display.
+ */
+extern int scrquiet;
+
+/*
+ * getstring reads a string from the keyboard, doing all necessary
+ * output flushing. Prompting is to be done by the caller. This
+ * routine should be called instead of os_gets.
+ */
+int getstring(char *prompt, char *buf, int bufl)
+{
+ char *result;
+ int savemoremode;
+ int retval = 0;
+
+ /* show prompt if one was given and flush output */
+ savemoremode = setmore(0);
+ if (prompt != 0)
+ {
+ /* display the prompt text */
+ outformat(prompt);
+
+ /* make sure it shows up in the log file as well */
+ out_logfile_print(prompt, FALSE);
+ }
+ outflushn(0);
+ outreset();
+
+ /* read from the command input file if we have one */
+ if (scrfp != 0)
+ {
+ int quiet = scrquiet;
+
+ /* try reading from command input file */
+ if ((result = qasgets(buf, bufl)) == 0)
+ {
+ /*
+ * End of command input file; return to reading the
+ * keyboard. If we didn't already show the prompt, show it
+ * now.
+ *
+ * Note that qasgets() will have closed the script file
+ * before returning eof, so we won't directly read the
+ * command here but instead handle it later when we check to
+ * see if we need to read from the keyboard.
+ */
+ if (quiet && prompt != 0)
+ outformat(prompt);
+ outflushn(0);
+ outreset();
+
+ /*
+ * Guarantee that moremode is turned back on. (moremode can
+ * be turned off for one of two reasons: we're printing the
+ * prompt, or we're reading from a script with no pauses.
+ * In either case, moremode should be turned back on at this
+ * point. -CDN)
+ */
+ savemoremode = 1;
+
+ /* turn off NONSTOP mode now that we're done with the script */
+ os_nonstop_mode(FALSE);
+ }
+
+ /* success */
+ retval = 0;
+ }
+
+ /* if we don't have a script file, read from the keyboard */
+ if (scrfp == 0)
+ {
+ /* update the status line */
+ runstat();
+
+ /* read a line from the keyboard */
+ result = (char *)os_gets((uchar *)buf, bufl);
+
+ /*
+ * if the result is null, we're at eof, so return a non-zero
+ * value; otherwise, we successfully read a command, so return
+ * zero
+ */
+ retval = (result == 0);
+ }
+
+ /* restore the original "more" mode */
+ setmore(savemoremode);
+
+ /* check the result */
+ if (retval != 0)
+ {
+ /* we got an error reading the command - return the error */
+ return retval;
+ }
+ else
+ {
+ char *p;
+
+ /*
+ * we got a command, or at least a partial command (if we timed
+ * out, we may still have a partial line in the buffer) - write
+ * the input line to the log and/or command files, as
+ * appropriate
+ */
+ out_logfile_print(buf, TRUE);
+ if (cmdfile != 0)
+ {
+ os_fprintz(cmdfile, ">");
+ os_fprintz(cmdfile, buf);
+ os_fprintz(cmdfile, "\n");
+ }
+
+ /* translate the input to the internal character set */
+ for (p = buf ; *p != '\0' ; ++p)
+ *p = cmap_n2i(*p);
+
+ /* success */
+ return retval;
+ }
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/ltk.cpp b/engines/glk/tads/tads2/ltk.cpp
index b112839..c385ee7 100644
--- a/engines/glk/tads/tads2/ltk.cpp
+++ b/engines/glk/tads/tads2/ltk.cpp
@@ -130,15 +130,15 @@ void ltk_free(void *mem) {
* ltk_errlog - ERRor LOGging function. Logs an error from the LER
* system.
*/
-void ltk_errlog(void *ctx, char *fac, int errno, int argc, erradef *argv) {
+void ltk_errlog(void *ctx, char *fac, int errCode, int argc, erradef *argv) {
char buf[128]; /* formatted error buffer */
char msg[128]; /* message buffer */
/* $$$ filter out error #504 $$$ */
- if (errno == 504) return;
+ if (errCode == 504) return;
/* get the error message into msg */
- errmsg((errcxdef *)ctx, msg, sizeof(msg), errno);
+ errmsg((errcxdef *)ctx, msg, sizeof(msg), errCode);
/* format the error message */
errfmt(buf, (int)sizeof(buf), msg, argc, argv);
diff --git a/engines/glk/tads/tads2/output.cpp b/engines/glk/tads/tads2/output.cpp
index 6aa39b5..8b9fd74 100644
--- a/engines/glk/tads/tads2/output.cpp
+++ b/engines/glk/tads/tads2/output.cpp
@@ -3554,6 +3554,33 @@ void out_set_doublespace(int dbl)
doublespace = dbl;
}
+int tio_askfile(const char *prompt, char *reply, int replen, int prompt_type, os_filetype_t file_type) {
+ // let the OS layer handle it
+ return os_askfile(prompt, reply, replen, prompt_type, file_type);
+}
+
+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) {
+ // call the OS implementation
+ return os_input_dialog(icon_id, prompt, standard_button_set,
+ buttons, button_count,
+ default_index, cancel_index);
+}
+
+void outfmt(tiocxdef *ctx, uchar *txt) {
+ uint len;
+
+ VARUSED(ctx);
+
+ /* read the length prefix */
+ len = osrp2(txt) - 2;
+ txt += 2;
+
+ /* write out the string */
+ tioputslen(ctx, (char *)txt, len);
+}
+
} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/post_compilation.cpp b/engines/glk/tads/tads2/post_compilation.cpp
index cd501db..c1755ca 100644
--- a/engines/glk/tads/tads2/post_compilation.cpp
+++ b/engines/glk/tads/tads2/post_compilation.cpp
@@ -28,6 +28,38 @@ namespace Glk {
namespace TADS {
namespace TADS2 {
+void supcont(void *ctx, objnum obj, prpnum prp) {
+ // No implementation
+}
+
+void supivoc(supcxdef *ctx) {
+ // No implementation
+}
+
+void supfind(errcxdef *ctx, tokthdef *tab, voccxdef *voc,
+ objnum *preinit, int warnlevel, int casefold) {
+ // No implementation
+}
+
+void suprsrv(supcxdef *sup, void(*bif[])(struct bifcxdef *, int),
+ toktdef *tab, int fncntmax, int v1compat, char *new_do, int casefold) {
+ // No implementation
+}
+
+void supbif(supcxdef *sup, void(*bif[])(struct bifcxdef *, int), int bifsiz) {
+ // No implementation
+}
+
+void sup_log_undefobj(mcmcxdef *mctx, errcxdef *ec, int err,
+ char *sym_name, int sym_name_len, objnum objn) {
+ // No implementation
+}
+
+void supivoc1(supcxdef *sup, voccxdef *ctx, vocidef *v, objnum target,
+ int inh_from_obj, int flags) {
+ // No implementation
+}
+
} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/post_compilation.h b/engines/glk/tads/tads2/post_compilation.h
index fdd1261..1d7ac7a 100644
--- a/engines/glk/tads/tads2/post_compilation.h
+++ b/engines/glk/tads/tads2/post_compilation.h
@@ -46,34 +46,34 @@ struct supcxdef {
};
/* set up contents list for one object for demand-on-load */
-inline void supcont(void *ctx, objnum obj, prpnum prp);
+extern void supcont(void *ctx, objnum obj, prpnum prp);
/* set up inherited vocabulary (called before executing game) */
-inline void supivoc(supcxdef *ctx);
+extern void supivoc(supcxdef *ctx);
/* find required objects/functions */
-inline void supfind(errcxdef *ctx, tokthdef *tab, voccxdef *voc,
+extern void supfind(errcxdef *ctx, tokthdef *tab, voccxdef *voc,
objnum *preinit, int warnlevel, int casefold);
/* set up reserved words */
-inline void suprsrv(supcxdef *sup, void (*bif[])(struct bifcxdef *, int),
+extern 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) */
-inline void supbif(supcxdef *sup, void (*bif[])(struct bifcxdef *, int),
+extern void supbif(supcxdef *sup, void (*bif[])(struct bifcxdef *, int),
int bifsiz);
/* log an undefined-object error */
-inline void sup_log_undefobj(mcmcxdef *mctx, errcxdef *ec, int err,
+extern 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 */
-inline void supivoc1(supcxdef *sup, voccxdef *ctx, vocidef *v, objnum target,
+extern 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 */
-inline void supgnam(char *buf, tokthdef *tab, objnum objn);
+extern void supgnam(char *buf, tokthdef *tab, objnum objn);
/* table of built-in functions */
struct supbidef {
diff --git a/engines/glk/tads/tads2/runtime_driver.cpp b/engines/glk/tads/tads2/runtime_driver.cpp
index b2169dd..20ba6c1 100644
--- a/engines/glk/tads/tads2/runtime_driver.cpp
+++ b/engines/glk/tads/tads2/runtime_driver.cpp
@@ -53,6 +53,84 @@ void tok_read_defines(tokcxdef *tctx, osfildef *fp, errcxdef *ec)
}
/* dummy debugger functions */
+
+int dbgbpset(dbgcxdef *ctx, char *addr, int *bpnum) { return 0; }
+
+int dbgbpat(dbgcxdef *ctx, objnum objn, objnum self,
+ uint ofs, int *bpnum, char *bpname, int toggle,
+ char *condition, int *did_set) { return 0; }
+
+int dbgbpatid(dbgcxdef *ctx, int bpnum, objnum target, objnum self,
+ uint ofs, char *bpname, int toggle, char *cond,
+ int *did_set) { return 0; }
+
+int dbgisbp(dbgcxdef *ctx, objnum target, objnum self, uint ofs, int *bpnum) { return 0; }
+
+int dbgisbpena(dbgcxdef *ctx, int bpnum) { return 0; }
+
+int dbgbpdel(dbgcxdef *ctx, int bpnum) { return 0; }
+
+int dbgbpdis(dbgcxdef *ctx, int bpnum, int disable) { return 0; }
+
+int dbgbpsetcond(dbgcxdef *ctx, int bpnum, char *cond) { return 0; }
+
+void dbgbplist(dbgcxdef *ctx, void(*dispfn)(void *ctx, const char *str, int len), void *dispctx) {}
+
+void dbgbpenum(dbgcxdef *ctx, void(*cbfunc)(void *cbctx, int bpnum, const char *desc,
+ const char *cond, int disabled), void *cbctx) {}
+
+void dbgbpeach(dbgcxdef *ctx, void(*fn)(void *, int, uchar *, uint), void *fnctx) {}
+
+int dbgbpgetinfo(dbgcxdef *ctx, int bpnum, char *descbuf, size_t descbuflen,
+ char *condbuf, size_t condbuflen) { return 0; }
+
+int dbgeval(dbgcxdef *ctx, char *expr,
+ void(*dispfn)(void *dispctx, const char *str, int strl),
+ void *dispctx, int level, int showtype) { return 0; }
+
+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) { return 0 ;}
+
+void dbgenumlcl(dbgcxdef *ctx, int level,
+ void(*func)(void *ctx, const char *lclnam, size_t lclnamlen),
+ void *cbctx) {}
+
+int dbgcompile(dbgcxdef *ctx, char *expr, dbgfdef *fr, objnum *objn,
+ int speculative) { return 0; }
+
+void dbgwhere(dbgcxdef *ctx, char *buf) {}
+
+int dbgwxset(dbgcxdef *ctx, char *expr, int *wxnum, int level) { return 0; }
+
+int dbgwxdel(dbgcxdef *ctx, int wxnum) { return 0; }
+
+void dbgwxupd(dbgcxdef *ctx,
+ void(*dispfn)(void *dispctx, const char *txt, int len),
+ void *dispctx) {}
+
+void dbgswitch(struct lindef **linp, struct lindef *newlin) {}
+
+void dbguini(dbgcxdef *ctx, const char *game_filename) {}
+
+void dbguini2(dbgcxdef *ctx) {}
+
+int dbgu_err_resume(dbgcxdef *ctx) { return 0; }
+
+int dbgu_find_src(const char *origname, int origlen,
+ char *fullname, size_t full_len, int must_find_file) { return 0; }
+
+void dbgucmd(dbgcxdef *ctx, int bphit, int err, unsigned int *exec_ofs) {}
+
+void dbguquitting(dbgcxdef *ctx) {}
+
+void dbguterm(dbgcxdef *ctx) {}
+
+void dbguerr(dbgcxdef *ctx, int errnum, char *msg) {}
+
void trchid(void) {}
void trcsho(void) {}
diff --git a/engines/glk/tads/tads2/text_io.h b/engines/glk/tads/tads2/text_io.h
index 33a914e..6519465 100644
--- a/engines/glk/tads/tads2/text_io.h
+++ b/engines/glk/tads/tads2/text_io.h
@@ -212,8 +212,7 @@ void out_set_doublespace(int dbl);
* 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);
+int tio_askfile(const char *prompt, char *reply, int replen, int prompt_type, os_filetype_t file_type);
/*
* Display a dialog, using a system-defined dialog (via os_input_dialog)
Commit: 5aacb456458af18581fcf72fc96824027c566e79
https://github.com/scummvm/scummvm/commit/5aacb456458af18581fcf72fc96824027c566e79
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00
Commit Message:
GLK: TADS2: Added plygo
Changed paths:
engines/glk/tads/tads2/play.cpp
diff --git a/engines/glk/tads/tads2/play.cpp b/engines/glk/tads/tads2/play.cpp
index 115d2b6..0c78c6d 100644
--- a/engines/glk/tads/tads2/play.cpp
+++ b/engines/glk/tads/tads2/play.cpp
@@ -21,13 +21,335 @@
*/
#include "glk/tads/tads2/play.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/file_io.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
void plygo(runcxdef *run, voccxdef *voc, tiocxdef *tio, objnum preinit, char *restore_fname) {
- // TODO
+ int err;
+ errcxdef *ec = run->runcxerr;
+ char filbuf[128];
+ int first_time;
+ int noreg inited = FALSE;
+
+ NOREG((&inited));
+
+ first_time = TRUE;
+
+ /*
+ * Write out the special <?T2> HTML sequence, in case we're on an HTML
+ * system. This tells the HTML parser to use the parsing rules for
+ * TADS 2 callers.
+ */
+ outformat("\\H+<?T2>\\H-");
+
+startover:
+ if (!inited)
+ {
+ /* use Me as the format-string actor for preinit and init */
+ tiosetactor(voc->voccxtio, voc->voccxme);
+
+ /*
+ * Run preinit, if it hasn't been run yet. Note that we only
+ * do this the first time through. If we come back here via the
+ * restart function, preinit will already have been run in the
+ * restart function itself, so we don't need to run it again.
+ */
+ if (first_time)
+ {
+ /* make a note that we've been through here once already */
+ first_time = FALSE;
+
+ /* remember the preinit function for later use in restarting */
+ voc->voccxpreinit = preinit;
+
+ /* run the preinit() function */
+ ERRBEGIN(ec)
+ {
+ /* reset the interpreter */
+ runrst(run);
+
+ /* reset the parser */
+ voc_stk_ini(voc, (uint)VOC_STACK_SIZE);
+
+ /* run preinit */
+ if (preinit != MCMONINV)
+ runfn(run, preinit, 0);
+ }
+ ERRCATCH(ec, err)
+ {
+ /* if they restarted, go back and start over */
+ if (err == ERR_RUNRESTART)
+ goto startover;
+
+ /* resignal the error */
+ errrse(ec);
+ }
+ ERREND(ec);
+ }
+
+ /*
+ * Run the "init" function. Do NOT run init if we're restoring
+ * a game directly from the command line AND there's an
+ * initRestore function defined.
+ */
+ if (restore_fname == 0 || voc->voccxinitrestore == MCMONINV)
+ {
+ ERRBEGIN(ec)
+ {
+ /* reset the interpreter */
+ runrst(run);
+
+ /* reset the parser */
+ voc_stk_ini(voc, (uint)VOC_STACK_SIZE);
+
+ /* run init */
+ runfn(run, (objnum)voc->voccxini, 0);
+ }
+ ERRCATCH(ec, err)
+ {
+ /* if they restarted, go back and start over */
+ if (err == ERR_RUNRESTART)
+ goto startover;
+
+ /* resignal the error */
+ errrse(ec);
+ }
+ ERREND(ec);
+ }
+ }
+
+ /* next time through, we'll need to run init again */
+ inited = FALSE;
+
+ /*
+ * check for startup parameter file to restore - if there's a
+ * system-specific parameter file specified, pretend that it was
+ * specified as the restore file
+ */
+ if (os_paramfile(filbuf))
+ restore_fname = filbuf;
+
+ /* check for a file to restore */
+ if (restore_fname != 0)
+ {
+ /*
+ * Check to see if the game file supports the initRestore
+ * function. If so, call it to restore the game. If not,
+ * restore the game directly.
+ */
+ if (voc->voccxinitrestore != MCMONINV)
+ {
+ char restore_buf[OSFNMAX*2];
+ char *src;
+ char *dst;
+
+ /* convert any backslashes to double backslashes */
+ for (src = restore_fname, dst = restore_buf ;
+ *src != '\0' && dst + 2 < restore_buf + sizeof(restore_buf) ;
+ ++src)
+ {
+ switch(*src)
+ {
+ case '\\':
+ /* it's a backslash - double it */
+ *dst++ = '\\';
+ *dst++ = '\\';
+ break;
+
+ default:
+ /* copy the character as-is */
+ *dst++ = *src;
+ }
+ }
+
+ /*
+ * all the game's initRestore function with the name of
+ * saved game file to restore as the argument
+ */
+
+ /* reset the interpreter */
+ runrst(run);
+
+ /* reset the parser */
+ voc_stk_ini(voc, (uint)VOC_STACK_SIZE);
+
+ /* push the game file name and run initRestore */
+ runpstr(run, restore_buf, dst - restore_buf, 0);
+ runfn(run, (objnum)voc->voccxinitrestore, 1);
+ }
+ else
+ {
+ /* restore the game */
+ os_printz("\n\n[Restoring saved game]\n\n");
+ err = fiorso(voc, restore_fname);
+ if (err)
+ {
+ char buf[60 + OSFNMAX];
+
+ sprintf(buf, "\n\nError: unable to restore file \"%s\"\n\n",
+ restore_fname);
+ os_printz(buf);
+ }
+ }
+
+ /* forget the saved game name, in case we restore */
+ restore_fname = 0;
+ }
+
+ /* clear out the redo command buffer */
+ voc->voccxredobuf[0] = '\0';
+
+ /* read and execute commands */
+ for (;;)
+ {
+ char buf[128];
+
+ err = 0;
+ ERRBEGIN(ec)
+
+ /* read a new command if there's nothing to redo */
+ if (!voc->voccxredo)
+ {
+ /* reset hidden output so we're showing output */
+ tioshow(tio);
+ tioflush(tio);
+
+ /* clear the interpreter stack */
+ runrst(run);
+
+ /* read a command */
+ vocread(voc, MCMONINV, MCMONINV, buf, (int)sizeof(buf), 0);
+
+ /* special qa checking */
+ if (buf[0] == '@')
+ {
+ int quiet = FALSE;
+ char *p;
+
+ p = buf + 1;
+ if (*p == '@')
+ {
+ /* turn off MORE mode */
+ setmore(0);
+
+ /* set NONSTOP mode in the OS layer */
+ os_nonstop_mode(TRUE);
+
+ /* skip the extra '@' */
+ ++p;
+ }
+ else if (*p == '!')
+ {
+ quiet = TRUE;
+ ++p;
+ }
+ while (*p != '\0' && t_isspace(*p)) ++p;
+ if (*p != '\0')
+ {
+ /* open the named file */
+ qasopn(p, quiet);
+ }
+ else
+ {
+ char fname[256];
+
+ /* no file was named - ask the user to select a file */
+ if (tio_askfile("Read script file:", fname, sizeof(fname),
+ OS_AFP_OPEN, OSFTCMD) == 0)
+ qasopn(fname, quiet);
+ }
+ goto end_loop;
+ }
+ }
+
+ /*
+ * If there's redo in the redo buffer, use it now. If the
+ * buffer is empty and the redo flag is set, we'll just
+ * re-execute whatever's in our internal buffer.
+ */
+ if (voc->voccxredo && voc->voccxredobuf[0] != '\0')
+ {
+ /* copy the redo buffer into our internal buffer */
+ strcpy(buf, voc->voccxredobuf);
+
+ /* we've consumed it now, so clear it out */
+ voc->voccxredobuf[0] = '\0';
+ }
+
+ /* we've now consumed the redo */
+ voc->voccxredo = FALSE;
+
+ /* clear any pending break that's queued up */
+ (void)os_break();
+
+ /* execute the command */
+ (void)voccmd(voc, buf, (uint)sizeof(buf));
+
+ end_loop:
+ ERRCATCH(ec, err)
+ {
+ if (err != ERR_RUNQUIT
+ && err != ERR_RUNRESTART
+ && !(err == ERR_RUNABRT && voc->voccxredo))
+ errclog(ec);
+ }
+ ERREND(ec);
+
+ /* on interrupt, undo last command (which was partially executed) */
+ if (err == ERR_USRINT && voc->voccxundo)
+ {
+ ERRBEGIN(ec)
+ objundo(voc->voccxmem, voc->voccxundo);
+ ERRCATCH(ec, err)
+ if (err != ERR_NOUNDO && err != ERR_ICUNDO)
+ errrse(ec);
+ ERREND(ec)
+ }
+
+ /* if they want to quit, we're done */
+ if (err == ERR_RUNQUIT)
+ break;
+ else if (err == ERR_RUNRESTART)
+ goto startover;
+ }
+
+ /*
+ * If we're quitting, give the debugger one last chance at taking
+ * control. If it just returns, we can go ahead and terminate, but
+ * if it wants it can restart the game by calling bifrst() as
+ * normal.
+ */
+ ERRBEGIN(ec)
+ {
+ /* clear anything in the debugger stack trace */
+ run->runcxdbg->dbgcxfcn = 0;
+ run->runcxdbg->dbgcxdep = 0;
+
+ /* tell the debugger the game has exited */
+ dbguquitting(run->runcxdbg);
+ }
+ ERRCATCH(ec, err)
+ {
+ switch(err)
+ {
+ case ERR_RUNRESTART:
+ /* they restarted the game - re-enter the play loop */
+ goto startover;
+
+ case ERR_RUNQUIT:
+ /* quitting - proceed to return as normal */
+ break;
+
+ default:
+ /* resignal any other error */
+ errrse(ec);
+ }
+ }
+ ERREND(ec);
}
} // End of namespace TADS2
Commit: e3454b5d96c258f59a1186eae1bdfd01d5f7af3e
https://github.com/scummvm/scummvm/commit/e3454b5d96c258f59a1186eae1bdfd01d5f7af3e
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00
Commit Message:
GLK: TADS: Don't support RTL
Changed paths:
engines/glk/glk.h
engines/glk/tads/tads.cpp
engines/glk/tads/tads.h
diff --git a/engines/glk/glk.h b/engines/glk/glk.h
index 6b95eb2..f96f384 100644
--- a/engines/glk/glk.h
+++ b/engines/glk/glk.h
@@ -86,7 +86,7 @@ protected:
/**
* Returns true whether a given feature is supported by the engine
*/
- virtual bool hasFeature(EngineFeature f) const;
+ virtual bool hasFeature(EngineFeature f) const override;
/**
* Setup the video mode
@@ -139,16 +139,6 @@ public:
}
/**
- * Returns the bitset of game features
- */
- uint32 getFeatures() const;
-
- /**
- * Returns whether the game is a demo
- */
- bool isDemo() const;
-
- /**
* Returns the language
*/
Common::Language getLanguage() const { return _gameDescription._language; };
diff --git a/engines/glk/tads/tads.cpp b/engines/glk/tads/tads.cpp
index 61e13cd..b021b02 100644
--- a/engines/glk/tads/tads.cpp
+++ b/engines/glk/tads/tads.cpp
@@ -35,6 +35,14 @@ TADS::TADS(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gam
os_init(nullptr, nullptr, 0, 0, 0);
}
+bool TADS::hasFeature(EngineFeature f) const {
+ // The TADS codebases rely on a lot of static globals, so RTL can't be supported
+ if (f == kSupportsRTL)
+ return false;
+
+ return GlkAPI::hasFeature(f);
+}
+
Common::Error TADS::loadGameData(strid_t file) {
// TODO
return Common::kNoError;
diff --git a/engines/glk/tads/tads.h b/engines/glk/tads/tads.h
index e4640f0..6ad8780 100644
--- a/engines/glk/tads/tads.h
+++ b/engines/glk/tads/tads.h
@@ -49,6 +49,11 @@ public:
virtual InterpreterType getInterpreterType() const override { return INTERPRETER_SCOTT; }
/**
+ * Returns true whether a given feature is supported by the engine
+ */
+ virtual bool hasFeature(EngineFeature f) const override;
+
+ /**
* Load a savegame from the passed stream
*/
virtual Common::Error loadGameData(strid_t file) override;
Commit: 0d172c8ca695ae67b881b92add19e488c6053fbb
https://github.com/scummvm/scummvm/commit/0d172c8ca695ae67b881b92add19e488c6053fbb
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00
Commit Message:
GLK: Fix freeing closed windows
Changed paths:
engines/glk/windows.cpp
engines/glk/windows.h
diff --git a/engines/glk/windows.cpp b/engines/glk/windows.cpp
index ffb1fbf..7b6833f 100644
--- a/engines/glk/windows.cpp
+++ b/engines/glk/windows.cpp
@@ -187,10 +187,21 @@ void Windows::windowClose(Window *win, StreamResult *result) {
return;
}
+ // Detach window being closed from parent pair window
+ pairWin->_children.remove_at(index);
+ win->_parent = nullptr;
+
if (!(pairWin->_dir & winmethod_Arbitrary)) {
- sibWin = (index = ((int)pairWin->_children.size() - 1)) ?
- pairWin->_children.front() : pairWin->_children[index + 1];
+ // Get the remaining child window
+ assert(pairWin->_children.size() == 1);
+ sibWin = pairWin->_children.front();
+
+ // Detach it from the pair window
+ index = pairWin->_children.indexOf(sibWin);
+ assert(index >= 0);
+ pairWin->_children.remove_at(index);
+ // Set up window as either the singular root, or grandparent pair window if one exists
grandparWin = dynamic_cast<PairWindow *>(pairWin->_parent);
if (!grandparWin) {
_rootWin = sibWin;
@@ -453,7 +464,12 @@ uint Windows::rgbShift(uint color) {
/*--------------------------------------------------------------------------*/
Windows::iterator &Windows::iterator::operator++() {
- _current = _windows->iterateTreeOrder(_current);
+ _current = _current->_next;
+ return *this;
+}
+
+Windows::iterator &Windows::iterator::operator--() {
+ _current = _current->_prev;
return *this;
}
diff --git a/engines/glk/windows.h b/engines/glk/windows.h
index 9cab07a..1974b3e 100644
--- a/engines/glk/windows.h
+++ b/engines/glk/windows.h
@@ -74,6 +74,11 @@ public:
iterator &operator++();
/**
+ * Move to previous
+ */
+ iterator &operator--();
+
+ /**
* Equality test
*/
bool operator==(const iterator &i) {
Commit: 0c02346b48e068dddff81bf4fde57040135deb51
https://github.com/scummvm/scummvm/commit/0c02346b48e068dddff81bf4fde57040135deb51
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:07-07:00
Commit Message:
GLK: TADS2: Fix os initialization
Changed paths:
engines/glk/tads/os_glk.cpp
engines/glk/tads/tads.cpp
engines/glk/tads/tads2/tads2.cpp
diff --git a/engines/glk/tads/os_glk.cpp b/engines/glk/tads/os_glk.cpp
index 884703c..90a5f00 100644
--- a/engines/glk/tads/os_glk.cpp
+++ b/engines/glk/tads/os_glk.cpp
@@ -92,7 +92,7 @@ int os_init(int *argc, char *argv[], const char *prompt,
/* close statuswin; reopened on request */
g_vm->glk_window_close(statuswin, 0);
- statuswin = NULL;
+ statuswin = nullptr;
g_vm->glk_set_window(mainwin);
diff --git a/engines/glk/tads/tads.cpp b/engines/glk/tads/tads.cpp
index b021b02..6ce2915 100644
--- a/engines/glk/tads/tads.cpp
+++ b/engines/glk/tads/tads.cpp
@@ -32,7 +32,6 @@ TADS *g_vm;
TADS::TADS(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc) {
g_vm = this;
- os_init(nullptr, nullptr, 0, 0, 0);
}
bool TADS::hasFeature(EngineFeature f) const {
diff --git a/engines/glk/tads/tads2/tads2.cpp b/engines/glk/tads/tads2/tads2.cpp
index 6203900..547cb0f 100644
--- a/engines/glk/tads/tads2/tads2.cpp
+++ b/engines/glk/tads/tads2/tads2.cpp
@@ -23,6 +23,7 @@
#include "glk/tads/tads2/tads2.h"
#include "glk/tads/tads2/appctx.h"
#include "glk/tads/tads2/runtime_app.h"
+#include "glk/tads/tads2/os.h"
namespace Glk {
namespace TADS {
@@ -32,11 +33,18 @@ TADS2::TADS2(OSystem *syst, const GlkGameDescription &gameDesc) : TADS(syst, gam
}
void TADS2::runGame() {
+ // Initialize the OS layer
+ os_init(nullptr, nullptr, 0, 0, 0);
+ os_instbrk(true);
+
char name[255];
strcpy(name, getFilename().c_str());
char *argv[2] = { nullptr, name };
trdmain(2, argv, nullptr, ".sav");
+
+ os_instbrk(false);
+ os_uninit();
}
} // End of namespace TADS2
Commit: aa5fd603b6fd623c138fd3529176847b5b538e49
https://github.com/scummvm/scummvm/commit/aa5fd603b6fd623c138fd3529176847b5b538e49
Author: dreammaster (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:07-07:00
Commit Message:
GLK: TADS2: Compilation fixes
Changed paths:
engines/glk/tads/os_buffer.h
engines/glk/tads/os_filetype.h
engines/glk/tads/os_glk.cpp
engines/glk/tads/os_glk.h
engines/glk/tads/tads2/built_in.cpp
engines/glk/tads/tads2/error.cpp
engines/glk/tads/tads2/error_handling.cpp
engines/glk/tads/tads2/error_handling.h
engines/glk/tads/tads2/line_source_file.cpp
engines/glk/tads/tads2/ltk.cpp
engines/glk/tads/tads2/ltk.h
engines/glk/tads/tads2/memory_cache_heap.cpp
engines/glk/tads/tads2/memory_cache_heap.h
engines/glk/tads/tads2/output.cpp
engines/glk/tads/tads2/text_io.h
engines/glk/tads/tads2/vocabulary.h
engines/glk/tads/tads2/vocabulary_parser.cpp
diff --git a/engines/glk/tads/os_buffer.h b/engines/glk/tads/os_buffer.h
index 3fc2209..4fa9662 100644
--- a/engines/glk/tads/os_buffer.h
+++ b/engines/glk/tads/os_buffer.h
@@ -33,6 +33,8 @@
#ifndef GLK_TADS_OS_BUFFER
#define GLK_TADS_OS_BUFFER
+#include "common/scummsys.h"
+
namespace Glk {
namespace TADS {
diff --git a/engines/glk/tads/os_filetype.h b/engines/glk/tads/os_filetype.h
index 93b1016..2a48c4b 100644
--- a/engines/glk/tads/os_filetype.h
+++ b/engines/glk/tads/os_filetype.h
@@ -55,7 +55,7 @@ enum os_filetype_t {
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) */
+ OSFTT3SAV = 15 /* T3 saved state file (.t3v) */
};
} // End of namespace TADS
diff --git a/engines/glk/tads/os_glk.cpp b/engines/glk/tads/os_glk.cpp
index 90a5f00..03d8a3a 100644
--- a/engines/glk/tads/os_glk.cpp
+++ b/engines/glk/tads/os_glk.cpp
@@ -641,8 +641,10 @@ unsigned char *os_gets(unsigned char *buf, size_t buflen)
* routine with use_timeout==false. The regular os_gets() would not
* satisfy this need, because it cannot resume an interrupted input.)
*/
+#if defined GLK_TIMERS && defined GLK_MODULE_LINE_ECHO
static char * timebuf = NULL;
static size_t timelen = 0;
+#endif
int os_gets_timeout(unsigned char *buf, size_t bufl,
unsigned long timeout_in_milliseconds, int use_timeout)
@@ -1099,5 +1101,12 @@ void os_expause() {
void os_plain(void) {}
+int memicmp(const char *s1, const char *s2, int len) {
+ Common::String cs1(s1, len);
+ Common::String cs2(s2, len);
+
+ return cs1.compareToIgnoreCase(cs2);
+}
+
} // End of namespace TADS
} // End of namespace Glk
diff --git a/engines/glk/tads/os_glk.h b/engines/glk/tads/os_glk.h
index 7b76876..ac5090f 100644
--- a/engines/glk/tads/os_glk.h
+++ b/engines/glk/tads/os_glk.h
@@ -3217,6 +3217,8 @@ int os_get_sysinfo(int code, void *param, long *result);
#define os_remainder_long(a, b) ((a) % (b))
#endif
+int memicmp(const char *s1, const char *s2, int len);
+
} // End of namespace TADS
} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/built_in.cpp b/engines/glk/tads/tads2/built_in.cpp
index 30c2772..c67399d 100644
--- a/engines/glk/tads/tads2/built_in.cpp
+++ b/engines/glk/tads/tads2/built_in.cpp
@@ -902,7 +902,7 @@ void bifrnd(bifcxdef *ctx, int argc)
/*
* case-insensitive substring matching
*/
-static char *bif_stristr(const char *s1, const char *s2)
+static const char *bif_stristr(const char *s1, const char *s2)
{
size_t s1len;
size_t s2len;
@@ -913,7 +913,7 @@ static char *bif_stristr(const char *s1, const char *s2)
{
/* if this is a match, return this substring */
if (memicmp(s1, s2, s2len) == 0)
- return (char *)s1;
+ return (const char *)s1;
}
return 0;
@@ -4146,7 +4146,7 @@ void bifinpdlg(bifcxdef *ctx, int argc)
uchar *p;
char prompt[256];
char lblbuf[256];
- char *labels[10];
+ const char *labels[10];
char *dst;
char *xp;
uint len;
diff --git a/engines/glk/tads/tads2/error.cpp b/engines/glk/tads/tads2/error.cpp
index eeb59cd..62c5e9a 100644
--- a/engines/glk/tads/tads2/error.cpp
+++ b/engines/glk/tads/tads2/error.cpp
@@ -93,7 +93,7 @@ int errfmt(char *outbuf, int outbufl, const char *fmt, int argc, const erradef *
static struct
{
int tokid;
- char *toknam;
+ const char *toknam;
} toklist[] =
{
{ TOKTSEM, "semicolon" },
@@ -106,7 +106,7 @@ int errfmt(char *outbuf, int outbufl, const char *fmt, int argc, const erradef *
{ TOKTWHILE, "\"while\"" },
{ TOKTLPAR, "left paren ('(')" },
{ TOKTEQ, "'='" },
- { 0, (char *)0 }
+ { 0, (const char *)nullptr }
};
for (i = 0 ; toklist[i].toknam ; ++i)
@@ -185,7 +185,7 @@ void errsign(errcxdef *ctx, int e) {
}
/* enter a string argument */
-char *errstr(errcxdef *ctx, char *str, int len) {
+char *errstr(errcxdef *ctx, const char *str, int len) {
char *ret = &ctx->errcxbuf[ctx->errcxofs];
memcpy(ret, str, (size_t)len);
diff --git a/engines/glk/tads/tads2/error_handling.cpp b/engines/glk/tads/tads2/error_handling.cpp
index b71fc8c..ac2b01d 100644
--- a/engines/glk/tads/tads2/error_handling.cpp
+++ b/engines/glk/tads/tads2/error_handling.cpp
@@ -34,7 +34,7 @@ int errfmt(char *outbuf, int outbufl, char *fmt, int argc, erradef *argv)
int argi = 0;
int len;
char buf[20];
- char *p = nullptr;
+ const char *p = nullptr;
char fmtchar;
while (*fmt != '\0' && outbufl > 1)
@@ -148,7 +148,7 @@ void errjmp(jmp_buf buf, int e)
#ifdef ERR_NO_MACRO
/* base error signal function */
-void errsign(errcxdef *ctx, int e, char *facility)
+void errsign(errcxdef *ctx, int e, const char *facility)
{
strncpy(ctx->errcxptr->errfac, facility, ERRFACMAX);
ctx->errcxptr->errfac[ERRFACMAX] = '\0';
@@ -161,7 +161,7 @@ void errsign(errcxdef *ctx, int e, char *facility)
}
/* signal an error with no arguments */
-void errsigf(errcxdef *ctx, char *facility, int e)
+void errsigf(errcxdef *ctx, const char *facility, int e)
{
errargc(ctx, 0);
errsign(ctx, e, facility);
@@ -188,7 +188,7 @@ void errrse1(errcxdef *ctx, errdef *fr)
}
/* log an error: base function */
-void errlogn(errcxdef *ctx, int err, char *facility)
+void errlogn(errcxdef *ctx, int err, const char *facility)
{
ctx->errcxofs = 0;
(*ctx->errcxlog)(ctx->errcxlgc, facility, err, ctx->errcxptr->erraac,
@@ -196,7 +196,7 @@ void errlogn(errcxdef *ctx, int err, char *facility)
}
/* log an error with no arguments */
-void errlogf(errcxdef *ctx, char *facility, int err)
+void errlogf(errcxdef *ctx, const char *facility, int err)
{
errargc(ctx, 0);
errlogn(ctx, err, facility);
diff --git a/engines/glk/tads/tads2/error_handling.h b/engines/glk/tads/tads2/error_handling.h
index 7c56b2f..8d37929 100644
--- a/engines/glk/tads/tads2/error_handling.h
+++ b/engines/glk/tads/tads2/error_handling.h
@@ -74,7 +74,7 @@ namespace TADS2 {
union erradef {
int erraint; /* integer argument */
- char *errastr; /* text string argument */
+ const char *errastr; /* text string argument */
};
struct errdef {
@@ -99,7 +99,7 @@ typedef struct errmfdef errmfdef;
struct errcxdef {
errdef *errcxptr; /* current error frame */
- void (*errcxlog)(void *, char *fac, int err, int argc, erradef *);
+ void (*errcxlog)(void *, const char *fac, int err, int argc, erradef *);
/* error logging callback function */
void *errcxlgc; /* context for error logging callback */
int errcxofs; /* offset in argument buffer */
@@ -187,7 +187,7 @@ char *errstr(errcxdef *ctx, const char *str, int len);
/* signal an error with argument count already set */
#ifdef ERR_NO_MACRO
-void errsign(errcxdef *ctx, int e, char *facility);
+void errsign(errcxdef *ctx, int e, const char *facility);
#else /* ERR_NO_MACRO */
# ifdef DEBUG
void errjmp(jmp_buf buf, int e);
@@ -206,7 +206,7 @@ void errjmp(jmp_buf buf, int e);
/* signal an error with no arguments */
#ifdef ERR_NO_MACRO
-void errsigf(errcxdef *ctx, char *facility, int err);
+void errsigf(errcxdef *ctx, const char *facility, int err);
#else /* ERR_NO_MACRO */
#define errsigf(ctx, fac, e) (errargc(ctx,0),errsign(ctx,e,fac))
#endif /* ERR_NO_MACRO */
@@ -270,7 +270,7 @@ void errrse1(errcxdef *ctx, errdef *fr);
/* log an error (no signalling, just reporting) */
#ifdef ERR_NO_MACRO
-void errlogn(errcxdef *ctx, int err, char *facility);
+void errlogn(errcxdef *ctx, int err, const char *facility);
#else /* ERR_NO_MACRO */
#define errlogn(ctx,err,fac) \
@@ -282,7 +282,7 @@ void errlogn(errcxdef *ctx, int err, char *facility);
/* log an error with no arguments */
#ifdef ERR_NO_MACRO
-void errlogf(errcxdef *ctx, char *facility, int err);
+void errlogf(errcxdef *ctx, const char *facility, int err);
#else /* ERR_NO_MACRO */
/* void errlogf(errcxdef *ctx, char *facility, int err); */
diff --git a/engines/glk/tads/tads2/line_source_file.cpp b/engines/glk/tads/tads2/line_source_file.cpp
index b5da3d1..430b1d9 100644
--- a/engines/glk/tads/tads2/line_source_file.cpp
+++ b/engines/glk/tads/tads2/line_source_file.cpp
@@ -403,7 +403,8 @@ void linfglop2(lindef *lin, uchar *buf)
/* save line source information to binary (.gam) file; TRUE ==> error */
int linfwrt(lindef *lin, osfildef *fp)
{
-# define linf ((linfdef *)lin)
+#define linf ((linfdef *)lin)
+#define UCHAR_MAX 255
uchar buf[UCHAR_MAX + 6];
size_t len;
uint pgcnt;
diff --git a/engines/glk/tads/tads2/ltk.cpp b/engines/glk/tads/tads2/ltk.cpp
index c385ee7..6961f21 100644
--- a/engines/glk/tads/tads2/ltk.cpp
+++ b/engines/glk/tads/tads2/ltk.cpp
@@ -130,7 +130,7 @@ void ltk_free(void *mem) {
* ltk_errlog - ERRor LOGging function. Logs an error from the LER
* system.
*/
-void ltk_errlog(void *ctx, char *fac, int errCode, int argc, erradef *argv) {
+void ltk_errlog(void *ctx, const char *fac, int errCode, int argc, erradef *argv) {
char buf[128]; /* formatted error buffer */
char msg[128]; /* message buffer */
@@ -152,7 +152,7 @@ void ltk_errlog(void *ctx, char *fac, int errCode, int argc, erradef *argv) {
/*
* ltk_dlg - DiaLog. Puts the given message in a dialog box.
*/
-void ltk_dlg(char *title, char *msg, ...) {
+void ltk_dlg(const char *title, const char *msg, ...) {
va_list argp; /* printf args */
char inbuf[80]; /* input buffer */
char outbuf[160]; /* allow inbuf to double in size */
diff --git a/engines/glk/tads/tads2/ltk.h b/engines/glk/tads/tads2/ltk.h
index 2fbe7a8..fa66b8a 100644
--- a/engines/glk/tads/tads2/ltk.h
+++ b/engines/glk/tads/tads2/ltk.h
@@ -56,13 +56,13 @@ extern void ltkfre();
* printf-style arguments must be passed in also, if the message
* requires them.
*/
-extern void ltk_dlg(char *title, char *msg, ...);
+extern void ltk_dlg(const char *title, const char *msg, ...);
/*
* ltk_errlog - Error logging function for LER routines.
*/
-extern void ltk_errlog(void *ctx, char *fac, int errCode, int agrc, erradef *argv);
+extern void ltk_errlog(void *ctx, const char *fac, int errCode, int agrc, erradef *argv);
/*
diff --git a/engines/glk/tads/tads2/memory_cache_heap.cpp b/engines/glk/tads/tads2/memory_cache_heap.cpp
index fae3c5e..5704f46 100644
--- a/engines/glk/tads/tads2/memory_cache_heap.cpp
+++ b/engines/glk/tads/tads2/memory_cache_heap.cpp
@@ -30,7 +30,7 @@ namespace TADS2 {
/* global to keep track of all allocations */
IF_DEBUG(ulong mchtotmem;)
-uchar *mchalo(errcxdef *ctx, size_t siz, char *comment) {
+uchar *mchalo(errcxdef *ctx, size_t siz, const char *comment) {
uchar *ret;
VARUSED(comment);
diff --git a/engines/glk/tads/tads2/memory_cache_heap.h b/engines/glk/tads/tads2/memory_cache_heap.h
index 269cdec..6405f49 100644
--- a/engines/glk/tads/tads2/memory_cache_heap.h
+++ b/engines/glk/tads/tads2/memory_cache_heap.h
@@ -43,7 +43,7 @@ namespace TADS2 {
* 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);
+uchar *mchalo(errcxdef *ctx, size_t siz, const char *comment);
/* allocate a structure */
#define MCHNEW(errctx, typ, comment) \
diff --git a/engines/glk/tads/tads2/output.cpp b/engines/glk/tads/tads2/output.cpp
index 8b9fd74..6ff2a99 100644
--- a/engines/glk/tads/tads2/output.cpp
+++ b/engines/glk/tads/tads2/output.cpp
@@ -192,7 +192,7 @@ static uchar *fmstop; /* format string area top */
static objnum cmdActor; /* current actor */
/* forward declarations of static functions */
-static void outstring_stream(out_stream_info *stream, char *s);
+static void outstring_stream(out_stream_info *stream, const char *s);
static void outchar_noxlat_stream(out_stream_info *stream, char c);
static char out_parse_entity(char *outbuf, size_t outbuf_size,
char **sp, size_t *slenp);
@@ -1077,8 +1077,8 @@ static void outflushn_stream(out_stream_info *stream, int nl)
}
else
{
- char *suffix = nullptr; /* extra text to add after the flushed text */
- int countnl = 0; /* true if line counts for [more] paging */
+ const char *suffix = nullptr; /* extra text to add after the flushed text */
+ int countnl = 0; /* true if line counts for [more] paging */
/* null-terminate the buffer at the current position */
stream->linebuf[++i] = '\0';
@@ -1621,7 +1621,7 @@ static void outchar_stream(out_stream_info *stream, char c)
/*
* write out a string, translating to the local system character set
*/
-static void outstring_stream(out_stream_info *stream, char *s)
+static void outstring_stream(out_stream_info *stream, const char *s)
{
/* write out each character in the string */
for ( ; *s ; ++s)
@@ -1813,7 +1813,7 @@ static char read_tag(char *dst, size_t dstlen, int *is_end_tag,
* display a string of a given length to a given stream
*/
static int outformatlen_stream(out_stream_info *stream,
- char *s, size_t slen)
+ const char *s, size_t slen)
{
char c;
int done = 0;
diff --git a/engines/glk/tads/tads2/text_io.h b/engines/glk/tads/tads2/text_io.h
index 6519465..9a0e198 100644
--- a/engines/glk/tads/tads2/text_io.h
+++ b/engines/glk/tads/tads2/text_io.h
@@ -92,7 +92,7 @@ uint tiocapturesize(tiocxdef *ctx);
void outfmt(tiocxdef *ctx, uchar *txt);
/* format a null-terminated (C-style) string to the display */
-int outformat(char *s);
+int outformat(const char *s);
/* format a counted-length string, which may not be null-terminated */
int outformatlen(char *s, uint len);
diff --git a/engines/glk/tads/tads2/vocabulary.h b/engines/glk/tads/tads2/vocabulary.h
index f4c058e..d28dcfe 100644
--- a/engines/glk/tads/tads2/vocabulary.h
+++ b/engines/glk/tads/tads2/vocabulary.h
@@ -609,7 +609,7 @@ void vocrevert(voccxdef *ctx);
void vocdmnclr(voccxdef *ctx);
/* display a parser error message */
-void vocerr(voccxdef *ctx, int err, char *f, ...);
+void vocerr(voccxdef *ctx, int err, const char *f, ...);
/*
* display a parser informational error message - this will display the
@@ -617,7 +617,7 @@ void vocerr(voccxdef *ctx, int err, char *f, ...);
* 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, ...);
+void vocerr_info(voccxdef *ctx, int err, const char *f, ...);
/* client undo callback - undoes a daemon/fuse/notifier */
void vocdundo(void *ctx, uchar *data);
diff --git a/engines/glk/tads/tads2/vocabulary_parser.cpp b/engines/glk/tads/tads2/vocabulary_parser.cpp
index c0ff75d..7190058 100644
--- a/engines/glk/tads/tads2/vocabulary_parser.cpp
+++ b/engines/glk/tads/tads2/vocabulary_parser.cpp
@@ -686,7 +686,7 @@ struct vocerr_va_info
* vocerr_va().
*/
static void vocerr_va_prep(voccxdef *ctx, struct vocerr_va_info *info,
- int err, char *f, va_list argptr)
+ int err, const char *f, va_list argptr)
{
/*
* presume that we'll use the given format string, instead of one
@@ -874,7 +874,7 @@ static void vocerr_va_prep(voccxdef *ctx, struct vocerr_va_info *info,
* property re-initialized for the call to this routine.
*/
static void vocerr_va(voccxdef *ctx, struct vocerr_va_info *info,
- int err, char *f, va_list argptr)
+ int err, const char *f, va_list argptr)
{
char *buf;
@@ -896,7 +896,7 @@ static void vocerr_va(voccxdef *ctx, struct vocerr_va_info *info,
/*
* display a parser informational message
*/
-void vocerr_info(voccxdef *ctx, int err, char *f, ...)
+void vocerr_info(voccxdef *ctx, int err, const char *f, ...)
{
va_list argptr;
struct vocerr_va_info info;
@@ -915,7 +915,7 @@ void vocerr_info(voccxdef *ctx, int err, char *f, ...)
/*
* display a parser error
*/
-void vocerr(voccxdef *ctx, int err, char *f, ...)
+void vocerr(voccxdef *ctx, int err, const char *f, ...)
{
va_list argptr;
struct vocerr_va_info info;
Commit: 1a974b9749f2c12edbdf98cbe9f4b6ec9fcba40d
https://github.com/scummvm/scummvm/commit/1a974b9749f2c12edbdf98cbe9f4b6ec9fcba40d
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:07-07:00
Commit Message:
GLK: TADS2: Further compilation fixes
Changed paths:
engines/glk/tads/tads2/error.cpp
engines/glk/tads/tads2/output.cpp
engines/glk/tads/tads2/run.cpp
engines/glk/tads/tads2/run.h
engines/glk/tads/tads2/runtime_driver.cpp
engines/glk/tads/tads2/text_io.h
engines/glk/tads/tads2/vocabulary_parser.cpp
diff --git a/engines/glk/tads/tads2/error.cpp b/engines/glk/tads/tads2/error.cpp
index 62c5e9a..ad83d06 100644
--- a/engines/glk/tads/tads2/error.cpp
+++ b/engines/glk/tads/tads2/error.cpp
@@ -184,16 +184,6 @@ void errsign(errcxdef *ctx, int e) {
#endif
}
-/* enter a string argument */
-char *errstr(errcxdef *ctx, const char *str, int len) {
- char *ret = &ctx->errcxbuf[ctx->errcxofs];
-
- memcpy(ret, str, (size_t)len);
- ret[len] = '\0';
- ctx->errcxofs += len + 1;
- return(ret);
-}
-
/* log an error: base function */
void errlogn(errcxdef *ctx, int err) {
error("errlogn");
diff --git a/engines/glk/tads/tads2/output.cpp b/engines/glk/tads/tads2/output.cpp
index 6ff2a99..6f5cd84 100644
--- a/engines/glk/tads/tads2/output.cpp
+++ b/engines/glk/tads/tads2/output.cpp
@@ -194,8 +194,7 @@ static objnum cmdActor; /* current actor */
/* forward declarations of static functions */
static void outstring_stream(out_stream_info *stream, const char *s);
static void outchar_noxlat_stream(out_stream_info *stream, char c);
-static char out_parse_entity(char *outbuf, size_t outbuf_size,
- char **sp, size_t *slenp);
+static char out_parse_entity(char *outbuf, size_t outbuf_size, const char **sp, size_t *slenp);
/* ------------------------------------------------------------------------ */
@@ -1741,7 +1740,7 @@ static void out_pop_stream()
* Get the next character, writing the previous character to the given
* output stream if it's not null.
*/
-static char nextout_copy(char **s, size_t *slen,
+static char nextout_copy(const char **s, size_t *slen,
char prv, out_stream_info *stream)
{
/* if there's a stream, write the previous character to the stream */
@@ -1759,7 +1758,7 @@ static char nextout_copy(char **s, size_t *slen,
* the next character after the tag name.
*/
static char read_tag(char *dst, size_t dstlen, int *is_end_tag,
- char **s, size_t *slen, out_stream_info *stream)
+ const char **s, size_t *slen, out_stream_info *stream)
{
char c;
@@ -2722,12 +2721,10 @@ static int outformatlen_stream(out_stream_info *stream,
/*
* Parse an HTML entity markup
*/
-static char out_parse_entity(char *outbuf, size_t outbuf_size,
- char **sp, size_t *slenp)
-{
+static char out_parse_entity(char *outbuf, size_t outbuf_size, const char **sp, size_t *slenp) {
char ampbuf[10];
char *dst;
- char *orig_s;
+ const char *orig_s;
size_t orig_slen;
const amp_tbl_t *ampptr;
size_t lo, hi, cur;
@@ -2988,8 +2985,7 @@ void tio_set_html_expansion(unsigned int html_char_val,
/*
* Write out a c-style (null-terminated) string.
*/
-int outformat(char *s)
-{
+int outformat(const char *s) {
return outformatlen(s, strlen(s));
}
@@ -2999,11 +2995,10 @@ int outformat(char *s)
* This routine sends out a string, one character at a time (via outchar).
* Escape codes ('\n', and so forth) are handled here.
*/
-int outformatlen(char *s, uint slen)
-{
+int outformatlen(const char *s, uint slen) {
char c;
uint orig_slen;
- char *orig_s;
+ const char *orig_s;
int ret;
int called_filter;
diff --git a/engines/glk/tads/tads2/run.cpp b/engines/glk/tads/tads2/run.cpp
index 5a9ede0..4179ff2 100644
--- a/engines/glk/tads/tads2/run.cpp
+++ b/engines/glk/tads/tads2/run.cpp
@@ -281,7 +281,7 @@ void runrepush(runcxdef *ctx, runsdef *val)
}
/* push a counted-length string onto the stack */
-void runpstr(runcxdef *ctx, char *str, int len, int sav)
+void runpstr(runcxdef *ctx, const char *str, int len, int sav)
{
runsdef val;
diff --git a/engines/glk/tads/tads2/run.h b/engines/glk/tads/tads2/run.h
index 6c303b8..297cb9d 100644
--- a/engines/glk/tads/tads2/run.h
+++ b/engines/glk/tads/tads2/run.h
@@ -146,7 +146,7 @@ void runpnil(runcxdef *ctx);
void runpbuf(runcxdef *ctx, int typ, void *val);
/* push a counted-length string onto the stack */
-void runpstr(runcxdef *ctx, char *str, int len, int sav);
+void runpstr(runcxdef *ctx, const char *str, int len, int sav);
/*
* Push a C-style string onto the stack, converting escape codes. If
diff --git a/engines/glk/tads/tads2/runtime_driver.cpp b/engines/glk/tads/tads2/runtime_driver.cpp
index 20ba6c1..aec8670 100644
--- a/engines/glk/tads/tads2/runtime_driver.cpp
+++ b/engines/glk/tads/tads2/runtime_driver.cpp
@@ -789,9 +789,7 @@ static void trdmain1(errcxdef *ec, int argc, char *argv[],
#endif
/* log an error */
-static void trdlogerr(void *ctx0, char *fac, int err,
- int argc, erradef *argv)
-{
+static void trdlogerr(void *ctx0, const char *fac, int err, int argc, erradef *argv) {
errcxdef *ctx = (errcxdef *)ctx0;
char buf[256];
char msg[256];
diff --git a/engines/glk/tads/tads2/text_io.h b/engines/glk/tads/tads2/text_io.h
index 9a0e198..d4dd16f 100644
--- a/engines/glk/tads/tads2/text_io.h
+++ b/engines/glk/tads/tads2/text_io.h
@@ -95,7 +95,7 @@ void outfmt(tiocxdef *ctx, uchar *txt);
int outformat(const char *s);
/* format a counted-length string, which may not be null-terminated */
-int outformatlen(char *s, uint len);
+int outformatlen(const char *s, uint len);
/* flush output, with specified newline mode */
void outflushn(int nl);
diff --git a/engines/glk/tads/tads2/vocabulary_parser.cpp b/engines/glk/tads/tads2/vocabulary_parser.cpp
index 7190058..0b7bed6 100644
--- a/engines/glk/tads/tads2/vocabulary_parser.cpp
+++ b/engines/glk/tads/tads2/vocabulary_parser.cpp
@@ -667,7 +667,7 @@ struct vocerr_va_info
char user_msg[400];
/* the sprintf-style format string to display */
- char *fmt;
+ const char *fmt;
/*
* Pointer to the output buffer to use to format the string 'fmt' with
@@ -734,7 +734,7 @@ static void vocerr_va_prep(voccxdef *ctx, struct vocerr_va_info *info,
};
struct argbuf_t args[5];
struct argbuf_t *argp;
- char *p;
+ const char *p;
/*
* Retrieve the arguments by examining the format string. We
Commit: 5381930e8b09f61b8c7969db4a657067586be897
https://github.com/scummvm/scummvm/commit/5381930e8b09f61b8c7969db4a657067586be897
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:07-07:00
Commit Message:
GLK: TADS2: Fix uninitialized variable warnings
Changed paths:
engines/glk/tads/tads2/built_in.cpp
engines/glk/tads/tads2/execute_command.cpp
engines/glk/tads/tads2/file_io.cpp
engines/glk/tads/tads2/line_source_file.cpp
engines/glk/tads/tads2/object.cpp
diff --git a/engines/glk/tads/tads2/built_in.cpp b/engines/glk/tads/tads2/built_in.cpp
index c67399d..5831903 100644
--- a/engines/glk/tads/tads2/built_in.cpp
+++ b/engines/glk/tads/tads2/built_in.cpp
@@ -1189,8 +1189,8 @@ void bifsub(bifcxdef *ctx, int argc)
/* cvtstr - convert value to a string */
void bifcvs(bifcxdef *ctx, int argc)
{
- char *p;
- int len;
+ char *p = nullptr;
+ int len = 0;
char buf[30];
bifcntargs(ctx, 1, argc);
diff --git a/engines/glk/tads/tads2/execute_command.cpp b/engines/glk/tads/tads2/execute_command.cpp
index d38d92b..d8ad6b2 100644
--- a/engines/glk/tads/tads2/execute_command.cpp
+++ b/engines/glk/tads/tads2/execute_command.cpp
@@ -2645,7 +2645,7 @@ int execmd(voccxdef *ctx, objnum actor, objnum prep,
{
uchar *l = runpoplst(rcx);
uint lstsiz;
- objnum defobj;
+ objnum defobj = 0;
int objcnt;
objnum newprep;
runsdef val;
@@ -2841,7 +2841,7 @@ int execmd(voccxdef *ctx, objnum actor, objnum prep,
{
uchar *l = runpoplst(rcx);
uint lstsiz;
- objnum defobj;
+ objnum defobj = 0;
int objcnt;
runsdef val;
objnum o;
@@ -3218,7 +3218,7 @@ int execmd(voccxdef *ctx, objnum actor, objnum prep,
uchar *l = runpoplst(rcx);
uint lstsiz;
int objcnt;
- objnum defobj;
+ objnum defobj = 0;
objnum o;
runsdef val;
@@ -3469,9 +3469,9 @@ int execmd(voccxdef *ctx, objnum actor, objnum prep,
}
else
{
- int is_him;
- int is_her;
- int is_them;
+ int is_him = 0;
+ int is_her = 0;
+ int is_them = 0;
/* run through the objects and check him/her */
for (i = 0 ; i < vcnt ; ++i)
diff --git a/engines/glk/tads/tads2/file_io.cpp b/engines/glk/tads/tads2/file_io.cpp
index 10b8bca..5495c1b 100644
--- a/engines/glk/tads/tads2/file_io.cpp
+++ b/engines/glk/tads/tads2/file_io.cpp
@@ -50,7 +50,7 @@ void OS_LOADDS fioldobj(void *ctx0, mclhd handle, uchar *ptr, ushort siz)
osfildef *fp = ctx->fiolcxfp;
char buf[7];
errcxdef *ec = ctx->fiolcxerr;
- uint rdsiz;
+ uint rdsiz = 0;
/* figure out what type of object is to be loaded */
osfseek(fp, seekpos + ctx->fiolcxst, OSFSK_SET);
@@ -714,8 +714,8 @@ static void fiord1(mcmcxdef *mctx, voccxdef *vctx, tokcxdef *tctx,
uchar *p;
uchar *bufp;
ulong bsiz;
- int len1;
- int len2;
+ int len1 = 0;
+ int len2 = 0;
/* do it in a single file read, if we can, for speed */
curpos = osfpos(fp) - startofs;
diff --git a/engines/glk/tads/tads2/line_source_file.cpp b/engines/glk/tads/tads2/line_source_file.cpp
index 430b1d9..affc3cd 100644
--- a/engines/glk/tads/tads2/line_source_file.cpp
+++ b/engines/glk/tads/tads2/line_source_file.cpp
@@ -87,7 +87,7 @@ linfdef *linfini(mcmcxdef *mctx, errcxdef *ec, char *filename,
int i;
objnum *objp;
linfdef *linf;
- osfildef *fp;
+ osfildef *fp = nullptr;
char fbuf[OSFNMAX + 1];
tokpdef fakepath;
int len;
@@ -404,8 +404,8 @@ void linfglop2(lindef *lin, uchar *buf)
int linfwrt(lindef *lin, osfildef *fp)
{
#define linf ((linfdef *)lin)
-#define UCHAR_MAX 255
- uchar buf[UCHAR_MAX + 6];
+#define BYTE_MAX 255
+ uchar buf[BYTE_MAX + 6];
size_t len;
uint pgcnt;
uchar *objp;
@@ -413,7 +413,7 @@ int linfwrt(lindef *lin, osfildef *fp)
buf[0] = lin->linid;
len = strlen(linf->linfnam);
- if (len > UCHAR_MAX)
+ if (len > BYTE_MAX)
return FALSE;
buf[1] = (uchar)len;
oswp4(buf + 2, linf->linfcrec);
diff --git a/engines/glk/tads/tads2/object.cpp b/engines/glk/tads/tads2/object.cpp
index 4360f5f..4e6f309 100644
--- a/engines/glk/tads/tads2/object.cpp
+++ b/engines/glk/tads/tads2/object.cpp
@@ -42,11 +42,11 @@ uint objgetp(mcmcxdef *mctx, objnum objn, prpnum prop, dattyp *typptr)
uint retval; /* property offset, if we find it */
uint ignprop; /* ignored property - use if real property isn't found */
uchar pbuf[2]; /* property number in portable format */
- uchar *indp;
+ uchar *indp = nullptr;
uchar *indbase;
int last;
int first;
- int cur;
+ int cur = 0;
oswp2(pbuf, prop); /* get property number in portable foramt */
objptr = (objdef *)mcmlck(mctx, objn); /* get a lock on the object */
@@ -192,7 +192,7 @@ static uint objgetap0(mcmcxdef *ctx, noreg objnum obj, prpnum prop,
{
uchar *sc;
ushort sccnt;
- ushort psav;
+ ushort psav = 0;
dattyp typsav = DAT_NIL;
objnum osavn = MCMONINV;
uchar *o1;
@@ -240,7 +240,7 @@ static uint objgetap0(mcmcxdef *ctx, noreg objnum obj, prpnum prop,
/* if we found the property, remember it */
if (poff != 0)
{
- int isdesc;
+ int isdesc = 0;
/* if we have a previous object, determine lineage */
if (found)
Commit: 8f9fcec16c41ba4bfc51e7b92bdc701f5a2a2e6b
https://github.com/scummvm/scummvm/commit/8f9fcec16c41ba4bfc51e7b92bdc701f5a2a2e6b
Author: dreammaster (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:07-07:00
Commit Message:
GLK: TADS2: Further compilation fixes
Changed paths:
engines/glk/tads/tads2/built_in.cpp
engines/glk/tads/tads2/data.cpp
engines/glk/tads/tads2/data.h
engines/glk/tads/tads2/debug.cpp
engines/glk/tads/tads2/debug.h
engines/glk/tads/tads2/execute_command.cpp
engines/glk/tads/tads2/get_string.cpp
engines/glk/tads/tads2/line_source_file.cpp
engines/glk/tads/tads2/object.cpp
engines/glk/tads/tads2/object.h
engines/glk/tads/tads2/output.cpp
engines/glk/tads/tads2/run.cpp
engines/glk/tads/tads2/run.h
engines/glk/tads/tads2/runtime_driver.cpp
engines/glk/tads/tads2/text_io.h
engines/glk/tads/tads2/vocabulary.cpp
engines/glk/tads/tads2/vocabulary.h
engines/glk/tads/tads2/vocabulary_parser.cpp
diff --git a/engines/glk/tads/tads2/built_in.cpp b/engines/glk/tads/tads2/built_in.cpp
index 5831903..4cc6e15 100644
--- a/engines/glk/tads/tads2/built_in.cpp
+++ b/engines/glk/tads/tads2/built_in.cpp
@@ -1189,7 +1189,7 @@ void bifsub(bifcxdef *ctx, int argc)
/* cvtstr - convert value to a string */
void bifcvs(bifcxdef *ctx, int argc)
{
- char *p = nullptr;
+ const char *p = nullptr;
int len = 0;
char buf[30];
@@ -1973,7 +1973,7 @@ void bifsct(bifcxdef *bifctx, int argc)
* Portable keystroke mappings. We map the extended key codes to these
* strings, so that the TADS code can access arrow keys and the like.
*/
-static char *ext_key_names[] =
+static const char *ext_key_names[] =
{
"[up]", /* CMD_UP - 1 */
"[down]", /* CMD_DOWN - 2 */
@@ -2142,8 +2142,8 @@ void bifwrd(bifcxdef *ctx, int argc)
int ob;
vocoldef *v;
uchar buf[128];
- uchar *dst;
- uchar *src;
+ uchar *dst;
+ const uchar *src;
uint len;
runsdef val;
@@ -2169,13 +2169,13 @@ void bifwrd(bifcxdef *ctx, int argc)
/* now build a list of strings from the words, if there are any */
if (v != 0 && voclistlen(v) != 0 && v->vocolfst != 0 && v->vocollst != 0)
{
- for (dst = buf + 2, src = (uchar *)v->vocolfst ;
- src <= (uchar *)v->vocollst ; src += len + 1)
+ for (dst = buf + 2, src = (const uchar *)v->vocolfst ;
+ src <= (const uchar *)v->vocollst ; src += len + 1)
{
*dst++ = DAT_SSTRING;
- len = strlen((char *)src);
+ len = strlen((const char *)src);
oswp2(dst, len + 2);
- strcpy((char *)dst + 2, (char *)src);
+ strcpy((char *)dst + 2, (const char *)src);
dst += len + 2;
}
}
diff --git a/engines/glk/tads/tads2/data.cpp b/engines/glk/tads/tads2/data.cpp
index fe002a3..dfd641c 100644
--- a/engines/glk/tads/tads2/data.cpp
+++ b/engines/glk/tads/tads2/data.cpp
@@ -32,7 +32,7 @@ namespace TADS {
namespace TADS2 {
/* return size of a data value */
-uint datsiz(dattyp typ, void *val)
+uint datsiz(dattyp typ, const void *val)
{
switch(typ)
{
@@ -45,7 +45,7 @@ uint datsiz(dattyp typ, void *val)
case DAT_SSTRING:
case DAT_DSTRING:
case DAT_LIST:
- return(osrp2((char *)val));
+ return(osrp2((const char *)val));
case DAT_NIL:
case DAT_TRUE:
@@ -59,10 +59,10 @@ uint datsiz(dattyp typ, void *val)
case DAT_TPL:
/* template is counted array of 10-byte entries, plus length byte */
- return(1 + ((*(uchar *)val) * VOCTPLSIZ));
+ return(1 + ((*(const uchar *)val) * VOCTPLSIZ));
case DAT_TPL2:
- return(1 + ((*(uchar *)val) * VOCTPL2SIZ));
+ return(1 + ((*(const uchar *)val) * VOCTPL2SIZ));
default:
return(0);
diff --git a/engines/glk/tads/tads2/data.h b/engines/glk/tads/tads2/data.h
index e573730..6a914cd 100644
--- a/engines/glk/tads/tads2/data.h
+++ b/engines/glk/tads/tads2/data.h
@@ -50,7 +50,7 @@ enum DataType {
typedef int dattyp;
/* determine the size of a piece of data */
-uint datsiz(dattyp typ, void *valptr);
+uint datsiz(dattyp typ, const void *valptr);
} // End of namespace TADS2
} // End of namespace TADS
diff --git a/engines/glk/tads/tads2/debug.cpp b/engines/glk/tads/tads2/debug.cpp
index 94e0b37..726cbe2 100644
--- a/engines/glk/tads/tads2/debug.cpp
+++ b/engines/glk/tads/tads2/debug.cpp
@@ -188,12 +188,12 @@ int dbgnam(dbgcxdef *ctx, char *outbuf, int typ, int val)
}
/* send a buffer value (as from a list) to ui callback for display */
-static void dbgpbval(dbgcxdef *ctx, dattyp typ, uchar *val,
+static void dbgpbval(dbgcxdef *ctx, dattyp typ, const uchar *val,
void (*dispfn)(void *, const char *, int),
void *dispctx)
{
char buf[TOKNAMMAX + 1];
- char *p = buf;
+ const char *p = buf;
uint len;
switch(typ)
@@ -209,7 +209,7 @@ static void dbgpbval(dbgcxdef *ctx, dattyp typ, uchar *val,
case DAT_SSTRING:
len = osrp2(val) - 2;
- p = (char *)val + 2;
+ p = (const char *)val + 2;
break;
case DAT_NIL:
@@ -220,10 +220,10 @@ static void dbgpbval(dbgcxdef *ctx, dattyp typ, uchar *val,
case DAT_LIST:
(*dispfn)(dispctx, "[", 1);
len = osrp2(val) - 2;
- p = (char *)val + 2;
+ p = (const char *)val + 2;
while (len)
{
- dbgpbval(ctx, (dattyp)*p, (uchar *)(p + 1), dispfn, dispctx);
+ dbgpbval(ctx, (dattyp)*p, (const uchar *)(p + 1), dispfn, dispctx);
lstadv((uchar **)&p, &len);
if (len) (*dispfn)(dispctx, " ", 1);
}
@@ -265,8 +265,8 @@ void dbgpval(dbgcxdef *ctx, runsdef *val,
{
uchar buf[TOKNAMMAX + 1];
uint len;
- uchar *p = buf;
- char *typ = 0;
+ const uchar *p = buf;
+ const char *typ = 0;
switch(val->runstyp)
{
@@ -288,27 +288,28 @@ void dbgpval(dbgcxdef *ctx, runsdef *val,
break;
case DAT_NIL:
- p = (uchar *)"nil";
+ p = (const uchar *)"nil";
len = 3;
break;
- case DAT_LIST:
+ case DAT_LIST: {
if (showtype) (*dispfn)(dispctx, "list: ", 6);
(*dispfn)(dispctx, "[", 1);
len = osrp2(val->runsv.runsvstr) - 2;
- p = val->runsv.runsvstr + 2;
+ uchar *up = val->runsv.runsvstr + 2;
while (len)
{
- dbgpbval(ctx, (dattyp)*p, (uchar *)(p + 1), dispfn, dispctx);
- lstadv(&p, &len);
+ dbgpbval(ctx, (dattyp)*up, (const uchar *)(up + 1), dispfn, dispctx);
+ lstadv(&up, &len);
if (len) (*dispfn)(dispctx, " ", 1);
}
(*dispfn)(dispctx, "]", 1);
len = 0;
+ p = up;
break;
-
+ }
case DAT_TRUE:
- p = (uchar *)"true";
+ p = (const uchar *)"true";
len = 4;
break;
@@ -323,7 +324,7 @@ void dbgpval(dbgcxdef *ctx, runsdef *val,
break;
default:
- p = (uchar *)"[unknown type]";
+ p = (const uchar *)"[unknown type]";
len = 14;
break;
}
@@ -356,7 +357,7 @@ void dbgpval(dbgcxdef *ctx, runsdef *val,
/* display the text */
if (len != 0)
- (*dispfn)(dispctx, (char *)p, len);
+ (*dispfn)(dispctx, (const char *)p, len);
/* add a closing quote if it's a string and we showed an open quote */
if (val->runstyp == DAT_SSTRING && !(typ && showtype))
@@ -446,7 +447,7 @@ static void dbgdsdisp(void *ctx, const char *buf, int bufl)
if (buf[0] == '\n')
tioflush((tiocxdef *)ctx);
else
- tioputslen((tiocxdef *)ctx, (char *)buf, bufl);
+ tioputslen((tiocxdef *)ctx, (const char *)buf, bufl);
}
/* dump the stack */
diff --git a/engines/glk/tads/tads2/debug.h b/engines/glk/tads/tads2/debug.h
index 0e7369e..3c53a89 100644
--- a/engines/glk/tads/tads2/debug.h
+++ b/engines/glk/tads/tads2/debug.h
@@ -547,7 +547,7 @@ void trcsho(void);
#ifdef DBG_OFF
#define dbgenter(ctx, bp, self, target, prop, binum, argc)
-#define dbgleave(ctx, exittype)
+#define dbgleave(ctx, exittype) ((void)0)
#define dbgdump(ctx) ((void)0)
#define dbgrst(ctx) ((void)0)
#define dbgframe(ctx, frofs, linofs)
diff --git a/engines/glk/tads/tads2/execute_command.cpp b/engines/glk/tads/tads2/execute_command.cpp
index d8ad6b2..63c5f02 100644
--- a/engines/glk/tads/tads2/execute_command.cpp
+++ b/engines/glk/tads/tads2/execute_command.cpp
@@ -2264,7 +2264,7 @@ static void voc_askobj_indirect(voccxdef *ctx, vocoldef *dolist,
cnt = voclistlen(dolist);
for (len = 0, i = 0 ; i < cnt ; )
{
- char *p;
+ const char *p;
size_t curlen;
int j;
@@ -2331,7 +2331,7 @@ static void voc_askobj_indirect(voccxdef *ctx, vocoldef *dolist,
*/
for (i = 0 ; i < cnt ; )
{
- char *p;
+ const char *p;
uchar *subp;
uchar *subsubp;
size_t curlen;
@@ -3427,7 +3427,7 @@ int execmd(voccxdef *ctx, objnum actor, objnum prep,
int i;
int vcnt;
int distinct;
- char *lastfst;
+ const char *lastfst;
/*
* If possible, tailor the pronoun to the situation
diff --git a/engines/glk/tads/tads2/get_string.cpp b/engines/glk/tads/tads2/get_string.cpp
index 69982fc..624b8d9 100644
--- a/engines/glk/tads/tads2/get_string.cpp
+++ b/engines/glk/tads/tads2/get_string.cpp
@@ -56,7 +56,7 @@ extern int scrquiet;
* output flushing. Prompting is to be done by the caller. This
* routine should be called instead of os_gets.
*/
-int getstring(char *prompt, char *buf, int bufl)
+int getstring(const char *prompt, char *buf, int bufl)
{
char *result;
int savemoremode;
diff --git a/engines/glk/tads/tads2/line_source_file.cpp b/engines/glk/tads/tads2/line_source_file.cpp
index affc3cd..c2942a8 100644
--- a/engines/glk/tads/tads2/line_source_file.cpp
+++ b/engines/glk/tads/tads2/line_source_file.cpp
@@ -31,6 +31,7 @@ namespace Glk {
namespace TADS {
namespace TADS2 {
+#define BYTE_MAX 0xff
/* initialize a pre-allocated linfdef, skipping debugger page setup */
void linfini2(mcmcxdef *mctx, linfdef *linf,
@@ -404,7 +405,6 @@ void linfglop2(lindef *lin, uchar *buf)
int linfwrt(lindef *lin, osfildef *fp)
{
#define linf ((linfdef *)lin)
-#define BYTE_MAX 255
uchar buf[BYTE_MAX + 6];
size_t len;
uint pgcnt;
@@ -440,7 +440,7 @@ int linfwrt(lindef *lin, osfildef *fp)
int linfload(osfildef *fp, dbgcxdef *dbgctx, errcxdef *ec, tokpdef *path)
{
linfdef *linf;
- uchar buf[UCHAR_MAX + 6];
+ uchar buf[BYTE_MAX + 6];
uint pgcnt;
uchar *objp;
mcmon *objn;
diff --git a/engines/glk/tads/tads2/object.cpp b/engines/glk/tads/tads2/object.cpp
index 4e6f309..7bd2f3a 100644
--- a/engines/glk/tads/tads2/object.cpp
+++ b/engines/glk/tads/tads2/object.cpp
@@ -419,7 +419,7 @@ void objdelp(mcmcxdef *mctx, objnum objn, prpnum prop, int mark_only)
* pointer is null, no undo information is kept.
*/
void objsetp(mcmcxdef *ctx, objnum objn, prpnum prop, dattyp typ,
- void *val, objucxdef *undoctx)
+ const void *val, objucxdef *undoctx)
{
objdef *objptr;
prpdef *p;
diff --git a/engines/glk/tads/tads2/object.h b/engines/glk/tads/tads2/object.h
index 5e2499c..0536f05 100644
--- a/engines/glk/tads/tads2/object.h
+++ b/engines/glk/tads/tads2/object.h
@@ -275,7 +275,7 @@ objdef *objexp(mcmcxdef *ctx, objnum obj, ushort *siz);
* undo information is retained.
*/
void objsetp(mcmcxdef *ctx, objnum obj, prpnum prop,
- dattyp typ, void *val, objucxdef *undoctx);
+ dattyp typ, const void *val, objucxdef *undoctx);
/*
* Delete a property. If mark_only is true, we'll only mark the
diff --git a/engines/glk/tads/tads2/output.cpp b/engines/glk/tads/tads2/output.cpp
index 6f5cd84..79284b7 100644
--- a/engines/glk/tads/tads2/output.cpp
+++ b/engines/glk/tads/tads2/output.cpp
@@ -3203,7 +3203,7 @@ int tiologcls(tiocxdef *ctx)
* caller should not include any newlines in the text being displayed
* here.
*/
-void out_logfile_print(char *txt, int nl)
+void out_logfile_print(const char *txt, int nl)
{
/* if there's no log file, there's nothing to do */
if (logfp == 0)
diff --git a/engines/glk/tads/tads2/run.cpp b/engines/glk/tads/tads2/run.cpp
index 4179ff2..64b52a4 100644
--- a/engines/glk/tads/tads2/run.cpp
+++ b/engines/glk/tads/tads2/run.cpp
@@ -298,9 +298,9 @@ void runpstr(runcxdef *ctx, const char *str, int len, int sav)
}
/* push a C-style string, converting escape codes */
-void runpushcstr(runcxdef *ctx, char *str, size_t len, int sav)
+void runpushcstr(runcxdef *ctx, const char *str, size_t len, int sav)
{
- char *p;
+ const char *p;
char *dst;
size_t need;
runsdef val;
diff --git a/engines/glk/tads/tads2/run.h b/engines/glk/tads/tads2/run.h
index 297cb9d..0a39fca 100644
--- a/engines/glk/tads/tads2/run.h
+++ b/engines/glk/tads/tads2/run.h
@@ -153,7 +153,7 @@ void runpstr(runcxdef *ctx, const char *str, int len, int sav);
* the character contains backslashes, newline, or tab characters, we'll
* convert these characters to their escaped equivalent.
*/
-void runpushcstr(runcxdef *ctx, char *str, size_t len, int sav);
+void runpushcstr(runcxdef *ctx, const char *str, size_t len, int sav);
/*
* Push a property onto the stack. codepp is a pointer to the caller's
diff --git a/engines/glk/tads/tads2/runtime_driver.cpp b/engines/glk/tads/tads2/runtime_driver.cpp
index aec8670..6b6a6d7 100644
--- a/engines/glk/tads/tads2/runtime_driver.cpp
+++ b/engines/glk/tads/tads2/runtime_driver.cpp
@@ -224,7 +224,7 @@ static void trdusage(errcxdef *ec)
errmsg(ec, buf, (uint)sizeof(buf), ERR_TRUSPARM);
/* format in the application name */
- argv[0].errastr = (char *)ec->errcxappctx->usage_app_name;
+ argv[0].errastr = ec->errcxappctx->usage_app_name;
errfmt(buf2, (int)sizeof(buf2), buf, 1, argv);
/* display it */
diff --git a/engines/glk/tads/tads2/text_io.h b/engines/glk/tads/tads2/text_io.h
index d4dd16f..c6ba34c 100644
--- a/engines/glk/tads/tads2/text_io.h
+++ b/engines/glk/tads/tads2/text_io.h
@@ -111,7 +111,7 @@ void outreset(void);
* (in particular, if no more input is available from the keyboard),
* zero on success.
*/
-int getstring(char *prompt, char *buf, int bufl);
+int getstring(const char *prompt, char *buf, int bufl);
/* set capitalize-next-character mode on/off */
void outcaps(void);
@@ -133,7 +133,7 @@ int tiologcls(tiocxdef *ctx);
* caller should not include any newlines in the text being displayed
* here.
*/
-void out_logfile_print(char *txt, int nl);
+void out_logfile_print(const char *txt, int nl);
/*
diff --git a/engines/glk/tads/tads2/vocabulary.cpp b/engines/glk/tads/tads2/vocabulary.cpp
index 8c4fead..e04dcb5 100644
--- a/engines/glk/tads/tads2/vocabulary.cpp
+++ b/engines/glk/tads/tads2/vocabulary.cpp
@@ -68,7 +68,7 @@ vocwdef *vocwget(voccxdef *ctx, uint idx)
#endif /*VOCW_IN_CACHE */
/* hash value is based on first 6 characters only to allow match-in-6 */
-uint vochsh(uchar *t, int len)
+uint vochsh(const uchar *t, int len)
{
uint ret = 0;
@@ -80,7 +80,7 @@ uint vochsh(uchar *t, int len)
}
/* copy vocabulary word, and convert to lower case */
-static void voccpy(uchar *dst, uchar *src, int len)
+static void voccpy(uchar *dst, const uchar *src, int len)
{
for ( ; len ; --len, ++dst, ++src)
*dst = vocisupper(*src) ? tolower(*src) : *src;
diff --git a/engines/glk/tads/tads2/vocabulary.h b/engines/glk/tads/tads2/vocabulary.h
index d28dcfe..01fcbf3 100644
--- a/engines/glk/tads/tads2/vocabulary.h
+++ b/engines/glk/tads/tads2/vocabulary.h
@@ -163,8 +163,8 @@ struct vocddef {
/* 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 */
+ const char *vocolfst; /* first word in cmd[] that identified object */
+ const 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) */
};
@@ -346,14 +346,14 @@ void vociren(voccxdef *ctx, objnum oldnum, objnum newnum);
struct vocseadef {
vocdef *v;
vocwdef *vw;
- uchar *wrd1;
+ const uchar *wrd1;
int len1;
- uchar *wrd2;
+ const uchar *wrd2;
int len2;
};
/* find first word matching a given word */
-vocwdef *vocffw(voccxdef *ctx, char *wrd, int len, char *wrd2, int len2,
+vocwdef *vocffw(voccxdef *ctx, const char *wrd, int len, const char *wrd2, int len2,
int p, vocseadef *search_ctx);
/* find next word */
@@ -435,7 +435,7 @@ int try_preparse_cmd(voccxdef *ctx, char **cmd, int wrdcnt,
*/
int try_unknown_verb(voccxdef *ctx, objnum actor,
char **cmd, int *typelist, int wrdcnt, int *next_start,
- int do_fuses, int err, char *msg, ...);
+ int do_fuses, int err, const char *msg, ...);
/* find a template */
int voctplfnd(voccxdef *ctx, objnum verb_in, objnum prep,
@@ -445,7 +445,7 @@ int voctplfnd(voccxdef *ctx, objnum verb_in, objnum prep,
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);
+ char *cmd[], const char *firstwrd, const char *lastwrd);
/*
* check noun - determines whether the next set of words is a valid noun
@@ -506,7 +506,7 @@ void vocnoreach(voccxdef *ctx, objnum *list1, int cnt,
int multi_base_index, int multi_total_count);
/* set {numObj | strObj}.value, as appropriate */
-void vocsetobj(voccxdef *ctx, objnum obj, dattyp typ, void *val,
+void vocsetobj(voccxdef *ctx, objnum obj, dattyp typ, const void *val,
vocoldef *inobj, vocoldef *outobj);
/* macros to read values out of templates */
@@ -565,7 +565,7 @@ void vocsetobj(voccxdef *ctx, objnum obj, dattyp typ, void *val,
/* structure for special internal word table */
struct vocspdef {
- char *vocspin;
+ const char *vocspin;
char vocspout;
};
@@ -643,7 +643,7 @@ void vocdusave_delobj(voccxdef *ctx, objnum objn);
void vocdusave_me(voccxdef *ctx, objnum old_me);
/* compute vocabulary word hash value */
-uint vochsh(uchar *t, int len);
+uint vochsh(const uchar *t, int len);
/* TADS versions of isalpha, isspace, isdigit, etc */
#define vocisupper(c) ((uchar)(c) <= 127 && Common::isUpper((uchar)(c)))
diff --git a/engines/glk/tads/tads2/vocabulary_parser.cpp b/engines/glk/tads/tads2/vocabulary_parser.cpp
index 0b7bed6..a70c6f8 100644
--- a/engines/glk/tads/tads2/vocabulary_parser.cpp
+++ b/engines/glk/tads/tads2/vocabulary_parser.cpp
@@ -32,7 +32,7 @@ namespace TADS {
namespace TADS2 {
-static char *type_names[] =
+static const char *type_names[] =
{
"article", "adj", "noun", "prep", "verb", "special", "plural",
"unknown"
@@ -203,10 +203,10 @@ void voc_push_objlist(voccxdef *ctx, objnum objlist[], int cnt)
* byte. The list is bounded by firstwrd and lastwrd, inclusive of
* both.
*/
-static void voc_push_strlist(voccxdef *ctx, char *firstwrd, char *lastwrd)
+static void voc_push_strlist(voccxdef *ctx, const char *firstwrd, const char *lastwrd)
{
size_t curlen;
- char *p;
+ const char *p;
uint lstsiz;
uchar *lstp;
@@ -390,7 +390,7 @@ static void voc_push_toklist(voccxdef *ctx, char *wordlist[], int cnt)
int vocread(voccxdef *ctx, objnum actor, objnum verb,
char *buf, int bufl, int type)
{
- char *prompt;
+ const char *prompt;
int ret;
/* presume we'll return success */
@@ -528,7 +528,7 @@ int vocread(voccxdef *ctx, objnum actor, objnum verb,
* the user's entry, the second is the reference word in the dictionary.)
* Returns TRUE if the words match, FALSE otherwise.
*/
-static int voceq(uchar *s1, uint l1, uchar *s2, uint l2)
+static int voceq(const uchar *s1, uint l1, uchar *s2, uint l2)
{
uint i;
@@ -599,7 +599,7 @@ vocwdef *vocfnw(voccxdef *voccx, vocseadef *search_ctx)
}
/* find the first vocdef matching a set of words */
-vocwdef *vocffw(voccxdef *ctx, char *wrd, int len, char *wrd2, int len2,
+vocwdef *vocffw(voccxdef *ctx, const char *wrd, int len, const char *wrd2, int len2,
int p, vocseadef *search_ctx)
{
uint hshval;
@@ -607,15 +607,15 @@ vocwdef *vocffw(voccxdef *ctx, char *wrd, int len, char *wrd2, int len2,
vocwdef *vw, *vwf = nullptr;
/* get the word's hash value */
- hshval = vochsh((uchar *)wrd, len);
+ hshval = vochsh((const uchar *)wrd, len);
/* scan the hash list until we run out of entries, or find a match */
for (v = ctx->voccxhsh[hshval], vf = 0 ; v != 0 && vf == 0 ;
v = v->vocnxt)
{
/* if this word matches, look at the objects in its list */
- if (voceq((uchar *)wrd, len, v->voctxt, v->voclen)
- && voceq((uchar *)wrd2, len2, v->voctxt + v->voclen, v->vocln2))
+ if (voceq((const uchar *)wrd, len, v->voctxt, v->voclen)
+ && voceq((const uchar *)wrd2, len2, v->voctxt + v->voclen, v->vocln2))
{
/* look for a suitable object in the vocwdef list */
for (vw = vocwget(ctx, v->vocwlst) ; vw ;
@@ -646,9 +646,9 @@ vocwdef *vocffw(voccxdef *ctx, char *wrd, int len, char *wrd2, int len2,
search_ctx->vw = vw;
/* save the search criteria */
- search_ctx->wrd1 = (uchar *)wrd;
+ search_ctx->wrd1 = (const uchar *)wrd;
search_ctx->len1 = len;
- search_ctx->wrd2 = (uchar *)wrd2;
+ search_ctx->wrd2 = (const uchar *)wrd2;
search_ctx->len2 = len2;
}
@@ -970,7 +970,7 @@ void vocerr(voccxdef *ctx, int err, const char *f, ...)
*/
int try_unknown_verb(voccxdef *ctx, objnum actor,
char **cmd, int *typelist, int wrdcnt, int *next_start,
- int do_fuses, int vocerr_err, char *vocerr_msg, ...)
+ int do_fuses, int vocerr_err, const char *vocerr_msg, ...)
{
int show_msg;
va_list argptr;
@@ -1169,7 +1169,7 @@ int try_unknown_verb(voccxdef *ctx, objnum actor,
#define vocisspec(wrd) \
(vocisupper(*wrd) || (!vocisalpha(*wrd) && *wrd != '\'' && *wrd != '-'))
-static vocspdef vocsptab[] =
+static const vocspdef vocsptab[] =
{
{ "of", VOCW_OF },
{ "and", VOCW_AND },
@@ -1191,7 +1191,7 @@ static vocspdef vocsptab[] =
};
/* test a word to see if it's a particular special word */
-static int voc_check_special(voccxdef *ctx, char *wrd, int checktyp)
+static int voc_check_special(voccxdef *ctx, const char *wrd, int checktyp)
{
/* search the user or built-in special table, as appropriate */
if (ctx->voccxspp)
@@ -1219,13 +1219,13 @@ static int voc_check_special(voccxdef *ctx, char *wrd, int checktyp)
}
else
{
- vocspdef *x;
+ const vocspdef *x;
for (x = vocsptab ; x->vocspin ; ++x)
{
/* if it matches in type and text, we have a match */
if (x->vocspout == checktyp
- && !strncmp((char *)wrd, x->vocspin, (size_t)6))
+ && !strncmp((const char *)wrd, x->vocspin, (size_t)6))
return TRUE;
}
}
@@ -1240,7 +1240,7 @@ int voctok(voccxdef *ctx, char *cmd, char *outbuf, char **wrd,
int lower, int cvt_ones, int show_errors)
{
int i;
- vocspdef *x;
+ const vocspdef *x;
int l;
char *p;
char *w;
@@ -1355,8 +1355,7 @@ int voctok(voccxdef *ctx, char *cmd, char *outbuf, char **wrd,
{
for (x = vocsptab ; x->vocspin ; ++x)
{
- if (!strncmp((char *)wrd[i-1], (char *)x->vocspin,
- (size_t)6)
+ if (!strncmp((char *)wrd[i-1], x->vocspin, (size_t)6)
&& (cvt_ones ||
(x->vocspout != VOCW_ONE
&& x->vocspout != VOCW_ONES))
@@ -1933,7 +1932,7 @@ static int vocisect_flags(objnum *list1, uint *flags1,
static int vocgol(voccxdef *ctx, objnum *list, uint *flags, char **wrdlst,
int *typlst, int first, int cur, int last, int ofword)
{
- char *wrd;
+ const char *wrd;
int typ;
vocwdef *v;
int cnt;
@@ -2437,7 +2436,7 @@ void voc_make_obj_name(voccxdef *ctx, char *namebuf, char *cmd[],
* Make an object name from a list entry
*/
void voc_make_obj_name_from_list(voccxdef *ctx, char *namebuf,
- char *cmd[], char *firstwrd, char *lastwrd)
+ char *cmd[], const char *firstwrd, const char *lastwrd)
{
int i, i1, i2;
@@ -2583,7 +2582,7 @@ static int vocg1o(voccxdef *ctx, char *cmd[], int typelist[],
n++;
if (!cmd[n])
{
- char *p;
+ const char *p;
int ver;
if (vocspec(cmd[cur], VOCW_ALL))
@@ -4628,7 +4627,7 @@ int vocchkvis(voccxdef *ctx, objnum obj, objnum cmdActor)
}
/* set {numObj | strObj}.value, as appropriate */
-void vocsetobj(voccxdef *ctx, objnum obj, dattyp typ, void *val,
+void vocsetobj(voccxdef *ctx, objnum obj, dattyp typ, const void *val,
vocoldef *inobj, vocoldef *outobj)
{
*outobj = *inobj;
@@ -4638,7 +4637,7 @@ void vocsetobj(voccxdef *ctx, objnum obj, dattyp typ, void *val,
/* set up a vocoldef */
static void vocout(vocoldef *outobj, objnum obj, int flg,
- char *fst, char *lst)
+ const char *fst, const char *lst)
{
outobj->vocolobj = obj;
outobj->vocolflg = flg;
@@ -4765,7 +4764,7 @@ static void voc_get_spec_str(voccxdef *ctx, char vocw_id,
/* set it/him/her */
static int vocsetit(voccxdef *ctx, objnum obj, int accprop,
objnum actor, objnum verb, objnum prep,
- vocoldef *outobj, char *default_name, char vocw_id,
+ vocoldef *outobj, const char *default_name, char vocw_id,
prpnum defprop, int silent)
{
if (obj == MCMONINV || !vocchkaccess(ctx, obj, (prpnum)accprop,
@@ -4858,7 +4857,7 @@ static int voc_disambig_hook(voccxdef *ctx, objnum verb, objnum actor,
objnum prep, objnum otherobj,
prpnum accprop, prpnum verprop,
objnum *objlist, uint *flags, int *objcount,
- char *firstwrd, char *lastwrd,
+ const char *firstwrd, const char *lastwrd,
int num_wanted, int is_ambig, char *resp,
int silent)
{
@@ -5477,7 +5476,7 @@ int vocdisambig(voccxdef *ctx, vocoldef *outlist, vocoldef *inlist,
int lpos = inpos;
int i = 0;
int cnt;
- char *p;
+ const char *p;
int cnt2, cnt3;
int trying_again;
int user_count = 0;
@@ -6985,7 +6984,7 @@ int vocdisambig(voccxdef *ctx, vocoldef *outlist, vocoldef *inlist,
* use "such"
*/
if (i != cnt2) {
- char *last;
+ const char *last;
/* clear the word buffer */
newobj[0] = '\0';
Commit: 0d0dbf601e70b569936469c88b9f2a8f0197e6e6
https://github.com/scummvm/scummvm/commit/0d0dbf601e70b569936469c88b9f2a8f0197e6e6
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:07-07:00
Commit Message:
GLK: TADS2: Fix uninitialized variable warnings
Changed paths:
engines/glk/tads/tads2/output.cpp
engines/glk/tads/tads2/run.cpp
engines/glk/tads/tads2/run.h
diff --git a/engines/glk/tads/tads2/output.cpp b/engines/glk/tads/tads2/output.cpp
index 79284b7..5f7e46b 100644
--- a/engines/glk/tads/tads2/output.cpp
+++ b/engines/glk/tads/tads2/output.cpp
@@ -2448,7 +2448,7 @@ static int outformatlen_stream(out_stream_info *stream,
|| !scumm_stricmp(tagbuf, "em")
|| !scumm_stricmp(tagbuf, "strong"))
{
- int attr;
+ int attr = 0;
/* choose the attribute flag */
switch (tagbuf[0])
diff --git a/engines/glk/tads/tads2/run.cpp b/engines/glk/tads/tads2/run.cpp
index 64b52a4..0def6a7 100644
--- a/engines/glk/tads/tads2/run.cpp
+++ b/engines/glk/tads/tads2/run.cpp
@@ -1096,15 +1096,19 @@ void runexe(runcxdef *ctx, uchar *p0, objnum self, objnum target,
runsdef val; /* stack element (for pushing) */
runsdef val2; /* another one (for popping in two-op instructions) */
uint ofs; /* offset in code of current execution */
- prpnum prop; /* property number, when needed */
- objnum obj; /* object number, when needed */
+ prpnum prop = 0; /* property number, when needed */
+ objnum obj = 0; /* object number, when needed */
runsdef *noreg rstsp; /* sp to reset to on DISCARD instructions */
- uchar *lstp; /* list pointer */
+ uchar *lstp = nullptr; /* list pointer */
int nargc; /* argument count of called function */
runsdef *valp;
runsdef *stkval;
- int i;
+ int i = 0;
int brkchk;
+ runsdef val3;
+ int asityp;
+ int asiext = 0;
+ int lclnum = 0;
#ifndef DBG_OFF
int err;
@@ -1954,11 +1958,6 @@ resume_from_error:
default:
if ((opc & OPCASI_MASK) == OPCASI_MASK)
{
- runsdef val3;
- int asityp;
- int asiext;
- int lclnum;
-
valp = &val;
stkval = &val;
diff --git a/engines/glk/tads/tads2/run.h b/engines/glk/tads/tads2/run.h
index 0a39fca..8b7c590 100644
--- a/engines/glk/tads/tads2/run.h
+++ b/engines/glk/tads/tads2/run.h
@@ -57,7 +57,11 @@ struct runsdef {
objnum runsvobj; /* object value */
prpnum runsvprp; /* property number value */
uchar *runsvstr; /* string/list value */
- } runsv;
+ } runsv;
+
+ runsdef() : runstyp(0) {
+ runsv.runsvnum = 0;
+ }
};
/* external function control structure */
Commit: 139673984edd38895ed7646066f2af06f3dc00a3
https://github.com/scummvm/scummvm/commit/139673984edd38895ed7646066f2af06f3dc00a3
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:07-07:00
Commit Message:
GLK: TADS2: Revert char * statics back to string literals
Changed paths:
engines/glk/tads/tads2/execute_command.cpp
engines/glk/tads/tads2/vocabulary_parser.cpp
diff --git a/engines/glk/tads/tads2/execute_command.cpp b/engines/glk/tads/tads2/execute_command.cpp
index 63c5f02..ffec9d6 100644
--- a/engines/glk/tads/tads2/execute_command.cpp
+++ b/engines/glk/tads/tads2/execute_command.cpp
@@ -1687,11 +1687,8 @@ static void exesaveit(voccxdef *ctx, vocoldef *dolist)
ctx->voccxthm[i] = dolist[i].vocolobj;
if (dbg)
{
- static char *STR1 = ", ";
- static char *STR2 = "]\\n";
-
runppr(rcx, dolist[i].vocolobj, PRP_SDESC, 0);
- tioputs(ctx->voccxtio, i+1 < cnt ? STR1 : STR2);
+ tioputs(ctx->voccxtio, i+1 < cnt ? ", " : "]\\n");
}
}
}
diff --git a/engines/glk/tads/tads2/vocabulary_parser.cpp b/engines/glk/tads/tads2/vocabulary_parser.cpp
index a70c6f8..fba515e 100644
--- a/engines/glk/tads/tads2/vocabulary_parser.cpp
+++ b/engines/glk/tads/tads2/vocabulary_parser.cpp
@@ -1200,7 +1200,7 @@ static int voc_check_special(voccxdef *ctx, const char *wrd, int checktyp)
char *endp;
char typ;
int len;
- int wrdlen = strlen((char *)wrd);
+ int wrdlen = strlen(wrd);
for (p = ctx->voccxspp, endp = p + ctx->voccxspl ;
p < endp ; )
@@ -2516,9 +2516,7 @@ static int vocg1o(voccxdef *ctx, char *cmd[], int typelist[],
/* show trace message if in debug mode */
if (ctx->voccxflg & VOCCXFDBG) {
- static char *CHECK = ". Checking for actor\\n";
- static char *READING = ". Reading noun phrase\\n";
- tioputs(ctx->vocxtio, chkact ? CHECK : READING);
+ tioputs(ctx->vocxtio, chkact ? ". Checking for actor\\n" : ". Reading noun phrase\\n");
}
/* try the user parseNounPhrase hook */
@@ -5300,11 +5298,9 @@ int vocdisambig(voccxdef *ctx, vocoldef *outlist, vocoldef *inlist,
else if (inlist[inpos].vocolflg == VOCS_IT ||
(inlist[inpos].vocolflg == VOCS_THEM && ctx->voccxthc == 0))
{
- static char *IT = "it";
- static char *THEM = "them";
err = vocsetit(ctx, ctx->voccxit, accprop, cmdActor,
cmdVerb, cmdPrep, &outlist[outpos],
- inlist[inpos].vocolflg == VOCS_IT ? IT : THEM,
+ inlist[inpos].vocolflg == VOCS_IT ? "it" : "them",
(char)(inlist[inpos].vocolflg == VOCS_IT
? VOCW_IT : VOCW_THEM), defprop, silent);
if (err != 0)
@@ -6803,11 +6799,8 @@ int vocdisambig(voccxdef *ctx, vocoldef *outlist, vocoldef *inlist,
}
else if (vocspec(diswordlist[0], VOCW_ANY))
{
- static char *anynm = "any";
-
/* choose the first object arbitrarily */
- vocout(&outlist[outpos++], list1[i], VOCS_ALL,
- anynm, anynm);
+ vocout(&outlist[outpos++], list1[i], VOCS_ALL, "any", "any");
break;
}
else
Commit: 608e9af69d203d4dd4fc65d16ef808d945341ed3
https://github.com/scummvm/scummvm/commit/608e9af69d203d4dd4fc65d16ef808d945341ed3
Author: dreammaster (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:07-07:00
Commit Message:
GLK: TADS2: Fix more const loss warnings
Changed paths:
engines/glk/tads/tads2/line_source_file.cpp
engines/glk/tads/tads2/line_source_file.h
engines/glk/tads/tads2/runtime_app.h
engines/glk/tads/tads2/runtime_driver.cpp
engines/glk/tads/tads2/tokenizer.cpp
engines/glk/tads/tads2/tokenizer.h
diff --git a/engines/glk/tads/tads2/line_source_file.cpp b/engines/glk/tads/tads2/line_source_file.cpp
index c2942a8..1cf90b2 100644
--- a/engines/glk/tads/tads2/line_source_file.cpp
+++ b/engines/glk/tads/tads2/line_source_file.cpp
@@ -35,7 +35,7 @@ namespace TADS2 {
/* initialize a pre-allocated linfdef, skipping debugger page setup */
void linfini2(mcmcxdef *mctx, linfdef *linf,
- char *filename, int flen, osfildef *fp, int new_line_records)
+ const char *filename, int flen, osfildef *fp, int new_line_records)
{
/* set up method pointers */
linf->linflin.lingetp = linfget;
@@ -81,7 +81,7 @@ void linfini2(mcmcxdef *mctx, linfdef *linf,
* linfdef even if we can't find the file, reserving the maximum space
* for its path name to be filled in later.
*/
-linfdef *linfini(mcmcxdef *mctx, errcxdef *ec, char *filename,
+linfdef *linfini(mcmcxdef *mctx, errcxdef *ec, const char *filename,
int flen, tokpdef *path, int must_find_file,
int new_line_records)
{
diff --git a/engines/glk/tads/tads2/line_source_file.h b/engines/glk/tads/tads2/line_source_file.h
index a8b90d8..91b3127 100644
--- a/engines/glk/tads/tads2/line_source_file.h
+++ b/engines/glk/tads/tads2/line_source_file.h
@@ -75,13 +75,13 @@ struct linfdef {
};
/* initialize a file line source, opening the file for the line source */
-linfdef *linfini(mcmcxdef *mctx, errcxdef *errctx, char *filename,
+linfdef *linfini(mcmcxdef *mctx, errcxdef *errctx, const char *filename,
int flen, tokpdef *path, int must_find_file,
int new_line_records);
/* initialize a pre-allocated linfdef, skipping debugger page setup */
void linfini2(mcmcxdef *mctx, linfdef *linf,
- char *filename, int flen, osfildef *fp, int new_line_records);
+ const char *filename, int flen, osfildef *fp, int new_line_records);
/* get next line from line source */
int linfget(lindef *lin);
diff --git a/engines/glk/tads/tads2/runtime_app.h b/engines/glk/tads/tads2/runtime_app.h
index 0e4e163..e6f36d2 100644
--- a/engines/glk/tads/tads2/runtime_app.h
+++ b/engines/glk/tads/tads2/runtime_app.h
@@ -51,13 +51,13 @@ extern int G_tads_oem_copyright_prefix;
* application container context is optional; pass null if no context is
* required.
*/
-int trdmain(int argc, char **argv, appctxdef *appctx, char *save_ext);
+int trdmain(int argc, char **argv, appctxdef *appctx, const char *save_ext);
/*
* Main debugger subsystem entrypoint. Works like trdmain(), but starts
* the game under the debugger.
*/
-int tddmain(int argc, char **argv, appctxdef *appctx, char *save_ext);
+int tddmain(int argc, char **argv, appctxdef *appctx, const char *save_ext);
/*
* close and delete the swap file
diff --git a/engines/glk/tads/tads2/runtime_driver.cpp b/engines/glk/tads/tads2/runtime_driver.cpp
index 6b6a6d7..6f1674d 100644
--- a/engines/glk/tads/tads2/runtime_driver.cpp
+++ b/engines/glk/tads/tads2/runtime_driver.cpp
@@ -254,7 +254,7 @@ static void trdusage_s(errcxdef *ec)
static void trdmain1(errcxdef *ec, int argc, char *argv[],
- appctxdef *appctx, char *save_ext)
+ appctxdef *appctx, const char *save_ext)
{
osfildef *swapfp = (osfildef *)0;
runcxdef runctx;
@@ -857,7 +857,7 @@ void trd_close_swapfile(runcxdef *runctx)
}
/* main - called by os main after setting up arguments */
-int trdmain(int argc, char *argv[], appctxdef *appctx, char *save_ext)
+int trdmain(int argc, char *argv[], appctxdef *appctx, const char *save_ext)
{
errcxdef errctx;
int err;
diff --git a/engines/glk/tads/tads2/tokenizer.cpp b/engines/glk/tads/tads2/tokenizer.cpp
index fc0ff01..85bba24 100644
--- a/engines/glk/tads/tads2/tokenizer.cpp
+++ b/engines/glk/tads/tads2/tokenizer.cpp
@@ -39,11 +39,11 @@ static char tokmac3[] = "),nil))";
static char tokmac4[] = ")";
/* forward definition of static functions */
-static int tokdfhsh(char *sym, int len);
+static int tokdfhsh(const char *sym, int len);
/* find a #define symbol */
-static tokdfdef *tok_find_define(tokcxdef *ctx, char *sym, int len)
+static tokdfdef *tok_find_define(tokcxdef *ctx, const char *sym, int len)
{
int hsh;
tokdfdef *df;
@@ -150,7 +150,7 @@ void tok_write_defines(tokcxdef *ctx, osfildef *fp, errcxdef *ec)
}
/* compute a #define symbol's hash value */
-static int tokdfhsh(char *sym, int len)
+static int tokdfhsh(const char *sym, int len)
{
uint hsh;
@@ -160,8 +160,8 @@ static int tokdfhsh(char *sym, int len)
}
/* convert a #define symbol to lower case if folding case */
-static char *tok_casefold_defsym(tokcxdef *ctx, char *outbuf,
- char *src, int len)
+static const char *tok_casefold_defsym(tokcxdef *ctx, char *outbuf,
+ const char *src, int len)
{
if (ctx->tokcxflg & TOKCXCASEFOLD)
{
@@ -205,8 +205,8 @@ void tok_case_fold(tokcxdef *ctx, tokdef *tok)
}
/* add a symbol to the #define symbol table, folding case if necessary */
-void tok_add_define_cvtcase(tokcxdef *ctx, char *sym, int len,
- char *expan, int explen)
+void tok_add_define_cvtcase(tokcxdef *ctx, const char *sym, int len,
+ const char *expan, int explen)
{
char mysym[TOKNAMMAX];
@@ -218,7 +218,7 @@ void tok_add_define_cvtcase(tokcxdef *ctx, char *sym, int len,
}
/* add a symbol to the #define symbol table */
-void tok_add_define(tokcxdef *ctx, char *sym, int len,
+void tok_add_define(tokcxdef *ctx, const char *sym, int len,
char *expan, int explen)
{
int hsh;
@@ -261,7 +261,7 @@ 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)
+void tok_del_define(tokcxdef *ctx, const char *sym, int len)
{
int hsh;
tokdfdef *df;
@@ -290,7 +290,7 @@ void tok_del_define(tokcxdef *ctx, char *sym, int len)
}
/* scan a #define symbol to see how long it is */
-static int tok_scan_defsym(tokcxdef *ctx, char *p, int len)
+static int tok_scan_defsym(tokcxdef *ctx, const char *p, int len)
{
int symlen;
@@ -307,11 +307,11 @@ static int tok_scan_defsym(tokcxdef *ctx, char *p, int len)
}
/* process a #define */
-static void tokdefine(tokcxdef *ctx, char *p, int len)
+static void tokdefine(tokcxdef *ctx, const char *p, int len)
{
- char *sym;
+ const char *sym;
int symlen;
- char *expan;
+ const char *expan;
char mysym[TOKNAMMAX];
/* get the symbol */
@@ -374,10 +374,10 @@ static void tok_update_if_stat(tokcxdef *ctx)
}
/* process an #ifdef or a #ifndef */
-static void tok_ifdef_ifndef(tokcxdef *ctx, char *p, int len, int is_ifdef)
+static void tok_ifdef_ifndef(tokcxdef *ctx, const char *p, int len, int is_ifdef)
{
int symlen;
- char *sym;
+ const char *sym;
int stat;
int found;
char mysym[TOKNAMMAX];
@@ -416,38 +416,38 @@ static void tok_ifdef_ifndef(tokcxdef *ctx, char *p, int len, int is_ifdef)
}
/* process a #error */
-static void tok_p_error(tokcxdef *ctx, char *p, int len)
+static void tok_p_error(tokcxdef *ctx, const char *p, int len)
{
errlog1(ctx->tokcxerr, ERR_P_ERROR,
ERRTSTR, errstr(ctx->tokcxerr, p, len));
}
/* process a #ifdef */
-static void tokifdef(tokcxdef *ctx, char *p, int len)
+static void tokifdef(tokcxdef *ctx, const char *p, int len)
{
tok_ifdef_ifndef(ctx, p, len, TRUE);
}
/* process a #ifndef */
-static void tokifndef(tokcxdef *ctx, char *p, int len)
+static void tokifndef(tokcxdef *ctx, const char *p, int len)
{
tok_ifdef_ifndef(ctx, p, len, FALSE);
}
/* process a #if */
-static void tokif(tokcxdef *ctx, char *p, int len)
+static void tokif(tokcxdef *ctx, const char *p, int len)
{
errsig(ctx->tokcxerr, ERR_PIF_NA);
}
/* process a #elif */
-static void tokelif(tokcxdef *ctx, char *p, int len)
+static void tokelif(tokcxdef *ctx, const char *p, int len)
{
errsig(ctx->tokcxerr, ERR_PELIF_NA);
}
/* process a #else */
-static void tokelse(tokcxdef *ctx, char *p, int len)
+static void tokelse(tokcxdef *ctx, const char *p, int len)
{
int cnt;
@@ -471,7 +471,7 @@ static void tokelse(tokcxdef *ctx, char *p, int len)
}
/* process a #endif */
-static void tokendif(tokcxdef *ctx, char *p, int len)
+static void tokendif(tokcxdef *ctx, const char *p, int len)
{
/* if we're not expecting #endif, it's an error */
if (ctx->tokcxifcnt == 0)
@@ -488,9 +488,9 @@ static void tokendif(tokcxdef *ctx, char *p, int len)
}
/* process a #undef */
-static void tokundef(tokcxdef *ctx, char *p, int len)
+static void tokundef(tokcxdef *ctx, const char *p, int len)
{
- char *sym;
+ const char *sym;
int symlen;
char mysym[TOKNAMMAX];
@@ -514,7 +514,7 @@ static void tokundef(tokcxdef *ctx, char *p, int len)
}
/* process a #pragma directive */
-static void tokpragma(tokcxdef *ctx, char *p, int len)
+static void tokpragma(tokcxdef *ctx, const char *p, int len)
{
/* ignore empty pragmas */
if (len == 0)
@@ -549,15 +549,15 @@ static void tokpragma(tokcxdef *ctx, char *p, int len)
}
/* process a #include directive */
-static void tokinclude(tokcxdef *ctx, char *p, int len)
+static void tokinclude(tokcxdef *ctx, const char *p, int len)
{
linfdef *child;
tokpdef *path;
- char *fname;
+ const char *fname;
int match;
int flen;
linfdef *lin;
- char *q;
+ const char *q;
size_t flen2;
/* find the filename portion */
@@ -749,14 +749,14 @@ static int tokgetlin(tokcxdef *ctx, int dopound)
if (dopound && ctx->tokcxlen != 0 && ctx->tokcxptr[0] == '#'
&& !(ctx->tokcxlin->linflg & LINFNOINC))
{
- char *p;
+ const char *p;
int len;
- static struct
+ static const struct
{
- char *nm;
+ const char *nm;
int len;
int ok_in_if;
- void (*fn)(tokcxdef *, char *, int);
+ void (*fn)(tokcxdef *, const char *, int);
}
*dirp, dir[] =
{
@@ -850,7 +850,7 @@ static int tokgetlin(tokcxdef *ctx, int dopound)
/* get the next token, removing it from the input stream */
int toknext(tokcxdef *ctx)
{
- char *p;
+ const char *p;
tokdef *tok = &ctx->tokcxcur;
int len;
@@ -949,20 +949,21 @@ nexttoken:
{
int l;
int hash;
- char *q;
+ const char *q;
+ char *tq;
toktdef *tab;
int found = FALSE;
uchar thischar;
tokdfdef *df;
- for (hash = 0, l = 0, q = tok->toknam ;
+ for (hash = 0, l = 0, tq = tok->toknam ;
len != 0 && TOKISSYM(*p) && l < TOKNAMMAX ;
(thischar = ((Common::isUpper((uchar)*p)
&& (ctx->tokcxflg & TOKCXCASEFOLD))
? Common::isLower((uchar)*p) : *p)),
(hash = ((hash + thischar) & (TOKHASHSIZE - 1))),
- (*q++ = thischar), ++p, --len, ++l) ;
- *q = '\0';
+ (*tq++ = thischar), ++p, --len, ++l) ;
+ *tq = '\0';
if (len != 0 && TOKISSYM(*p))
{
while (len != 0 && TOKISSYM(*p)) ++p, --len;
@@ -1081,7 +1082,7 @@ nexttoken:
else if (*p == '"' || *p == '\'')
{
char delim; /* closing delimiter we're looking for */
- char *strstart; /* pointer to start of string */
+ const char *strstart; /* pointer to start of string */
int warned;
delim = *p;
diff --git a/engines/glk/tads/tads2/tokenizer.h b/engines/glk/tads/tads2/tokenizer.h
index 11d76c6..882ab92 100644
--- a/engines/glk/tads/tads2/tokenizer.h
+++ b/engines/glk/tads/tads2/tokenizer.h
@@ -334,10 +334,10 @@ struct tokcxdef {
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);
+ void (*tokcxsad)(void *ctx, const char *str, ushort len);
/* add characters to a string */
void (*tokcxsend)(void *ctx); /* finish storing string */
- char *tokcxmsav[TOKMACNEST]; /* saved positions for macro expansion */
+ const char *tokcxmsav[TOKMACNEST]; /* saved positions for macro expansion */
ushort tokcxmsvl[TOKMACNEST]; /* saved lengths for macro expansion */
int tokcxmlvl; /* macro nesting level */
int tokcxflg; /* flags */
@@ -350,7 +350,7 @@ struct tokcxdef {
tokdef tokcxcur; /* current token */
char *tokcxbuf; /* buffer for long lines */
ushort tokcxbsz; /* size of long line buffer */
- char *tokcxptr; /* pointer into line source */
+ const 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 */
@@ -429,8 +429,8 @@ int toknext(tokcxdef *ctx);
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);
+void tok_add_define(tokcxdef *ctx, const char *sym, int len,
+ const char *expan, int explen);
/*
* add a symbol to the #define symbol table, folding case if we're
More information about the Scummvm-git-logs
mailing list