aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Gilbert2019-05-18 13:26:57 -1000
committerPaul Gilbert2019-05-24 18:21:06 -0700
commit3d9e03af554814bee10f112c2efa0b7f0b722489 (patch)
treef174c8004d0c554dae124df63d874f78d2e88e5d
parentfcb2592ec24f983c49f1e2e1b4f41cc9659a9229 (diff)
downloadscummvm-rg350-3d9e03af554814bee10f112c2efa0b7f0b722489.tar.gz
scummvm-rg350-3d9e03af554814bee10f112c2efa0b7f0b722489.tar.bz2
scummvm-rg350-3d9e03af554814bee10f112c2efa0b7f0b722489.zip
GLK: TADS2: Soooo much more implementation
-rw-r--r--engines/glk/glk.h5
-rw-r--r--engines/glk/glulxe/glulxe.cpp2
-rw-r--r--engines/glk/glulxe/glulxe.h1
-rw-r--r--engines/glk/module.mk15
-rw-r--r--engines/glk/tads/os_buffer.cpp103
-rw-r--r--engines/glk/tads/os_buffer.h48
-rw-r--r--engines/glk/tads/os_frob_tads.cpp476
-rw-r--r--engines/glk/tads/os_frob_tads.h1201
-rw-r--r--engines/glk/tads/os_glk.cpp1002
-rw-r--r--engines/glk/tads/os_glk.h3744
-rw-r--r--engines/glk/tads/osfrobtads.cpp100
-rw-r--r--engines/glk/tads/osfrobtads.h272
-rw-r--r--engines/glk/tads/tads.cpp36
-rw-r--r--engines/glk/tads/tads.h2
-rw-r--r--engines/glk/tads/tads2/appctx.h1
-rw-r--r--engines/glk/tads/tads2/built_in.h4
-rw-r--r--engines/glk/tads/tads2/character_map.cpp2
-rw-r--r--engines/glk/tads/tads2/command_line.cpp87
-rw-r--r--engines/glk/tads/tads2/command_line.h70
-rw-r--r--engines/glk/tads/tads2/error_handling.h2
-rw-r--r--engines/glk/tads/tads2/error_message.cpp86
-rw-r--r--engines/glk/tads/tads2/execute_command.cpp3688
-rw-r--r--engines/glk/tads/tads2/file_io.cpp8
-rw-r--r--engines/glk/tads/tads2/file_io.h8
-rw-r--r--engines/glk/tads/tads2/lib.h3
-rw-r--r--engines/glk/tads/tads2/ltk.cpp197
-rw-r--r--engines/glk/tads/tads2/ltk.h133
-rw-r--r--engines/glk/tads/tads2/memory_cache.h2
-rw-r--r--engines/glk/tads/tads2/memory_cache_swap.h2
-rw-r--r--engines/glk/tads/tads2/os.cpp9
-rw-r--r--engines/glk/tads/tads2/os.h4763
-rw-r--r--engines/glk/tads/tads2/play.cpp35
-rw-r--r--engines/glk/tads/tads2/play.h46
-rw-r--r--engines/glk/tads/tads2/post_compilation.cpp433
-rw-r--r--engines/glk/tads/tads2/post_compilation.h25
-rw-r--r--engines/glk/tads/tads2/qa_scriptor.cpp120
-rw-r--r--engines/glk/tads/tads2/run.h2
-rw-r--r--engines/glk/tads/tads2/runstat.cpp89
-rw-r--r--engines/glk/tads/tads2/runtime_app.cpp35
-rw-r--r--engines/glk/tads/tads2/runtime_app.h113
-rw-r--r--engines/glk/tads/tads2/runtime_driver.cpp839
-rw-r--r--engines/glk/tads/tads2/tads2.cpp554
-rw-r--r--engines/glk/tads/tads2/tads2.h38
-rw-r--r--engines/glk/tads/tads2/tokenizer.cpp57
-rw-r--r--engines/glk/tads/tads2/tokenizer_hash.cpp336
-rw-r--r--engines/glk/tads/tads2/vocabulary_parser.cpp8169
46 files changed, 20691 insertions, 6272 deletions
diff --git a/engines/glk/glk.h b/engines/glk/glk.h
index bebb4827f5..6b95eb2a62 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 cbe09cef07..ba67dea442 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 c0008e8f97..56a912eeca 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 810c493c88..1ce24586c9 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 0000000000..0f00e9dd9b
--- /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 0000000000..3fc2209cec
--- /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 0000000000..8c8d3f10c0
--- /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 0000000000..e8c0845760
--- /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 0000000000..5565086626
--- /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 75354eacef..237502a456 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 6dcaadb356..0000000000
--- 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 d503a7b023..0000000000
--- 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 5dc400b164..61e13cdb15 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 49f59f5bf5..e4640f04bf 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 3b3f499777..5f32217cc5 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 f5807e61a8..5078eda2ee 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 9191835db5..82fe865fa7 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 0000000000..c6bd5e0dda
--- /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 0000000000..a231fca1c4
--- /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 ab1f86e396..7c56b2f831 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 0000000000..3cfe8deea7
--- /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 0000000000..d38d92b370
--- /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,
+ (vocseadef *)0);
+
+ /* if we didn't find a verb template, we can't process the sentence */
+ if (n == 0)
+ {
+ /* try parseUnknownVerb, and show an error if that doesn't handle it */
+ if (try_unknown_verb(ctx, actor, cmd, typelist, wrdcnt, next_word,
+ TRUE, VOCERR(18),
+ "I don't understand that sentence."))
+ {
+ /* they handled it successfully - end the command with success */
+ return 0;
+ }
+ else
+ {
+ /*
+ * parseUnknownVerb failed or aborted - end the command with
+ * an error
+ */
+ return 1;
+ }
+ }
+
+ /* get the deepverb object */
+ verb = n->vocwobj;
+
+ /* default actor is "Me" */
+ if (actor == MCMONINV)
+ actor = ctx->voccxme;
+
+ /* set a savepoint, if we're keeping undo information */
+ if (ctx->voccxundo)
+ objusav(ctx->voccxundo);
+
+ /*
+ * Check that the room will allow this command -- it may not
+ * due to darkness or other ailment. We can find out with the
+ * roomCheck(verb) message, sent to the meobj.
+ */
+ {
+ int t;
+
+ /* call roomCheck */
+ runrst(rcx);
+ runpobj(rcx, verb);
+ runppr(rcx, ctx->voccxme, PRP_ROOMCHECK, 1);
+ t = runpoplog(rcx);
+
+ /* if they returned nil, stop the command, but indicate success */
+ if (!t)
+ return 0;
+ }
+
+ /* look for a new-style template first, then the old-style template */
+ exe_get_tpl(ctx, verb, &tplofs, &actofs);
+
+ /* make sure we found a verb */
+ if (tplofs == 0 && actofs == 0 && verb != ctx->voccxvag)
+ {
+ /* try parseUnknownVerb, and show an error if that doesn't handle it */
+ if (try_unknown_verb(ctx, actor, cmd, typelist, wrdcnt, next_word,
+ TRUE, VOCERR(23),
+ "internal error: verb has no action, doAction, or ioAction"))
+ return 0;
+ else
+ return 1;
+ }
+
+ /*
+ * Check to see if we have an "all" - if we do, we'll need to
+ * display the direct object's name even if only one direct object
+ * comes of it.
+ */
+ multi_flags = check_for_multi(dolist);
+
+ /*
+ * set up dobj word list in case objwords is used in doDefault (the
+ * game may want to check for "all" and disallow it, for example)
+ */
+ ctx->voccxdobj = dolist;
+
+ /* set up our stack allocations, which we may need from now on */
+ voc_enter(ctx, &save_sp);
+ VOC_STK_ARRAY(ctx, char, donewcmd, VOCBUFSIZ);
+ VOC_STK_ARRAY(ctx, char, ionewcmd, VOCBUFSIZ);
+ VOC_STK_ARRAY(ctx, char, donewbuf, 2*VOCBUFSIZ);
+ VOC_STK_ARRAY(ctx, char, ionewbuf, 2*VOCBUFSIZ);
+ VOC_STK_ARRAY(ctx, char *, donewlist, VOCBUFSIZ);
+ VOC_STK_ARRAY(ctx, char *, ionewlist, VOCBUFSIZ);
+ VOC_MAX_ARRAY(ctx, int, donewtype);
+ VOC_MAX_ARRAY(ctx, int, ionewtype);
+ VOC_MAX_ARRAY(ctx, vocoldef, dolist1);
+ VOC_MAX_ARRAY(ctx, vocoldef, iolist1);
+
+ /* keep going until we're done with the sentence */
+ for ( ;; )
+ {
+ askflags = err = 0;
+
+ ERRBEGIN(ctx->voccxerr)
+
+ /*
+ * Now see what kind of sentence we have. If we have no
+ * objects and an action, use the action. If we have a direct
+ * object and a doAction, use the doAction. If we have an
+ * indirect object and an ioAction with a matching preposition,
+ * use the ioAction. If we have an indirect object and no
+ * matching ioAction, complain. If we have a direct object and
+ * no doAction or ioAction, complain. If we have fewer objects
+ * than we really want, ask the user for more of them.
+ */
+ if (voclistlen(dolist) == 0 && voclistlen(iolist) == 0)
+ {
+ if (actofs || verb == ctx->voccxvag)
+ {
+ if ((err = exeloop(ctx, actor, verb, (vocoldef *)0, &prep,
+ (vocoldef *)0, multi_flags,
+ (uchar *)0, 0)) != 0)
+ goto exit_error;
+ }
+ else
+ {
+ /*
+ * The player has not specified any objects, but the
+ * verb seems to require one. See if there's a unique
+ * default.
+ */
+ runrst(rcx);
+ runpnil(rcx);
+ runpobj(rcx, prep);
+ runpobj(rcx, actor);
+ runppr(rcx, verb, PRP_DODEFAULT, 3);
+
+ if (runtostyp(rcx) == DAT_LIST)
+ {
+ uchar *l = runpoplst(rcx);
+ uint lstsiz;
+ objnum defobj;
+ int objcnt;
+ objnum newprep;
+ runsdef val;
+ objnum o;
+
+ /* push list back on stack, to keep in heap */
+ val.runsv.runsvstr = l;
+ val.runstyp = DAT_LIST;
+ runrepush(rcx, &val);
+
+ /* get list size out of list */
+ lstsiz = osrp2(l) - 2;
+ l += 2;
+
+ /* find default preposition for verb, if any */
+ runppr(rcx, verb, PRP_PREPDEFAULT, 0);
+ if (runtostyp(rcx) == DAT_OBJECT)
+ newprep = runpopobj(rcx);
+ else
+ {
+ newprep = MCMONINV;
+ rundisc(rcx);
+ }
+
+ if (!voctplfnd(ctx, verb, newprep, tpl, &newstyle))
+ {
+ for (objcnt = 0 ; lstsiz && objcnt < 2
+ ; lstadv(&l, &lstsiz))
+ {
+ if (*l == DAT_OBJECT)
+ {
+ ++objcnt;
+ defobj = osrp2(l + 1);
+ }
+ }
+ }
+ else
+ {
+ int dobj_first;
+
+ /*
+ * Get the template flags. If we must
+ * disambiguate the direct object first for this
+ * verb, do so now.
+ */
+ tplflags = (newstyle ? voctplflg(tpl) : 0);
+ dobj_first = (tplflags & VOCTPLFLG_DOBJ_FIRST);
+
+ for (objcnt = 0 ; lstsiz && objcnt < 2
+ ; lstadv(&l, &lstsiz))
+ {
+ if (*l == DAT_OBJECT)
+ {
+ o = osrp2(l + 1);
+ if (!objgetap(ctx->voccxmem, o, voctplvd(tpl),
+ (objnum *)0, FALSE))
+ continue;
+
+ tiohide(ctx->voccxtio);
+ if (newprep != MCMONINV && !dobj_first)
+ runpnil(rcx);
+ runpobj(rcx, actor);
+ runppr(rcx, o, voctplvd(tpl),
+ ((newprep != MCMONINV && !dobj_first)
+ ? 2 : 1));
+
+ if (!tioshow(ctx->voccxtio))
+ {
+ ++objcnt;
+ defobj = o;
+ }
+ }
+ }
+
+ /* no longer need list in heap, so discard it */
+ rundisc(rcx);
+
+ /* use default object if there's exactly one */
+ if (objcnt == 1)
+ {
+ dolist[0].vocolobj = defobj;
+ dolist[0].vocolflg = 0;
+ dolist[0].vocolfst = dolist[0].vocollst = 0;
+ dolist[1].vocolobj = MCMONINV;
+ dolist[1].vocolflg = 0;
+ dolist[1].vocolfst = dolist[1].vocollst = 0;
+
+ runrst(rcx);
+ if (ctx->voccxpdef2 != MCMONINV)
+ {
+ runpnil(rcx);
+ runpobj(rcx, defobj);
+ runpobj(rcx, verb);
+ runpobj(rcx, actor);
+ runfn(rcx, ctx->voccxpdef2, 4);
+ }
+ else if (ctx->voccxpdef != MCMONINV)
+ {
+ runpnil(rcx);
+ runpobj(rcx, defobj);
+ runfn(rcx, ctx->voccxpdef, 2);
+ }
+ else
+ {
+ /* tell the player what we're doing */
+ vocerr_info(ctx, VOCERR(130), "(");
+ runppr(rcx, defobj, PRP_THEDESC, 0);
+ vocerr_info(ctx, VOCERR(131), ")");
+ tioflush(ctx->voccxtio);
+ }
+ err = -2; /* "continue" */
+ goto exit_error;
+ }
+ }
+ }
+ else
+ rundisc(rcx);
+
+ /*
+ * No unique default; ask the player for a direct
+ * object, and try the command again if he is kind
+ * enough to provide one.
+ */
+ askflags = ERR_RUNASKD;
+ }
+ }
+ else if (voclistlen(iolist) == 0)
+ {
+ /* direct object(s), but no indirect object -- find doAction */
+ if (voctplfnd(ctx, verb, MCMONINV, tpl, &newstyle))
+ {
+ /* disambiguate the direct object list, now that we can */
+ if (vocdisambig(ctx, dolist1, dolist, PRP_DODEFAULT,
+ PRP_VALIDDO, voctplvd(tpl), cmd, MCMONINV,
+ actor, verb, prep, cmdbuf, FALSE))
+ {
+ err = -1;
+ goto exit_error;
+ }
+ iobj = MCMONINV;
+
+ /*
+ * save the disambiguated direct object list, in case
+ * we hit an askio in the course of processing it
+ */
+ memcpy(dolist, dolist1,
+ (size_t)(voclistlen(dolist1) + 1)*sizeof(dolist[0]));
+
+ /* re-check for multi-mode */
+ if (multi_flags == 0)
+ multi_flags = check_for_multi(dolist1);
+
+ /* save it/them/him/her, and execute the command */
+ exesaveit(ctx, dolist1);
+ if ((err = exeloop(ctx, actor, verb, dolist1, &prep,
+ (vocoldef *)0, multi_flags,
+ tpl, newstyle)) != 0)
+ goto exit_error;
+ }
+ else
+ {
+ /* no doAction - we'll need to find an indirect object */
+ runrst(rcx);
+ runppr(rcx, verb, PRP_PREPDEFAULT, 0);
+ if (runtostyp(rcx) != DAT_OBJECT)
+ {
+ /* discard the result */
+ rundisc(rcx);
+
+ /* call parseUnknownVerb to handle it */
+ if (try_unknown_verb(ctx, actor, cmd, typelist,
+ wrdcnt, next_word, TRUE, VOCERR(24),
+ "I don't recognize that sentence."))
+ {
+ /* handled - end the command successfully */
+ err = 0;
+ }
+ else
+ {
+ /* not handled - indicate failure */
+ err = -1;
+ }
+ goto exit_error;
+ }
+ prep = runpopobj(rcx);
+
+ runrst(rcx);
+ runpobj(rcx, prep);
+ runpobj(rcx, actor);
+ runppr(rcx, verb, PRP_IODEFAULT, 2);
+
+ if (runtostyp(rcx) == DAT_LIST)
+ {
+ uchar *l = runpoplst(rcx);
+ uint lstsiz;
+ objnum defobj;
+ int objcnt;
+ runsdef val;
+ objnum o;
+
+ /* push list back on stack, to keep in heap */
+ val.runsv.runsvstr = l;
+ val.runstyp = DAT_LIST;
+ runrepush(rcx, &val);
+
+ /* get list size out of list */
+ lstsiz = osrp2(l) - 2;
+ l += 2;
+
+ if (!voctplfnd(ctx, verb, prep, tpl, &newstyle))
+ {
+ for (objcnt = 0 ; lstsiz && objcnt < 2
+ ; lstadv(&l, &lstsiz))
+ {
+ if (*l == DAT_OBJECT)
+ {
+ objcnt++;
+ defobj = osrp2(l + 1);
+ }
+ }
+ }
+ else
+ {
+ int dobj_first;
+
+ /*
+ * Get the template flags. If we must
+ * disambiguate the direct object first for this
+ * verb, do so now.
+ */
+ tplflags = (newstyle ? voctplflg(tpl) : 0);
+ dobj_first = (tplflags & VOCTPLFLG_DOBJ_FIRST);
+ if (dobj_first)
+ {
+ if (vocdisambig(ctx, dolist1, dolist,
+ PRP_DODEFAULT, PRP_VALIDDO,
+ voctplvd(tpl), cmd, MCMONINV,
+ actor, verb, prep, cmdbuf,
+ FALSE))
+ {
+ err = -1;
+ goto exit_error;
+ }
+
+ /* only one direct object is allowed here */
+ if (voclistlen(dolist1) > 1)
+ {
+ vocerr(ctx, VOCERR(28),
+ "You can't use multiple objects with this command.");
+ err = -1;
+ goto exit_error;
+ }
+
+ /* save the object in the original list */
+ memcpy(dolist, dolist1,
+ (size_t)(2 * sizeof(dolist[0])));
+ }
+
+ for (objcnt = 0 ; lstsiz && objcnt < 2
+ ; lstadv(&l, &lstsiz))
+ {
+ if (*l == DAT_OBJECT)
+ {
+ o = osrp2(l + 1);
+ if (!objgetap(ctx->voccxmem, o, voctplvi(tpl),
+ (objnum *)0, FALSE))
+ continue;
+
+ tiohide(ctx->voccxtio);
+ if (dobj_first)
+ runpobj(rcx, dolist[0].vocolobj);
+ runpobj(rcx, actor);
+ runppr(rcx, o, voctplvi(tpl),
+ (dobj_first ? 2 : 1));
+ if (!tioshow(ctx->voccxtio))
+ {
+ objcnt++;
+ defobj = o;
+ }
+ }
+ }
+ }
+
+ /* no longer need list in heap, so discard it */
+ rundisc(rcx);
+
+ /* if there's exactly one default object, use it */
+ if (objcnt == 1)
+ {
+ iolist[0].vocolobj = defobj;
+ iolist[0].vocolflg = 0;
+ iolist[0].vocolfst = iolist[0].vocollst = 0;
+ iolist[1].vocolobj = MCMONINV;
+ iolist[1].vocolflg = 0;
+ iolist[1].vocolfst = iolist[1].vocollst = 0;
+
+ /* tell the user what we're assuming */
+ runrst(rcx);
+ if (ctx->voccxpdef2 != MCMONINV)
+ {
+ runpobj(rcx, prep);
+ runpobj(rcx, defobj);
+ runpobj(rcx, verb);
+ runpobj(rcx, actor);
+ runfn(rcx, ctx->voccxpdef2, 4);
+ }
+ else if (ctx->voccxpdef != MCMONINV)
+ {
+ runpobj(rcx, prep);
+ runpobj(rcx, defobj);
+ runfn(rcx, ctx->voccxpdef, 2);
+ }
+ else
+ {
+ vocerr_info(ctx, VOCERR(130), "(");
+ runppr(rcx, prep, PRP_SDESC, 0);
+ vocerr_info(ctx, VOCERR(132), " ");
+ runppr(rcx, defobj, PRP_THEDESC, 0);
+ vocerr_info(ctx, VOCERR(131), ")");
+ }
+ tioflush(ctx->voccxtio);
+ err = -2; /* "continue" */
+ goto exit_error;
+ }
+ }
+ else
+ rundisc(rcx);
+
+ /*
+ * We didn't get a unique default indirect object, so
+ * we should ask the player for an indirct object, and
+ * repeat the command should he provide one.
+ */
+ askflags = ERR_RUNASKI;
+ }
+ }
+ else
+ {
+ objnum otherobj;
+
+ /* find the template for this verb/prep combination */
+ if (!voctplfnd(ctx, verb, prep, tpl, &newstyle))
+ {
+ vocoldef *np1;
+
+ /*
+ * If we could have used the preposition in the first noun
+ * phrase rather than in the verb, and this would have
+ * yielded a valid verb phrase, the error is "I don't see
+ * any <noun phrase> here".
+ *
+ * Otherwise, it's a verb phrasing error. In this case,
+ * call parseUnknownVerb to handle the error; the default
+ * error is "I don't recognize that sentence".
+ */
+ np1 = dolist[0].vocolfst < iolist[0].vocolfst
+ ? dolist : iolist;
+ if ((np1->vocolflg & VOCS_TRIMPREP) != 0)
+ {
+ char namebuf[VOCBUFSIZ];
+
+ /*
+ * it's a trimmed prep phrase, so we actually have an
+ * unmatched object - report the error
+ */
+ voc_make_obj_name_from_list(
+ ctx, namebuf, cmd, np1->vocolfst, np1->vocolhlst);
+ vocerr(ctx, VOCERR(9), "I don't see any %s here.",
+ namebuf);
+
+ /* terminate the command with an error */
+ err = -1;
+ }
+ else if (try_unknown_verb(ctx, actor, cmd, typelist,
+ wrdcnt, next_word, TRUE,
+ VOCERR(24),
+ "I don't recognize that sentence."))
+ {
+ /* they handled it - terminate command successfully */
+ err = 0;
+ }
+ else
+ {
+ /* that failed - terminate the command with an error */
+ err = -1;
+ }
+
+ /* terminate the command */
+ goto exit_error;
+ }
+
+ /*
+ * We have both direct and indirect objects. If we don't
+ * yet have the direct object, go ask for it
+ */
+ if (voclistlen(dolist) == 0)
+ {
+ askflags = ERR_RUNASKD;
+ goto exit_error;
+ }
+
+ /* get the flags (if old-style, flags are always zero) */
+ tplflags = (newstyle ? voctplflg(tpl) : 0);
+
+ /*
+ * the "other" object (dobj if doing iobj, iobj if doing
+ * dobj) is not known when the first object is disambiguated
+ */
+ otherobj = MCMONINV;
+
+ /* disambiguate the objects in the proper order */
+ if (tplflags & VOCTPLFLG_DOBJ_FIRST)
+ {
+ /* disambiguate the direct object list */
+ if (vocdisambig(ctx, dolist1, dolist, PRP_DODEFAULT,
+ PRP_VALIDDO, voctplvd(tpl), cmd, otherobj,
+ actor, verb, prep, cmdbuf, FALSE))
+ {
+ err = -1;
+ goto exit_error;
+ }
+
+ /*
+ * only one direct object is allowed if it's
+ * disambiguated first
+ */
+ if (voclistlen(dolist1) > 1)
+ {
+ vocerr(ctx, VOCERR(28),
+ "You can't use multiple objects with this command.");
+ err = -1;
+ goto exit_error;
+ }
+
+ /* the other object is now known for iboj disambiguation */
+ otherobj = dolist1[0].vocolobj;
+ }
+
+ /* disambiguate the indirect object list */
+ if (vocdisambig(ctx, iolist1, iolist, PRP_IODEFAULT,
+ PRP_VALIDIO, voctplvi(tpl), cmd, otherobj,
+ actor, verb, prep, cmdbuf, FALSE))
+ {
+ err = -1;
+ goto exit_error;
+ }
+
+ /* only one indirect object is allowed */
+ if (voclistlen(iolist1) > 1)
+ {
+ vocerr(ctx, VOCERR(25),
+ "You can't use multiple indirect objects.");
+ err = -1;
+ goto exit_error;
+ }
+ otherobj = iobj = iolist1[0].vocolobj;
+
+ /*
+ * disambiguate the direct object list if we haven't
+ * already done so (we might have disambiguated it first due
+ * to the DisambigDobjFirst flag being set in the template)
+ */
+ if (!(tplflags & VOCTPLFLG_DOBJ_FIRST)
+ && vocdisambig(ctx, dolist1, dolist, PRP_DODEFAULT,
+ PRP_VALIDDO, voctplvd(tpl), cmd, otherobj,
+ actor, verb, prep, cmdbuf, FALSE))
+ {
+ err = -1;
+ goto exit_error;
+ }
+
+ /* re-check for multi-mode */
+ if (multi_flags == 0)
+ multi_flags = check_for_multi(dolist1);
+
+ /* save it/them/him/her, and execute the command */
+ exesaveit(ctx, dolist1);
+ if ((err = exeloop(ctx, actor, verb, dolist1, &prep, iolist1,
+ multi_flags, tpl, newstyle)) != 0)
+ goto exit_error;
+ }
+
+ exit_error: ;
+
+ ERRCATCH(ctx->voccxerr, err)
+ if (err == ERR_RUNASKI) prep = errargint(0);
+ if (err != ERR_RUNASKD && err != ERR_RUNASKI)
+ errrse(ctx->voccxerr);
+ ERREND(ctx->voccxerr)
+
+ switch(err)
+ {
+ case 0:
+ break;
+
+ case ERR_RUNABRT:
+ /* "abort" executed - return the ABORT code */
+ VOC_RETVAL(ctx, save_sp, err);
+
+ case ERR_RUNEXIT:
+ /*
+ * "exit" executed - terminate the command, but return
+ * success, since we want to process any additional commands
+ */
+ VOC_RETVAL(ctx, save_sp, 0);
+
+ case ERR_RUNEXITOBJ:
+ /*
+ * "exitobj" executed - indicate success, since this merely
+ * indicates that the game decided it was done processing an
+ * object early
+ */
+ VOC_RETVAL(ctx, save_sp, 0);
+
+ case ERR_RUNASKI:
+ case ERR_RUNASKD:
+ askflags = err;
+ break;
+
+ case -2: /* special code: continue with main loop */
+ continue;
+
+ case -1: /* special code: return an error */
+ default:
+ VOC_RETVAL(ctx, save_sp, 1);
+ }
+
+ /*
+ * If we got this far, we probably want more information. The
+ * askflags can tell us what to do from here.
+ */
+ if (askflags)
+ {
+ int old_unknown;
+ int exenewpos;
+
+ /*
+ * if we had unknown words, don't ask for more information
+ * at this point; simply give up and report the unknown word
+ */
+ if (ctx->voccxunknown != 0)
+ {
+ VOC_RETVAL(ctx, save_sp, 1);
+ }
+
+ /* find new template indicated by the additional object */
+ foundtpl = voctplfnd(ctx, verb, prep, tpl, &newstyle);
+ tplflags = (newstyle ? voctplflg(tpl) : 0);
+
+ /* find a default object of the type requested */
+ runrst(rcx);
+ if (askflags == ERR_RUNASKD) runpnil(rcx);
+ runpobj(rcx, prep);
+ runpobj(rcx, actor);
+ runppr(rcx, verb,
+ (prpnum)(askflags == ERR_RUNASKD
+ ? PRP_DODEFAULT : PRP_IODEFAULT),
+ (askflags == ERR_RUNASKD ? 3 : 2));
+
+ /*
+ * If we got a list back from ?oDefault, and we have a new
+ * template for the command, process the list normally with
+ * the object verification routine for this template. If we
+ * end up with exactly one object, we will assume it is the
+ * object to be used; otherwise, make no assumption and ask
+ * the user for guidance.
+ */
+ if (runtostyp(rcx) == DAT_LIST && foundtpl)
+ {
+ uchar *l = runpoplst(rcx);
+ uint lstsiz;
+ int objcnt;
+ objnum defobj;
+ objnum o;
+ runsdef val;
+
+ /* push list back on stack, to keep it in the heap */
+ val.runsv.runsvstr = l;
+ val.runstyp = DAT_LIST;
+ runrepush(rcx, &val);
+
+ /* get list size out of list */
+ lstsiz = osrp2(l) - 2;
+ l += 2;
+
+ for (objcnt = 0 ; lstsiz && objcnt < 2 ; lstadv(&l, &lstsiz))
+ {
+ if (*l == DAT_OBJECT)
+ {
+ prpnum verprop;
+ int argc = 1;
+
+ o = osrp2(l + 1);
+ verprop = (askflags == ERR_RUNASKD ? voctplvd(tpl)
+ : voctplvi(tpl));
+
+ if (!objgetap(ctx->voccxmem, o, verprop,
+ (objnum *)0, FALSE))
+ continue;
+
+ tiohide(ctx->voccxtio);
+
+ /*
+ * In the unlikely event that we have an
+ * indirect object but no direct object, push
+ * the iobj. This can happen when the player
+ * types a sentence such as "verb prep iobj".
+ */
+ if (voclistlen(iolist) != 0
+ && askflags == ERR_RUNASKD
+ && !(tplflags & VOCTPLFLG_DOBJ_FIRST))
+ {
+ /* push the indirect object */
+ runpobj(rcx, iolist[0].vocolobj);
+
+ /* note the second argument */
+ argc = 2;
+ }
+
+ /*
+ * If this is a disambigDobjFirst verb, and
+ * we're validating an indirect object list,
+ * then we must push the direct object argument
+ * to the indirect object validation routine.
+ */
+ if (askflags == ERR_RUNASKI
+ && (tplflags & VOCTPLFLG_DOBJ_FIRST) != 0)
+ {
+ /* push the diret object */
+ runpobj(rcx, dolist[0].vocolobj);
+
+ /* note the second argument */
+ argc = 2;
+ }
+
+ /* push the actor and call the verXoVerb routine */
+ runpobj(rcx, actor);
+ runppr(rcx, o, verprop, argc);
+ if (!tioshow(ctx->voccxtio))
+ {
+ ++objcnt;
+ defobj = o;
+ }
+
+ }
+ }
+
+ /* no longer need list in heap, so discard it */
+ rundisc(rcx);
+
+ /* if we found exactly one object, it's the default */
+ if (objcnt == 1)
+ {
+ if (askflags == ERR_RUNASKD)
+ {
+ dolist[0].vocolobj = defobj;
+ dolist[0].vocolflg = 0;
+ dolist[0].vocolfst = dolist[0].vocollst = 0;
+ dolist[1].vocolobj = MCMONINV;
+ dolist[1].vocolflg = 0;
+ dolist[1].vocolfst = dolist[1].vocollst = 0;
+ }
+ else
+ {
+ iolist[0].vocolobj = defobj;
+ iolist[0].vocolflg = 0;
+ iolist[0].vocolfst = iolist[0].vocollst = 0;
+ iolist[1].vocolobj = MCMONINV;
+ iolist[1].vocolflg = 0;
+ iolist[1].vocolfst = iolist[1].vocollst = 0;
+ }
+
+ /* tell the user what we're assuming */
+ if (ctx->voccxpdef2 != MCMONINV)
+ {
+ if (askflags == ERR_RUNASKI)
+ runpobj(rcx, prep);
+ else
+ runpnil(rcx);
+ runpobj(rcx, defobj);
+ runpobj(rcx, verb);
+ runpobj(rcx, actor);
+ runfn(rcx, ctx->voccxpdef2, 4);
+ }
+ else if (ctx->voccxpdef != MCMONINV)
+ {
+ if (askflags == ERR_RUNASKI)
+ runpobj(rcx, prep);
+ else
+ runpnil(rcx);
+ runpobj(rcx, defobj);
+ runfn(rcx, ctx->voccxpdef, 2);
+ }
+ else
+ {
+ vocerr_info(ctx, VOCERR(130), "(");
+ if (askflags == ERR_RUNASKI)
+ {
+ runppr(rcx, prep, PRP_SDESC, 0);
+ vocerr_info(ctx, VOCERR(132), " ");
+ }
+ runppr(rcx, defobj, PRP_THEDESC, 0);
+ vocerr_info(ctx, VOCERR(131), ")");
+ }
+ tioflush(ctx->voccxtio);
+ continue; /* try the command again */
+ }
+ }
+ else
+ rundisc(rcx);
+
+ /* make sure output capturing is off for the prompt */
+ tiocapture(ctx->voccxtio, (mcmcxdef *)0, FALSE);
+ tioclrcapture(ctx->voccxtio);
+
+ /*
+ * If we're asking for an indirect object, and we have a
+ * list of direct objects, and parseAskobjIndirect is
+ * defined, call it. Otherwise, if there's a
+ * parseAskobjActor routine, call it. Otherwise, if there's
+ * a parseAskobj routine, use that. Finally, if none of
+ * those are defined, generate the default phrasing.
+ */
+ if (ctx->voccxpask3 != MCMONINV
+ && askflags == ERR_RUNASKI
+ && voclistlen(dolist) != 0)
+ {
+ /* call parseAskobjIndirect */
+ voc_askobj_indirect(ctx, dolist, actor, verb, prep);
+ }
+ else if (ctx->voccxpask2 != MCMONINV)
+ {
+ if (askflags == ERR_RUNASKI)
+ runpobj(ctx->voccxrun, prep);
+ runpobj(ctx->voccxrun, verb);
+ runpobj(ctx->voccxrun,
+ (objnum)(actor == MCMONINV ? ctx->voccxme : actor));
+ runfn(ctx->voccxrun, ctx->voccxpask2,
+ askflags == ERR_RUNASKI ? 3 : 2);
+ }
+ else if (ctx->voccxpask != MCMONINV)
+ {
+ if (askflags == ERR_RUNASKI)
+ runpobj(ctx->voccxrun, prep);
+ runpobj(ctx->voccxrun, verb);
+ runfn(ctx->voccxrun, ctx->voccxpask,
+ askflags == ERR_RUNASKI ? 2 : 1);
+ }
+ else
+ {
+ /*
+ * Phrase the question: askDo: "What do you want
+ * <actor> to <verb>?" askIo: "What do you want <actor>
+ * to <verb> it <prep>?" If the actor is Me, leave the
+ * actor out of it.
+ */
+ if (actor != MCMONINV && actor != ctx->voccxme)
+ {
+ vocerr_info(ctx, VOCERR(148), "What do you want ");
+ runppr(rcx, actor, PRP_THEDESC, 0);
+ vocerr_info(ctx, VOCERR(149), " to ");
+ }
+ else
+ {
+ /* no actor - don't mention one */
+ vocerr_info(ctx, VOCERR(140), "What do you want to ");
+ }
+
+ /* add the verb */
+ runppr(rcx, verb, PRP_SDESC, 0);
+
+ /*
+ * add an appropriate pronoun for the direct object,
+ * and the preposition, if we're asking for an indirect
+ * object
+ */
+ if (askflags == ERR_RUNASKI)
+ {
+ int i;
+ int vcnt;
+ int distinct;
+ char *lastfst;
+
+ /*
+ * If possible, tailor the pronoun to the situation
+ * rather than using "it"; if we have multiple
+ * objects, use "them", and if we have agreement
+ * with the possible single objects about "him" or
+ * "her", use that. Otherwise, use "it". If "all"
+ * was specified for any word, automatically assume
+ * multiple distinct objects were specified.
+ */
+ vcnt = voclistlen(dolist);
+ for (distinct = 0, i = 0, lastfst = 0 ; i < vcnt ; ++i)
+ {
+ /* if the first word is different here, note it */
+ if (lastfst != dolist[i].vocolfst)
+ {
+ /* this is a different word - count it */
+ ++distinct;
+ lastfst = dolist[i].vocolfst;
+ }
+
+ /* always assume multiple distinct objects on "all" */
+ if (dolist[i].vocolflg & VOCS_ALL)
+ {
+ distinct = 2;
+ break;
+ }
+ }
+
+ /*
+ * If we have multiple words, use "them";
+ * otherwise, see if we can find agreement about
+ * using "him" or "her".
+ */
+ if (distinct > 1)
+ {
+ /* multiple words specified by user - use "them" */
+ vocerr_info(ctx, VOCERR(144), " them ");
+ }
+ else
+ {
+ int is_him;
+ int is_her;
+ int is_them;
+
+ /* run through the objects and check him/her */
+ for (i = 0 ; i < vcnt ; ++i)
+ {
+ int him1, her1, them1;
+
+ /* if it's special (number, string), use "it" */
+ if (dolist[i].vocolobj == MCMONINV)
+ {
+ him1 = FALSE;
+ her1 = FALSE;
+ them1 = FALSE;
+ }
+ else
+ {
+ /* check for "him" */
+ runppr(rcx, dolist[i].vocolobj, PRP_ISHIM, 0);
+ him1 = (runtostyp(rcx) == DAT_TRUE);
+ rundisc(rcx);
+
+ /* check for "her" */
+ runppr(rcx, dolist[i].vocolobj, PRP_ISHER, 0);
+ her1 = (runtostyp(rcx) == DAT_TRUE);
+ rundisc(rcx);
+
+ /* check for "them" */
+ runppr(rcx, dolist[i].vocolobj,
+ PRP_ISTHEM, 0);
+ them1 = (runtostyp(rcx) == DAT_TRUE);
+ rundisc(rcx);
+ }
+
+ /*
+ * if this is the first object, it
+ * definitely agrees; otherwise, keep going
+ * only if it agrees with what we found on
+ * the last pass
+ */
+ if (i == 0)
+ {
+ is_him = him1;
+ is_her = her1;
+ is_them = them1;
+ }
+ else
+ {
+ /* turn off either that is no longer true */
+ if (!him1) is_him = FALSE;
+ if (!her1) is_her = FALSE;
+ if (!them1) is_them = FALSE;
+ }
+
+ /* if all are false, stop now */
+ if (!is_him && !is_her && !is_them)
+ break;
+ }
+
+ /*
+ * If we could agree on "him", "her", or "them",
+ * use that pronoun; otherwise, use "it". If we
+ * found both "him" and "her" are acceptable for
+ * all objects, use "them".
+ */
+ if ((is_him && is_her) || is_them)
+ vocerr_info(ctx, VOCERR(147), " them ");
+ else if (is_him)
+ vocerr_info(ctx, VOCERR(145), " him ");
+ else if (is_her)
+ vocerr_info(ctx, VOCERR(146), " her ");
+ else
+ vocerr_info(ctx, VOCERR(141), " it ");
+ }
+
+ /* finish off the question with the prep and a "?" */
+ if (prep != MCMONINV)
+ runppr(rcx, prep, PRP_SDESC, 0);
+ else
+ vocerr_info(ctx, VOCERR(142), "to");
+ }
+ vocerr_info(ctx, VOCERR(143), "?");
+ }
+ tioflush(ctx->voccxtio);
+
+ /*
+ * Get a new command line. If the player gives us
+ * something that looks like a noun list, and nothing more,
+ * he anwered our question; otherwise, he's typing a new
+ * command, so we must return to the caller with the reparse
+ * flag set.
+ */
+ if (askflags == ERR_RUNASKD)
+ {
+ exenewbuf = donewbuf;
+ exenewcmd = donewcmd;
+ exenewlist = donewlist;
+ exenewtype = donewtype;
+ }
+ else
+ {
+ exenewbuf = ionewbuf;
+ exenewcmd = ionewcmd;
+ exenewlist = ionewlist;
+ exenewtype = ionewtype;
+ }
+
+ /* read the new command */
+ if (vocread(ctx, actor, verb, exenewcmd, VOCBUFSIZ,
+ askflags == ERR_RUNASKD ? 3 : 4) == VOCREAD_REDO)
+ {
+ /*
+ * we got an input line, but we want to treat it as a brand
+ * new command line - copy the new text to the command
+ * buffer, set the 'redo' flag, and give up
+ */
+ strcpy(cmdbuf, exenewcmd);
+ ctx->voccxredo = TRUE;
+ VOC_RETVAL(ctx, save_sp, 1);
+ }
+
+ if (!(cnt = voctok(ctx, exenewcmd, exenewbuf, exenewlist,
+ TRUE, FALSE, TRUE)))
+ {
+ runrst(rcx);
+ runfn(rcx, ctx->voccxprd, 0);
+ VOC_RETVAL(ctx, save_sp, 1);
+ }
+ if (cnt < 0)
+ {
+ ctx->voccxunknown = 0;
+ VOC_RETVAL(ctx, save_sp, 1);
+ }
+
+ /*
+ * Save the unknown word count while getting types, and set
+ * the count to a non-zero value - this will force the type
+ * checker to generate an error on an unknown word. This
+ * removes a little control from the game (since
+ * parseUnknownXobj won't be called), but there's not much
+ * else we can do here.
+ */
+ old_unknown = ctx->voccxunknown;
+ ctx->voccxunknown = 1;
+
+ /* get the types */
+ exenewlist[cnt] = 0;
+ if (vocgtyp(ctx, exenewlist, exenewtype, cmdbuf))
+ {
+ /*
+ * clear the unknown word count so that we fail with
+ * this error rather than trying to deal with unknown
+ * words
+ */
+ ctx->voccxunknown = 0;
+
+ /* return failure */
+ VOC_RETVAL(ctx, save_sp, 1);
+ }
+
+ /* restore the unknown word count */
+ ctx->voccxunknown = old_unknown;
+
+ /* start at the first word */
+ exenewpos = 0;
+
+ /*
+ * if we're asking for an indirect object, and the first
+ * word is a preposition, and matches the preposition that
+ * we supplied to precede the indirect object, skip the
+ * preposition
+ */
+ if (askflags == ERR_RUNASKI
+ && prep != MCMONINV
+ && (exenewtype[0] & VOCT_PREP) != 0)
+ {
+ vocwdef *vp;
+
+ /* get the preposition */
+ vp = vocffw(ctx, exenewlist[0], (int)strlen(exenewlist[0]),
+ (char *)0, 0, PRP_PREP, (vocseadef *)0);
+ if (vp != 0 && vp->vocwobj == prep)
+ ++exenewpos;
+ }
+
+ /* check for a noun */
+ newnoun = (askflags == ERR_RUNASKD ? dolist : iolist);
+ cnt = vocchknoun(ctx, exenewlist, exenewtype, exenewpos, &next,
+ newnoun, FALSE);
+
+ if (cnt < 0) { VOC_RETVAL(ctx, save_sp, 1); } /* invalid syntax */
+ if (cnt == 0
+ || (exenewlist[next] && !vocspec(exenewlist[next], VOCW_THEN)
+ && *exenewlist[next] != '\0'))
+ {
+ strcpy(cmdbuf, exenewcmd);
+ ctx->voccxredo = TRUE;
+ VOC_RETVAL(ctx, save_sp, 1);
+ }
+
+ /* re-check the 'multi' flags */
+ multi_flags = check_for_multi(newnoun);
+
+ /* give it another go by going back to the top of the loop */
+ }
+ else
+ {
+ /* normal exit flags - return success */
+ VOC_RETVAL(ctx, save_sp, 0);
+ }
+ }
+}
+
+} // End of namespace TADS2
+} // 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 d82f67abea..bef91f10d2 100644
--- a/engines/glk/tads/tads2/file_io.cpp
+++ b/engines/glk/tads/tads2/file_io.cpp
@@ -963,14 +963,14 @@ static void fiord1(mcmcxdef *mctx, voccxdef *vctx, tokcxdef *tctx,
}
/* read binary file */
-void fiord(mcmcxdef *mctx, voccxdef *vctx, tokcxdef *tctx, char *fname,
- char *exename, fiolcxdef *setupctx, objnum *preinit, uint *flagp,
+void fiord(mcmcxdef *mctx, voccxdef *vctx, tokcxdef *tctx, const char *fname,
+ const 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;
+ const char *display_fname;
/* presume there will be no need to run preinit */
*preinit = MCMONINV;
@@ -1034,7 +1034,7 @@ void fiord(mcmcxdef *mctx, voccxdef *vctx, tokcxdef *tctx, char *fname,
char suffix_lc[4];
char suffix_uc[4];
int i;
- char *base_name;
+ const char *base_name;
/* use the game or executable filename, as appropriate */
base_name = display_fname;
diff --git a/engines/glk/tads/tads2/file_io.h b/engines/glk/tads/tads2/file_io.h
index 0edfbaee6d..464a1f93e8 100644
--- a/engines/glk/tads/tads2/file_io.h
+++ b/engines/glk/tads/tads2/file_io.h
@@ -42,9 +42,7 @@ struct tokthdef;
struct tokcxdef;
/* load-on-demand context (passed in by mcm in load callback) */
-typedef struct fiolcxdef fiolcxdef;
-struct fiolcxdef
-{
+struct fiolcxdef {
osfildef *fiolcxfp; /* file pointer of load file */
errcxdef *fiolcxerr; /* error handling context */
ulong fiolcxst; /* starting offset in file */
@@ -70,8 +68,8 @@ 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(mcmcxdef *mctx, voccxdef *vctx, tokcxdef *tctx, char *fname,
- char *exename, fiolcxdef *setupctx, objnum *preinit, uint *flagp,
+void fiord(mcmcxdef *mctx, voccxdef *vctx, tokcxdef *tctx, const char *fname,
+ const char *exename, fiolcxdef *setupctx, objnum *preinit, uint *flagp,
tokpdef *path, uchar **fmtsp, uint *fmtlp, uint *pcntptr, int flags,
appctxdef *appctx, char *argv0);
diff --git a/engines/glk/tads/tads2/lib.h b/engines/glk/tads/tads2/lib.h
index a7ed2aadf7..596a66d57d 100644
--- a/engines/glk/tads/tads2/lib.h
+++ b/engines/glk/tads/tads2/lib.h
@@ -46,9 +46,6 @@ 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
diff --git a/engines/glk/tads/tads2/ltk.cpp b/engines/glk/tads/tads2/ltk.cpp
new file mode 100644
index 0000000000..b1128394b2
--- /dev/null
+++ b/engines/glk/tads/tads2/ltk.cpp
@@ -0,0 +1,197 @@
+/* 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/ltk.h"
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/error_handling.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+/*--------------------------------- ltkini ---------------------------------*/
+/*
+ * ltkini - allocate and INItialize toolkit context.
+ */
+void ltkini(unsigned short heapsiz) {
+}
+
+
+/*--------------------------------- ltkfre ---------------------------------*/
+/*
+ * ltkfre - FREe toolkit context.
+ */
+void ltkfre() {
+}
+
+
+/*------------------------------ ltk_suballoc ------------------------------*/
+/*
+ * ltk_suballoc - SUB ALLOCate memory from heap segment.
+ */
+void *ltk_suballoc(size_t siz) {
+ return ltk_alloc(siz);
+}
+
+
+/*---------------------------- ltk_sigsuballoc -----------------------------*/
+/*
+ * ltk_sigsuballoc - allocate from heap, signal failure.
+ */
+void *ltk_sigsuballoc(errcxdef *errcx, size_t siz) {
+ void *ptr; /* ptr to allocated memory */
+
+ /* allocate the memory */
+ if (!(ptr = ltk_suballoc(siz)))
+ {
+ /* signal an error */
+ errsigf(errcx, "LTK", 0);
+ }
+
+ /* return the memory */
+ return(ptr);
+}
+
+
+/*------------------------------ ltk_subfree -------------------------------*/
+/*
+ * ltk_subfree - FREe memory allocated by ltk_suballoc
+ */
+void ltk_subfree(void *ptr) {
+ free(ptr);
+}
+
+
+/*------------------------------- ltk_alloc --------------------------------*/
+/*
+ * ltk_alloc - Allocate a block of memory
+ */
+void *ltk_alloc(size_t siz) {
+ byte *data = (byte *)malloc(siz);
+ Common::fill(data, data + siz, 0);
+ return data;
+}
+
+/*------------------------------ ltk_realloc ------------------------------*/
+
+void *ltk_realloc(void *ptr, size_t siz) {
+ return realloc(ptr, siz);
+}
+
+/*------------------------------ ltk_sigalloc ------------------------------*/
+/*
+ * ltk_sigalloc - allocate permanent global memory, and signal on failure.
+ */
+void *ltk_sigalloc(errcxdef *errcx, size_t siz) {
+ void *ptr; /* pointer to allocated memory */
+
+ if (!(ptr = ltk_alloc(siz))) {
+ /* signal error */
+ errsigf(errcx, "LTK", 0);
+ }
+
+ /* return a ptr to the allocated memory */
+ return ptr;
+}
+
+
+/*-------------------------------- ltk_free --------------------------------*/
+/*
+ * ltk_free - free a block of memory allocated by ltk_alloc. This
+ * takes the memory handle stashed just behind the allocation, and frees
+ * the block of memory.
+ */
+void ltk_free(void *mem) {
+ free(mem);
+}
+
+/*------------------------------- ltk_errlog -------------------------------*/
+/*
+* 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) {
+ char buf[128]; /* formatted error buffer */
+ char msg[128]; /* message buffer */
+
+ /* $$$ filter out error #504 $$$ */
+ if (errno == 504) return;
+
+ /* get the error message into msg */
+ errmsg((errcxdef *)ctx, msg, sizeof(msg), errno);
+
+ /* format the error message */
+ errfmt(buf, (int)sizeof(buf), msg, argc, argv);
+
+ /* display a dialog box containing the error message */
+ ltk_dlg("Error", buf);
+}
+
+
+/*-------------------------------- ltk_dlg ---------------------------------*/
+/*
+* ltk_dlg - DiaLog. Puts the given message in a dialog box.
+*/
+void ltk_dlg(char *title, char *msg, ...) {
+ va_list argp; /* printf args */
+ char inbuf[80]; /* input buffer */
+ char outbuf[160]; /* allow inbuf to double in size */
+
+ /* clip the input message, if necessary */
+ strncpy(inbuf, msg, sizeof(inbuf));
+ inbuf[sizeof(inbuf) - 1] = '\0';
+
+ /* get the printf args, build the message, and display it */
+ va_start(argp, msg);
+ vsprintf(outbuf, inbuf, argp);
+
+ /* display the message */
+ error("%s", outbuf);
+}
+
+
+/*-------------------------------- ltk_beep --------------------------------*/
+/*
+* ltk_beep - BEEP the PC's speaker.
+*/
+void ltk_beep() {
+}
+
+/*------------------------------ ltk_beg_wait ------------------------------*/
+/*
+* ltk_beg_wait - Put up hourglass prompt.
+*/
+void ltk_beg_wait() {
+}
+
+
+/*------------------------------ ltk_end_wait ------------------------------*/
+/*
+* ltk_end_wait - Put up normal prompt.
+*/
+void ltk_end_wait() {
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/ltk.h b/engines/glk/tads/tads2/ltk.h
new file mode 100644
index 0000000000..2fbe7a853b
--- /dev/null
+++ b/engines/glk/tads/tads2/ltk.h
@@ -0,0 +1,133 @@
+/* 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 porting Tool Kit
+ *
+ * These are generic definitions which should be applicable to any system.
+ */
+
+#ifndef GLK_TADS_TADS2_LTK
+#define GLK_TADS_TADS2_LTK
+
+#include "glk/tads/tads2/error_handling.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+/*
+ * ltkini - allocate and INItialize ltk context. 'heapsiz' is the
+ * requested size for the local heap. Returns 0 if the request cannot be
+ * satisfied.
+ */
+extern void ltkini(unsigned short heapsiz);
+
+
+/*
+ * ltkfre - FREe ltk context.
+ */
+extern void ltkfre();
+
+
+/*
+ * ltk_dlg - DiaLoG. Present user with informational dialog message.
+ * 'title' specifies the title to use in the dialog box, 'msg' is the
+ * text message, which may contain printf-style formatting.
+ * printf-style arguments must be passed in also, if the message
+ * requires them.
+ */
+extern void ltk_dlg(char *title, char *msg, ...);
+
+
+/*
+ * ltk_errlog - Error logging function for LER routines.
+ */
+extern void ltk_errlog(void *ctx, char *fac, int errCode, int agrc, erradef *argv);
+
+
+/*
+ * ltk_alloc - ALLOCate permanent global memory. Returns 0 if the
+ * request cannot be satisfied.
+ */
+extern void *ltk_alloc(size_t siz);
+
+/* ltk_realloc - reallocate memory; analogous to realloc() */
+extern void *ltk_realloc(void *ptr, size_t siz);
+
+
+/*
+ * ltk_sigalloc - ALLOCate permanent global memory, signals error on
+ * failure.
+ */
+extern void *ltk_sigalloc(struct errcxdef *errcx, size_t siz);
+
+
+/*
+ * ltk_free - FREE memory allocated using ltk_alloc.
+ */
+extern void ltk_free(void *ptr);
+
+
+/*
+ * ltk_suballoc - SUB-ALLOCate memory from user heap. Returns 0 if the
+ * request cannot be satisfied.
+ */
+extern void *ltk_suballoc(size_t siz);
+
+
+/*
+ * ltk_sigsuballoc - SUB-ALLOCate memory from user heap, signals error
+ * on failure.
+ */
+extern void *ltk_sigsuballoc(struct errcxdef *errcx, size_t siz);
+
+
+/*
+ * ltk_subfree - SUBsegment FREE. Frees memory allocated by
+ * ltk_suballoc.
+ */
+extern void ltk_subfree(void *ptr);
+
+
+/*
+ * ltk_beep - BEEP the user.
+ */
+extern void ltk_beep(void);
+
+
+/*
+ * ltk_beg_wait - signal that the user needs to wait.
+ */
+extern void ltk_beg_wait(void);
+
+
+/*
+ * ltk_end_wait - end the waiting period .
+ */
+extern void ltk_end_wait(void);
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/memory_cache.h b/engines/glk/tads/tads2/memory_cache.h
index ec0383b7e1..26159aebad 100644
--- a/engines/glk/tads/tads2/memory_cache.h
+++ b/engines/glk/tads/tads2/memory_cache.h
@@ -30,7 +30,7 @@
#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"
+#include "glk/tads/os_frob_tads.h"
namespace Glk {
namespace TADS {
diff --git a/engines/glk/tads/tads2/memory_cache_swap.h b/engines/glk/tads/tads2/memory_cache_swap.h
index f1b9849aec..5a7ac7da4f 100644
--- a/engines/glk/tads/tads2/memory_cache_swap.h
+++ b/engines/glk/tads/tads2/memory_cache_swap.h
@@ -33,7 +33,7 @@
#include "glk/tads/tads2/lib.h"
#include "glk/tads/tads2/error_handling.h"
-#include "glk/tads/osfrobtads.h"
+#include "glk/tads/os_frob_tads.h"
namespace Glk {
namespace TADS {
diff --git a/engines/glk/tads/tads2/os.cpp b/engines/glk/tads/tads2/os.cpp
index 219bd3ba6c..ec94b68161 100644
--- a/engines/glk/tads/tads2/os.cpp
+++ b/engines/glk/tads/tads2/os.cpp
@@ -27,6 +27,15 @@ namespace Glk {
namespace TADS {
namespace TADS2 {
+osfildef *oserrop(const char *arg0) {
+ char buf[128];
+
+ if (!os_locate("tadserr.msg", 11, arg0, buf, sizeof(buf)))
+ return((osfildef *)0);
+
+ return(osfoprb(buf, OSFTERRS));
+}
+
long os_get_sys_clock_ms() {
return g_system->getMillis();
}
diff --git a/engines/glk/tads/tads2/os.h b/engines/glk/tads/tads2/os.h
index 666a5cb809..46c89bf4da 100644
--- a/engines/glk/tads/tads2/os.h
+++ b/engines/glk/tads/tads2/os.h
@@ -30,7 +30,8 @@
#define GLK_TADS_TADS2_OS
#include "common/system.h"
-#include "glk/tads/osfrobtads.h"
+#include "glk/tads/os_frob_tads.h"
+#include "glk/tads/os_glk.h"
#include "glk/tads/tads2/lib.h"
#include "glk/tads/tads2/appctx.h"
@@ -266,3463 +267,6 @@ namespace TADS2 {
#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
@@ -3735,1308 +279,13 @@ int os0main(int oargc, char **oargv,
* new-style OS main entrypoint - takes an application container context
*/
int os0main2(int oargc, char **oargv,
- int (*mainfn)(int, char **, struct appctxdef *, char *),
+ int (*mainfn)(int, char **, appctxdef *, char *),
const char *before, const char *config,
- struct appctxdef *appctx);
+ appctxdef *appctx);
-/*
- * OBSOLETE - Get filename from startup parameter, if possible; returns
- * true and fills in the buffer with the parameter filename on success,
- * false if no parameter file could be found.
- *
- * (This was used until TADS 2.2.5 for the benefit of the Mac interpreter,
- * and interpreters on systems with similar desktop shells, to allow the
- * user to launch the terp by double-clicking on a saved game file. The
- * terp would read the launch parameters, discover that a saved game had
- * been used to invoke it, and would then stash away the saved game info
- * for later retrieval from this function. This functionality was replaced
- * in 2.2.5 with a command-line parameter: the terp now uses the desktop
- * launch data to synthesize a suitable argv[] vectro to pass to os0main()
- * or os0main2(). This function should now simply be stubbed out - it
- * should simply return FALSE.)
- */
-int os_paramfile(char *buf);
-
-/*
- * Initialize. This should be called during program startup to
- * initialize the OS layer and check OS-specific command-line arguments.
- *
- * If 'prompt' and 'buf' are non-null, and there are no arguments on the
- * given command line, the OS code can use the prompt to ask the user to
- * supply a filename, then store the filename in 'buf' and set up
- * argc/argv to give a one-argument command string. (This mechanism for
- * prompting for a filename is obsolescent, and is retained for
- * compatibility with a small number of existing implementations only;
- * new implementations should ignore this mechanism and leave the
- * argc/argv values unchanged.)
- */
-int os_init(int *argc, char *argv[], const char *prompt,
- char *buf, int bufsiz);
-/*
- * Termination functions. There are three main termination functions,
- * described individually below; here's a brief overview of the
- * relationship among the functions. The important thing to realize is
- * that these functions have completely independent purposes; they should
- * never call one another, and they should never do any of the work that's
- * intended for the others.
- *
- * os_uninit() is meant to undo the effects of os_init(). On many
- * systems, os_init() has some global effect, such as setting the terminal
- * to some special input or output mode. os_uninit's purpose is to undo
- * these global effects, returning the terminal mode (and whatever else)
- * to the conditions they were in at program startup. Portable callers
- * are meant to call this routine at some point before terminating if they
- * ever called os_init(). Note that this routine DOES NOT terminate the
- * program - it should simply undo anything that os_init() did and return,
- * to let the caller do any additional termination work of its own.
- *
- * os_expause() optionally pauses before termination, to allow the user to
- * acknowledge any text the program displays just before exiting. This
- * doesn't have to do anything at all, but it's useful on systems where
- * program termination will do something drastic like close the window:
- * without a pause, the user wouldn't have a chance to read any text the
- * program displayed after the last interactive input, because the window
- * would abruptly disappear moments after the text was displayed. For
- * systems where termination doesn't have such drastic effects, there's no
- * need to do anything in this routine. Because it's up to this routine
- * whether or not to pause, this routine must display a prompt if it's
- * going to pause for user input - the portable caller obviously can't do
- * so, because the caller doesn't know if the routine is going to pause or
- * not (so if the caller displayed a prompt assuming the routine would
- * pause, the prompt would show up even on systems where there actually is
- * no pause, which would be confusing). This routine DOES NOT terminate
- * the program; it simply pauses if necessary to allow the user to
- * acknowledge the last bit of text the program displayed, then returns to
- * allow the caller to carry on with its own termination work.
- *
- * os_term() is meant to perform the same function as the C standard
- * library routine exit(): this actually terminates the program, exiting
- * to the operating system. This routine is not meant to return to its
- * caller, because it's supposed to exit the program directly. For many
- * systems, this routine can simply call exit(); the portable code calls
- * this routine instead of calling exit() directly, because some systems
- * have their own OS-specific way of terminating rather than using exit().
- * This routine MUST NOT undo the effects of os_init(), because callers
- * might not have ever called os_init(); callers are required to call
- * os_uninit() if they ever called os_init(), before calling os_term(), so
- * this routine can simply assume that any global modes set by os_init()
- * have already been undone by the time this is called.
- */
-
-/*
- * Uninitialize. This is called prior to progam termination to reverse
- * the effect of any changes made in os_init(). For example, if
- * os_init() put the terminal in raw mode, this should restore the
- * previous terminal mode. This routine should not terminate the
- * program (so don't call exit() here) - the caller might have more
- * processing to perform after this routine returns.
- */
-void os_uninit(void);
-
-/*
- * Pause prior to exit, if desired. This is meant to be called by
- * portable code just before the program is to be terminated; it can be
- * implemented to show a prompt and wait for user acknowledgment before
- * proceeding. This is useful for implementations that are using
- * something like a character-mode terminal window running on a graphical
- * operating system: this gives the implementation a chance to pause
- * before exiting, so that the window doesn't just disappear
- * unceremoniously.
- *
- * This is allowed to do nothing at all. For regular character-mode
- * systems, this routine usually doesn't do anything, because when the
- * program exits, the terminal will simply return to the OS command
- * prompt; none of the text displayed just before the program exited will
- * be lost, so there's no need for any interactive pause. Likewise, for
- * graphical systems where the window will remain open, even after the
- * program exits, until the user explicitly closes the window, there's no
- * need to do anything here.
- *
- * If this is implemented to pause, then this routine MUST show some kind
- * of prompt to let the user know we're waiting. In the simple case of a
- * text-mode terminal window on a graphical OS, this should simply print
- * out some prompt text ("Press a key to exit...") and then wait for the
- * user to acknowledge the prompt (by pressing a key, for example). For
- * graphical systems, the prompt could be placed in the window's title
- * bar, or status-bar, or wherever is appropriate for the OS.
- */
-void os_expause(void);
-
-/*
- * Terminate. This should exit the program with the given exit status.
- * In general, this should be equivalent to the standard C library
- * exit() function, but we define this interface to allow the OS code to
- * do any necessary pre-termination cleanup.
- */
-void os_term(int status);
-
-/*
- * Install/uninstall the break handler. If possible, the OS code should
- * set (if 'install' is true) or clear (if 'install' is false) a signal
- * handler for keyboard break signals (control-C, etc, depending on
- * local convention). The OS code should set its own handler routine,
- * which should note that a break occurred with an internal flag; the
- * portable code uses os_break() from time to time to poll this flag.
- */
-void os_instbrk(int install);
-
-/*
- * Check for user break ("control-C", etc) - returns true if a break is
- * pending, false if not. If this returns true, it should "consume" the
- * pending break (probably by simply clearing the OS code's internal
- * break-pending flag).
- */
-int os_break(void);
-
-/*
- * Sleep for a given interval. This should simply pause for the given
- * number of milliseconds, then return. On multi-tasking systems, this
- * should use a system API to suspend the current process for the desired
- * delay; on single-tasking systems, this can simply sit in a wait loop
- * until the desired interval has elapsed.
- */
-void os_sleep_ms(long delay_in_milliseconds);
-
-/*
- * Yield CPU; returns TRUE if user requested an interrupt (a "control-C"
- * type of operation to abort the entire program), FALSE to continue.
- * Portable code should call this routine from time to time during lengthy
- * computations that don't involve any UI operations; if practical, this
- * routine should be invoked roughly every 10 to 100 milliseconds.
- *
- * The purpose of this routine is to support "cooperative multitasking"
- * systems, such as pre-X MacOS, where it's necessary for each running
- * program to call the operating system explicitly in order to yield the
- * CPU from time to time to other concurrently running programs. On
- * cooperative multitasking systems, a program can only lose control of
- * the CPU by making specific system calls, usually related to GUI events;
- * a program can never lose control of the CPU asynchronously. So, a
- * program that performs lengthy computations without any UI interaction
- * can cause the rest of the system to freeze up until the computations
- * are finished; but if a compute-intensive program explicitly yields the
- * CPU from time to time, it allows other programs to remain responsive.
- * Yielding the CPU at least every 100 milliseconds or so will generally
- * allow the UI to remain responsive; yielding more frequently than every
- * 10 ms or so will probably start adding noticeable overhead.
- *
- * On single-tasking systems (such as MS-DOS), there's only one program
- * running at a time, so there's no need to yield the CPU; on virtually
- * every modern system, the OS automatically schedules CPU time without
- * the running programs having any say in the matter, so again there's no
- * need for a program to yield the CPU. For systems where this routine
- * isn't needed, the system header should simply #define os_yield to
- * something like "((void)0)" - this will allow the compiler to completely
- * ignore calls to this routine for systems where they aren't needed.
- *
- * Note that this routine is NOT meant to provide scheduling hinting to
- * modern systems with true multitasking, so a trivial implementation is
- * fine for any modern system.
- */
-#ifndef os_yield
-int os_yield(void);
-#endif
-
-/*
- * Set the default saved-game extension. This routine will NOT be called
- * when we're using the standard saved game extension; this routine will be
- * invoked only if we're running as a stand-alone game, and the game author
- * specified a non-standard saved-game extension when creating the
- * stand-alone game.
- *
- * This routine is not required if the system does not use the standard,
- * semi-portable os0.c implementation. Even if the system uses the
- * standard os0.c implementation, it can provide an empty routine here if
- * the system code doesn't need to do anything special with this
- * information.
- *
- * The extension is specified as a null-terminated string. The extension
- * does NOT include the leading period.
- */
-void os_set_save_ext(const char *ext);
-
-/*
- * Get the saved game extension previously set with os_set_save_ext().
- * Returns null if no custom extension has been set.
- */
-const char *os_get_save_ext();
-
-
-/* ------------------------------------------------------------------------*/
-/*
- * Translate a character from the HTML 4 Unicode character set to the
- * current character set used for display. Takes an HTML 4 character
- * code and returns the appropriate local character code.
- *
- * The result buffer should be filled in with a null-terminated string
- * that should be used to represent the character. Multi-character
- * results are possible, which may be useful for certain approximations
- * (such as using "(c)" for the copyright symbol).
- *
- * Note that we only define this prototype if this symbol isn't already
- * defined as a macro, which may be the case on some platforms.
- * Alternatively, if the function is already defined (for example, as an
- * inline function), the defining code can define OS_XLAT_HTML4_DEFINED,
- * in which case we'll also omit this prototype.
- *
- * Important: this routine provides the *default* mapping that is used
- * when no external character mapping file is present, and for any named
- * entities not defined in the mapping file. Any entities in the
- * mapping file, if used, will override this routine.
- *
- * A trivial implementation of this routine (that simply returns a
- * one-character result consisting of the original input character,
- * truncated to eight bits if necessary) can be used if you want to
- * require an external mapping file to be used for any game that
- * includes HTML character entities. The DOS version implements this
- * routine so that games will still look reasonable when played with no
- * mapping file present, but other systems are not required to do this.
- */
-#ifndef os_xlat_html4
-# ifndef OS_XLAT_HTML4_DEFINED
-void os_xlat_html4(unsigned int html4_char,
- char *result, size_t result_buf_len);
-# endif
-#endif
-
-/*
- * Generate a filename for a character-set mapping file. This function
- * should determine the current native character set in use, if
- * possible, then generate a filename, according to system-specific
- * conventions, that we should attempt to load to get a mapping between
- * the current native character set and the internal character set
- * identified by 'internal_id'.
- *
- * The internal character set ID is a string of up to 4 characters.
- *
- * On DOS, the native character set is a DOS code page. DOS code pages
- * are identified by 3- or 4-digit identifiers; for example, code page
- * 437 is the default US ASCII DOS code page. We generate the
- * character-set mapping filename by appending the internal character
- * set identifier to the DOS code page number, then appending ".TCP" to
- * the result. So, to map between ISO Latin-1 (internal ID = "La1") and
- * DOS code page 437, we would generate the filename "437La1.TCP".
- *
- * Note that this function should do only two things. First, determine
- * the current native character set that's in use. Second, generate a
- * filename based on the current native code page and the internal ID.
- * This function is NOT responsible for figuring out the mapping or
- * anything like that -- it's simply where we generate the correct
- * filename based on local convention.
- *
- * 'filename' is a buffer of at least OSFNMAX characters.
- *
- * 'argv0' is the executable filename from the original command line.
- * This parameter is provided so that the system code can look for
- * mapping files in the original TADS executables directory, if desired.
- */
-void os_gen_charmap_filename(char *filename, char *internal_id,
- char *argv0);
-
-/*
- * Receive notification that a character mapping file has been loaded.
- * The caller doesn't require this routine to do anything at all; this
- * is purely for the system-dependent code's use so that it can take
- * care of any initialization that it must do after the caller has
- * loaded a charater mapping file. 'id' is the character set ID, and
- * 'ldesc' is the display name of the character set. 'sysinfo' is the
- * extra system information string that is stored in the mapping file;
- * the interpretation of this information is up to this routine.
- *
- * For reference, the Windows version uses the extra information as a
- * code page identifier, and chooses its default font character set to
- * match the code page. On DOS, the run-time requires the player to
- * activate an appropriate code page using a DOS command (MODE CON CP
- * SELECT) prior to starting the run-time, so this routine doesn't do
- * anything at all on DOS.
- */
-void os_advise_load_charmap(const char *id, const char *ldesc, const char *sysinfo);
-
-/*
- * Generate the name of the character set mapping table for Unicode
- * characters to and from the given local character set. Fills in the
- * buffer with the implementation-dependent name of the desired
- * character set map. See below for the character set ID codes.
- *
- * For example, on Windows, the implementation would obtain the
- * appropriate active code page (which is simply a Windows character set
- * identifier number) from the operating system, and build the name of
- * the Unicode mapping file for that code page, such as "CP1252". On
- * Macintosh, the implementation would look up the current script system
- * and return the name of the Unicode mapping for that script system,
- * such as "ROMAN" or "CENTEURO".
- *
- * If it is not possible to determine the specific character set that is
- * in use, this function should return "asc7dflt" (ASCII 7-bit default)
- * as the character set identifier on an ASCII system, or an appropriate
- * base character set name on a non-ASCII system. "asc7dflt" is the
- * generic character set mapping for plain ASCII characters.
- *
- * The given buffer must be at least 32 bytes long; the implementation
- * must limit the result it stores to 32 bytes. (We use a fixed-size
- * buffer in this interface for simplicity, and because there seems no
- * need for greater flexibility in the interface; a character set name
- * doesn't carry very much information so shouldn't need to be very
- * long. Note that this function doesn't generate a filename, but
- * simply a mapping name; in practice, a map name will be used to
- * construct a mapping file name.)
- *
- * Because this function obtains the Unicode mapping name, there is no
- * need to specify the internal character set to be used: the internal
- * character set is Unicode.
- */
-/*
- * Implementation note: when porting this routine, the convention that
- * you use to name your mapping files is up to you. You should simply
- * choose a convention for this implementation, and then use the same
- * convention for packaging the mapping files for your OS release. In
- * most cases, the best convention is to use the names that the Unicode
- * consortium uses in their published cross-mapping listings, since
- * these listings can be used as the basis of the mapping files that you
- * include with your release. For example, on Windows, the convention
- * is to use the code page number to construct the map name, as in
- * CP1252 or CP1250.
- */
-void os_get_charmap(char *mapname, int charmap_id);
-
-/*
- * Character map for the display (i.e., for the user interface). This
- * is the character set which is used for input read from the keyboard,
- * and for output displayed on the monitor or terminal.
- */
-#define OS_CHARMAP_DISPLAY 1
-
-/*
- * Character map for mapping filename strings. This should identify the
- * character set currently in use on the local system for filenames
- * (i.e., for strings identifying objects in the local file system),
- * providing a suitable name for use in opening a mapping file.
- *
- * On many platforms, the entire system uses only one character set at
- * the OS level, so the file system character set is the same as the
- * display character set. Some systems define a particular character
- * set for file system use, though, because different users might be
- * running applications on terminals that display different character
- * sets.
- */
-#define OS_CHARMAP_FILENAME 2
-
-/*
- * Default character map for file contents. On most systems, this will
- * be the same as display. On some systems, it won't be possible to
- * know in general what character set files use; in fact, this isn't
- * possible anywhere, since a file might have been copied without
- * translation from a different type of computer using a different
- * character set. So, this isn't meant to provide a reliable choice of
- * character set for any arbitrary file; it's simply meant to be a good
- * guess that most files on this system are likely to use.
- */
-#define OS_CHARMAP_FILECONTENTS 3
-
-/*
- * Default character map for the command line. This is the maping we use
- * to interpret command line arguments passed to our main() or equivalent.
- * On most systems, this will be the same as the display character set.
- */
-#define OS_CHARMAP_CMDLINE 4
-
-
-/* ------------------------------------------------------------------------ */
-/*
- * External Banner Interface. This interface provides the ability to
- * divide the display window into multiple sub-windows, each with its own
- * independent contents.
- *
- * To determine where a new banner is displayed, we look at the banners as
- * a tree, rooted at the "main window," the special banner that the system
- * automatically creates initially for the main game text. We start by
- * allocating the entire display (or the entire application window, if
- * we're running on a GUI system) to the main window. We then traverse
- * the tree, starting with the root window's children. For each child
- * window, we allocate space for the child out of the parent window's
- * area, according to the child's alignment and size settings, and deduct
- * this space from the parent window's size. We then lay out the children
- * of the child.
- *
- * For each banner window, we take its requested space out of the parent
- * window's area by starting at the edge of the parent window rectangle as
- * indicated by the banner's alignment, and taking the requested `width
- * (for a left/right banner) or height (for a top/bottom banner), limiting
- * to the available width/height in the parent window's space. Give the
- * banner the full extent of the parent's space in its other dimension (so
- * a left/right banner gets the full height of the parent space, and a
- * top/bottom banner gets the full width).
- *
- * Note that the layout proceeds exclusively down the tree (i.e., from the
- * root to children to grandchildren, and so on). It *appears* that a
- * child affects its parent, because of the deduction step: a child
- * acquires screen space by carving out a chunk of its parent. The right
- * way to think about this, though, is that the parent's full area is the
- * union of the parent window and all of its children; when viewed this
- * way, the parent's full area is fully determined the instant the parent
- * is laid out, and never changes as its children are laid out. Note in
- * particular that a child can never make a parent larger; the only thing
- * a child can do to a parent is carve out a chunk of the parent for
- * itself, which doesn't affect the boundaries of the union of the parent
- * plus its children.
- *
- * Note also that if the banner has a border, and the implementation
- * actually draws borders, the border must be drawn for the *full* area of
- * the banner, as defined above. For example, suppose we have two
- * borders: banner A is a child of the main window, is top-aligned, and
- * has a border. Banner B is a child of banner A, right-aligned, with no
- * border. Obviously, without considering banner B, banner A's space runs
- * across the entire width of the main window, so its border (at the
- * bottom of its area) runs across the entire width of the main window.
- * Banner B carves out some space from A's right side for itself, so
- * banner A's actual on-screen area runs from the left edge of the main
- * window to banner B's left edge. However, even though banner A itself
- * no longer runs the full width of the main window, banner A's *full*
- * area - that is, the union of banner A's on-screen area and all of its
- * children's full areas - does still run the entire width of the main
- * window, hence banner A's border must still run the full width of the
- * main window. The simple way of looking at this is that a banner's
- * border is always to be drawn exactly the same way, regardless of
- * whether or not the banner has children - simply draw the banner as it
- * would be drawn if the banner had no children.
- *
- * Each time a banner is added or removed, we must recalculate the layout
- * of the remaining banners and main text area. The os_banner_xxx()
- * implementation is responsible for this layout refiguring.
- *
- * The entire external banner window interface is optional, although the
- * functions must at least be defined as dummies to avoid linker errors
- * when building. If a platform doesn't implement this feature,
- * os_banner_create() should simply return null, and the other routines
- * can do nothing.
- */
-
-/*
- * Create a banner window. 'info' gives the desired parameters for the new
- * banner.
- *
- * Note that certain requested parameter settings might or might not be
- * respected, depending on the capabilities of the platform and user
- * preferences. os_banner_getinfo() can be used after creation to
- * determine which parameter settings are actually used in the new banner.
- *
- * 'parent' gives the parent of this banner; this is the banner handle of
- * another banner window, or null. If 'parent' is null, then the new
- * banner is a child of the main window, which the system creates
- * automatically at startup and which contains the main input/output
- * transcript. The new banner's on-screen area is carved out of the
- * parent's space, according to the alignment and size settings of the new
- * window, so this determines how the window is laid out on the screen.
- *
- * 'where' is OS_BANNER_FIRST to make the new window the first child of its
- * parent; OS_BANNER_LAST to make it the last child of its parent;
- * OS_BANNER_BEFORE to insert it immediately before the existing banner
- * identified by handle in 'other'; or OS_BANNER_AFTER to insert
- * immediately after 'other'. When BEFORE or AFTER is used, 'other' must
- * be another child of the same parent; if it is not, the routine should
- * act as though 'where' were given as OS_BANNER_LAST.
- *
- * 'other' is a banner handle for an existing banner window. This is used
- * to specify the relative position among children of the new banner's
- * parent, if 'where' is either OS_BANNER_BEFORE or OS_BANNER_AFTER. If
- * 'where' is OS_BANNER_FIRST or OS_BANNER_LAST, 'other' is ignored.
- *
- * 'wintype' is the type of the window. This is one of the
- * OS_BANNER_TYPE_xxx codes indicating what kind of window is desired.
- *
- * 'align' is the banner's alignment, given as an OS_BANNER_ALIGN_xxx
- * value. Top/bottom banners are horizontal: they run across the full
- * width of the existing main text area. Left/right banners are vertical:
- * they run down the full height of the existing main text area.
- *
- * 'siz' is the requested size of the new banner. The meaning of 'siz'
- * depends on the value of 'siz_units', which can be OS_BANNER_SIZE_PCT to
- * set the size as a percentage of the REMAINING space, or
- * OS_BANNER_SIZE_ABS to set an absolute size in the "natural" units of the
- * window. The natural units vary by window type: for text and text grid
- * windows, this is in rows/columns of '0' characters in the default font
- * for the window. Note that when OS_BANNER_SIZE_ABS is used in a text or
- * text grid window, the OS implementation MUST add the space needed for
- * margins and borders when determining the actual pixel size of the
- * window; in other words, the window should be large enough that it can
- * actually display the given number or rows or columns.
- *
- * The size is interpreted as a width or height according to the window's
- * orientation. For a TOP or BOTTOM banner, the size is the height; for a
- * LEFT or RIGHT banner, the size is the width. A banner has only one
- * dimension's size given, since the other dimension's size is determined
- * automatically by the layout rules.
- *
- * Note that the window's size can be changed later using
- * banner_size_to_contents() or banner_set_size().
- *
- * 'style' is a combination of OS_BANNER_STYLE_xxx flags - see below. The
- * style flags give the REQUESTED style for the banner, which might or
- * might not be respected, depending on the platform's capabilities, user
- * preferences, and other factors. os_banner_getinfo() can be used to
- * determine which style flags are actually used.
- *
- * Returns the "handle" to the new banner window, which is an opaque value
- * that is used in subsequent os_banner_xxx calls to operate on the window.
- * Returns null if the window cannot be created. An implementation is not
- * required to support this functionality at all, and can subset it if it
- * does support it (for example, an implementation could support only
- * top/bottom-aligned banners, but not left/right-aligned), so callers must
- * be prepared for this routine to return null.
- */
-void *os_banner_create(void *parent, int where, void *other, int wintype,
- int align, int siz, int siz_units,
- unsigned long style);
-
-
-/*
- * insertion positions
- */
-#define OS_BANNER_FIRST 1
-#define OS_BANNER_LAST 2
-#define OS_BANNER_BEFORE 3
-#define OS_BANNER_AFTER 4
-
-/*
- * banner types
- */
-
-/*
- * Normal text stream window. This is a text stream that behaves
- * essentially like the main text window: text is displayed to this
- * through os_banner_disp(), always in a stream-like fashion by adding new
- * text to the end of any exiting text.
- *
- * Systems that use proportional fonts should usually simply use the same
- * font they use by default in the main text window. However, note that
- * the OS_BANNER_STYLE_TAB_ALIGN style flag might imply that a fixed-pitch
- * font should be used even when proportional fonts are available, because
- * a fixed-pitch font will allow the calling code to rely on using spaces
- * to align text within the window.
- */
-#define OS_BANNER_TYPE_TEXT 1
-
-/*
- * "Text grid" window. This type of window is similar to an normal text
- * window (OS_BANNER_TYPE_TEXT), but is guaranteed to arrange its text in
- * a regular grid of character cells, all of the same size. This means
- * that the output position can be moved to an arbitrary point within the
- * window at any time, so the calling program can precisely control the
- * layout of the text in the window.
- *
- * Because the output position can be moved to arbitrary positions in the
- * window, it is possible to overwrite text previously displayed. When
- * this happens, the old text is completely obliterated by the new text,
- * leaving no trace of the overwritten text.
- *
- * In order to guarantee that character cells are all the same size, this
- * type of window does not allow any text attributes. The implementation
- * should simply ignore any attempts to change text attributes in this
- * type of window. However, colors can be used to the same degree they
- * can be used in an ordinary text window.
- *
- * To guarantee the regular spacing of character cells, all
- * implementations must use fixed-pitch fonts for these windows. This
- * applies even to platforms where proportional fonts are available.
- */
-#define OS_BANNER_TYPE_TEXTGRID 2
-
-
-/*
- * banner alignment types
- */
-#define OS_BANNER_ALIGN_TOP 0
-#define OS_BANNER_ALIGN_BOTTOM 1
-#define OS_BANNER_ALIGN_LEFT 2
-#define OS_BANNER_ALIGN_RIGHT 3
-
-/*
- * size units
- */
-#define OS_BANNER_SIZE_PCT 1
-#define OS_BANNER_SIZE_ABS 2
-
-
-/*
- * banner style flags
- */
-
-/*
- * The banner has a visible border; this indicates that a line is to be
- * drawn to separate the banner from the adjacent window or windows
- * "inside" the banner. So, a top-aligned banner will have its border
- * drawn along its bottom edge; a left-aligned banner will show a border
- * along its right edge; and so forth.
- *
- * Note that character-mode platforms generally do NOT respect the border
- * style, since doing so takes up too much screen space.
- */
-#define OS_BANNER_STYLE_BORDER 0x00000001
-
-/*
- * The banner has a vertical/horizontal scrollbar. Character-mode
- * platforms generally do not support scrollbars.
- */
-#define OS_BANNER_STYLE_VSCROLL 0x00000002
-#define OS_BANNER_STYLE_HSCROLL 0x00000004
-
-/*
- * Automatically scroll the banner vertically/horizontally whenever new
- * text is displayed in the window. In other words, whenever
- * os_banner_disp() is called, scroll the window so that the text that the
- * new cursor position after the new text is displayed is visible in the
- * window.
- *
- * Note that this style is independent of the presence of scrollbars.
- * Even if there are no scrollbars, we can still scroll the window's
- * contents programmatically.
- *
- * Implementations can, if desired, keep an internal buffer of the
- * window's contents, so that the contents can be recalled via the
- * scrollbars if the text displayed in the banner exceeds the space
- * available in the banner's window on the screen. If the implementation
- * does keep such a buffer, we recommend the following method for managing
- * this buffer. If the AUTO_VSCROLL flag is not set, then the banner's
- * contents should be truncated at the bottom when the contents overflow
- * the buffer; that is, once the banner's internal buffer is full, any new
- * text that the calling program attempts to add to the banner should
- * simply be discarded. If the AUTO_VSCROLL flag is set, then the OLDEST
- * text should be discarded instead, so that the most recent text is
- * always retained.
- */
-#define OS_BANNER_STYLE_AUTO_VSCROLL 0x00000008
-#define OS_BANNER_STYLE_AUTO_HSCROLL 0x00000010
-
-/*
- * Tab-based alignment is required/supported. On creation, this is a hint
- * to the implementation that is sometimes necessary to determine what
- * kind of font to use in the new window, for non-HTML platforms. If this
- * flag is set on creation, the caller is indicating that it wants to use
- * <TAB> tags to align text in the window.
- *
- * Character-mode implementations that use a single font with fixed pitch
- * can simply ignore this. These implementations ALWAYS have a working
- * <TAB> capability, because the portable output formatter provides <TAB>
- * interpretation for a fixed-pitch window.
- *
- * Full HTML TADS implementations can also ignore this. HTML TADS
- * implementations always have full <TAB> support via the HTML
- * parser/renderer.
- *
- * Text-only implementations on GUI platforms (i.e., implementations that
- * are not based on the HTML parser/renderer engine in HTML TADS, but
- * which run on GUI platforms with proportionally-spaced text) should use
- * this flag to determine the font to display. If this flag is NOT set,
- * then the caller doesn't care about <TAB>, and the implementation is
- * free to use a proportionally-spaced font in the window if desired.
- *
- * When retrieving information on an existing banner, this flag indicates
- * that <TAB> alignment is actually supported on the window.
- */
-#define OS_BANNER_STYLE_TAB_ALIGN 0x00000020
-
-/*
- * Use "MORE" mode in this window. By default, a banner window should
- * happily allow text to overflow the vertical limits of the window; the
- * only special thing that should happen on overflow is that the window
- * should be srolled down to show the latest text, if the auto-vscroll
- * style is set. With this flag, though, a banner window acts just like
- * the main text window: when the window fills up vertically, we show a
- * MORE prompt (using appropriate system conventions), and wait for the
- * user to indicate that they're ready to see more text. On most systems,
- * the user acknowledges a MORE prompt by pressing a key or scrolling with
- * the mouse, but it's up to the system implementor to decide what's
- * appropriate for the system.
- *
- * Note that MORE mode in ANY banner window should generally override all
- * other user input focus. In other words, if the game in the main window
- * would like to read a keystroke from the user, but one of the banner
- * windows is pausing with a MORE prompt, any keyboard input should be
- * directed to the banner paused at the MORE prompt, not to the main
- * window; the main window should not receive any key events until the MORE
- * prompt has been removed.
- *
- * This style requires the auto-vscroll style. Implementations should
- * assume auto-vscroll when this style is set. This style can be ignored
- * with text grid windows.
- */
-#define OS_BANNER_STYLE_MOREMODE 0x00000040
-
-/*
- * This banner is a horizontal/vertical "strut" for sizing purposes. This
- * means that the banner's content size is taken into account when figuring
- * the content size of its *parent* banner. If the banner has the same
- * orientation as the parent, its content size is added to its parent's
- * internal content size to determine the parent's overall content size.
- * If the banner's orientation is orthogonal to the parent's, then the
- * parent's overall content size is the larger of the parent's internal
- * content size and this banner's content size.
- */
-#define OS_BANNER_STYLE_HSTRUT 0x00000080
-#define OS_BANNER_STYLE_VSTRUT 0x00000100
-
-
-/*
- * Delete a banner. This removes the banner from the display, which
- * requires recalculating the entire screen's layout to reallocate this
- * banner's space to other windows. When this routine returns, the banner
- * handle is invalid and can no longer be used in any os_banner_xxx
- * function calls.
- *
- * If the banner has children, the children will no longer be displayed,
- * but will remain valid in memory until deleted. A child window's
- * display area always comes out of its parent's space, so once the parent
- * is gone, a child has no way to acquire any display space; resizing the
- * child won't help, since it simply has no way to obtain any screen space
- * once its parent has been deleted. Even though the window's children
- * will become invisible, their banner handles will remain valid; the
- * caller is responsible for explicitly deleting the children even after
- * deleting their parent.
- */
-void os_banner_delete(void *banner_handle);
-
-/*
- * "Orphan" a banner. This tells the osifc implementation that the caller
- * wishes to sever all of its ties with the banner (as part of program
- * termination, for example), but that the calling program does not
- * actually require that the banner's on-screen display be immediately
- * removed.
- *
- * The osifc implementation can do one of two things:
- *
- * 1. Simply call os_banner_delete(). If the osifc implementation
- * doesn't want to do anything extra with the banner, it can simply delete
- * the banner, since the caller has no more use for it.
- *
- * 2. Take ownership of the banner. If the osifc implementation wishes
- * to continue displaying the final screen configuration after a program
- * has terminated, it can simply take over the banner and leave it on the
- * screen. The osifc subsystem must eventually delete the banner itself
- * if it takes this routine; for example, if the osifc subsystem allows
- * another client program to be loaded into the same window after a
- * previous program has terminated, it would want to delete any orphaned
- * banners from the previous program when loading a new program.
- */
-void os_banner_orphan(void *banner_handle);
-
-/*
- * Banner information structure. This is filled in by the system-specific
- * implementation in os_banner_getinfo().
- */
-struct os_banner_info_t
-{
- /* alignment */
- int align;
-
- /* style flags - these indicate the style flags actually in use */
- unsigned long style;
-
- /*
- * Actual on-screen size of the banner, in rows and columns. If the
- * banner is displayed in a proportional font or can display multiple
- * fonts of different sizes, this is approximated by the number of "0"
- * characters in the window's default font that will fit in the
- * window's display area.
- */
- int rows;
- int columns;
-
- /*
- * Actual on-screen size of the banner in pixels. This is meaningful
- * only for full HTML interpreter; for text-only interpreters, these
- * are always set to zero.
- *
- * Note that even if we're running on a GUI operating system, these
- * aren't meaningful unless this is a full HTML interpreter. Text-only
- * interpreters should always set these to zero, even on GUI OS's.
- */
- int pix_width;
- int pix_height;
-
- /*
- * OS line wrapping flag. If this is set, the window uses OS-level
- * line wrapping because the window uses a proportional font, so the
- * caller does not need to (and should not) perform line breaking in
- * text displayed in the window.
- *
- * Note that OS line wrapping is a PERMANENT feature of the window.
- * Callers can note this information once and expect it to remain
- * fixed through the window's lifetime.
- */
- int os_line_wrap;
-};
-typedef struct os_banner_info_t os_banner_info_t;
-
-/*
- * Get information on the banner - fills in the information structure with
- * the banner's current settings. Note that this should indicate the
- * ACTUAL properties of the banner, not the requested properties; this
- * allows callers to determine how the banner is actually displayed, which
- * depends upon the platform's capabilities and user preferences.
- *
- * Returns true if the information was successfully obtained, false if
- * not. This can return false if the underlying OS window has already
- * been closed by a user action, for example.
- */
-int os_banner_getinfo(void *banner_handle, os_banner_info_t *info);
-
-/*
- * Get the character width/height of the banner, for layout purposes. This
- * gives the size of the banner in character cells.
- *
- * These are not meaningful when the underlying window uses a proportional
- * font or varying fonts of different sizes. When the size of text varies
- * in the window, the OS layer is responsible for word-wrapping and other
- * layout, in which case these simply return zero.
- *
- * Note that these routines might appear to be redundant with the 'rows'
- * and 'columns' information returned from os_banner_getinfo(), but these
- * have two important distinctions. First, these routines return only the
- * width and height information, so they can be implemented with less
- * overhead than os_banner_getinfo(); this is important because formatters
- * might need to call these routines frequently while formatting text.
- * Second, these routines are not required to return an approximation for
- * windows using proportional fonts, as os_banner_getinfo() does; these can
- * simply return zero when a proportional font is in use.
- */
-int os_banner_get_charwidth(void *banner_handle);
-int os_banner_get_charheight(void *banner_handle);
-
-/* clear the contents of a banner */
-void os_banner_clear(void *banner_handle);
-
-/*
- * Display output on a banner. Writes the output to the window on the
- * display at the current output position.
- *
- * The following special characters should be recognized and handled:
- *
- * '\n' - newline; move output position to the start of the next line.
- *
- * '\r' - move output position to start of current line; subsequent text
- * overwrites any text previously displayed on the current line. It is
- * permissible to delete the old text immediately on seeing the '\r',
- * rather than waiting for additional text to actually overwrite it.
- *
- * All other characters should simply be displayed as ordinary printing
- * text characters. Note that tab characters should not be passed to this
- * routine, but if they are, they can simply be treated as ordinary spaces
- * if desired. Other control characters (backspace, escape, etc) should
- * never be passed to this routine; the implementation is free to ignore
- * any control characters not listed above.
- *
- * If any text displayed here overflows the current boundaries of the
- * window on the screen, the text MUST be "clipped" to the current window
- * boundaries; in other words, anything this routine tries to display
- * outside of the window's on-screen rectangle must not actually be shown
- * on the screen.
- *
- * Text overflowing the display boundaries MUST also be retained in an
- * internal buffer. This internal buffer can be limited to the actual
- * maximum display size of the terminal screen or application window, if
- * desired. It is necessary to retain clipped text, because this allows a
- * window to be expanded to the size of its contents AFTER the contents
- * have already been displayed.
- *
- * If the banner does its own line wrapping, it must indicate this via the
- * os_line_wrap flag in the os_banner_getinfo() return data. If the
- * banner doesn't indicate this flag, then it must not do any line
- * wrapping at all, even if the caller attempts to write text beyond the
- * right edge of the window - any text overflowing the width of the window
- * must simply be clipped.
- *
- * Text grid banners must ALWAYS clip - these banners should never perform
- * any line wrapping.
- */
-void os_banner_disp(void *banner_handle, const char *txt, size_t len);
-
-/*
- * Set the text attributes in a banner, for subsequent text displays.
- * 'attr' is a (bitwise-OR'd) combination of OS_ATTR_xxx values.
- */
-void os_banner_set_attr(void *banner_handle, int attr);
-
-/*
- * Set the text color in a banner, for subsequent text displays. The 'fg'
- * and 'bg' colors are given as RGB or parameterized colors; see the
- * definition of os_color_t for details.
- *
- * If the underlying renderer is HTML-enabled, then this should not be
- * used; the appropriate HTML code should simply be displayed to the
- * banner instead.
- */
-void os_banner_set_color(void *banner_handle, os_color_t fg, os_color_t bg);
-
-/*
- * Set the screen color in the banner - this is analogous to the screen
- * color in the main text area.
- *
- * If the underlying renderer is HTML-enabled, then this should not be
- * used; the HTML <BODY> tag should be used instead.
- */
-void os_banner_set_screen_color(void *banner_handle, os_color_t color);
-
-/* flush output on a banner */
-void os_banner_flush(void *banner_handle);
-
-/*
- * Set the banner's size. The size has the same meaning as in
- * os_banner_create().
- *
- * 'is_advisory' indicates whether the sizing is required or advisory only.
- * If this flag is false, then the size should be set as requested. If
- * this flag is true, it means that the caller intends to call
- * os_banner_size_to_contents() at some point, and that the size being set
- * now is for advisory purposes only. Platforms that support
- * size-to-contents may simply ignore advisory sizing requests, although
- * they might want to ensure that they have sufficient off-screen buffer
- * space to keep track of the requested size of display, so that the
- * information the caller displays in preparation for calling
- * size-to-contents will be retained. Platforms that do not support
- * size-to-contents should set the requested size even when 'is_advisory'
- * is true.
- */
-void os_banner_set_size(void *banner_handle, int siz, int siz_units,
- int is_advisory);
-
-/*
- * Set the banner to the size of its current contents. This can be used
- * to set the banner's size after some text (or other material) has been
- * displayed to the banner, so that the size can be set according to the
- * banner's actual space requirements.
- *
- * This changes the banner's "requested size" to match the current size.
- * Subsequent calls to os_banner_getinfo() will thus indicate a requested
- * size according to the size set here.
- */
-void os_banner_size_to_contents(void *banner_handle);
-
-/*
- * Turn HTML mode on/off in the banner window. If the underlying renderer
- * doesn't support HTML, these have no effect.
- */
-void os_banner_start_html(void *banner_handle);
-void os_banner_end_html(void *banner_handle);
-
-/*
- * Set the output coordinates in a text grid window. The grid window is
- * arranged into character cells numbered from row zero, column zero for
- * the upper left cell. This function can only be used if the window was
- * created with type OS_BANNER_TYPE_TEXTGRID; the request should simply be
- * ignored by other window types.
- *
- * Moving the output position has no immediate effect on the display, and
- * does not itself affect the "content size" for the purposes of
- * os_banner_size_to_contents(). This simply sets the coordinates where
- * any subsequent text is displayed.
- */
-void os_banner_goto(void *banner_handle, int row, int col);
-
-
-/* ------------------------------------------------------------------------ */
-/*
- * Get system information. 'code' is a SYSINFO_xxx code, which
- * specifies what type of information to get. The 'param' argument's
- * meaning depends on which code is selected. 'result' is a pointer to
- * an integer that is to be filled in with the result value. If the
- * code is not known, this function should return FALSE. If the code is
- * known, the function should fill in *result and return TRUE.
- */
-int os_get_sysinfo(int code, void *param, long *result);
-
-/* determine if systemInfo is supported - os_get_sysinfo never gets this */
-#define SYSINFO_SYSINFO 1
-
-/* get interpreter version number - os_get_sysinfo never gets this */
-#define SYSINFO_VERSION 2
-
-/* get operating system name - os_get_sysinfo never gets this */
-#define SYSINFO_OS_NAME 3
-
-/*
- * Can the system process HTML directives? returns 1 if so, 0 if not.
- * Note that if this returns false, then all of the codes below from
- * JPEG to LINKS are implicitly false as well, since TADS can only use
- * images, sounds, and links through HTML.
- */
-#define SYSINFO_HTML 4
-
-/* can the system display JPEG's? 1 if yes, 0 if no */
-#define SYSINFO_JPEG 5
-
-/* can the system display PNG's? 1 if yes, 0 if no */
-#define SYSINFO_PNG 6
-
-/* can the system play WAV's? 1 if yes, 0 if no */
-#define SYSINFO_WAV 7
-
-/* can the system play MIDI's? 1 if yes, 0 if no */
-#define SYSINFO_MIDI 8
-
-/* can the system play MIDI and WAV's simultaneously? yes=1, no=0 */
-#define SYSINFO_WAV_MIDI_OVL 9
-
-/* can the system play multiple WAV's simultaneously? yes=1, no=0 */
-#define SYSINFO_WAV_OVL 10
-
-/*
- * GENERAL NOTES ON PREFERENCE SETTINGS:
- *
- * Several of the selectors below refer to the preference settings. We're
- * talking about user-settable options to control various aspects of the
- * interpreter. The conventional GUI for this kind of thing is a dialog
- * box reachable through a menu command named something like "Options" or
- * "Preferences". A couple of general notes about these:
- *
- * 1. The entire existence of a preferences mechanism is optional -
- * interpreter writers aren't required to implement anything along these
- * lines. In some cases the local platforms might not have any suitable
- * conventions for a preferences UI (e.g., character-mode console
- * applications), and in other cases the terp developer might just want to
- * omit a prefs mechanism because of the work involved to implement it, or
- * to keep the UI simpler.
- *
- * 2. If a given SYSINFO_PREF_xxx selector refers to a preference item
- * that's not implemented in the local interpreter, the terp should simply
- * return a suitable default result. For example, if the interpreter
- * doesn't have a preference item to allow the user to turn sounds off, the
- * selector SYSINFO_PREF_SOUNDS should return 1 to indicate that the user
- * has not in fact turned off sounds (because there's no way to do so).
- *
- * 3. The various SYSINFO_PREF_xxx selectors are purely queries - they're
- * NOT a mechanism for enforcing the preferences. For example, if the
- * interpreter provides a "Sounds On/Off" option, it's up to the terp to
- * enforce it the Off setting by ignoring any sound playback requests. The
- * game isn't under any obligation to query any of the preferences or to
- * alter its behavior based on their settings - you should expect that the
- * game will go on trying to play sounds even when "Sounds Off" is selected
- * in the preferences. The purpose of these SYSINFO selectors is to let
- * the game determine the current presentation status, but *only if it
- * cares*. For example, the game might determine whether or not sounds are
- * actually being heard just before playing a sound effect that's important
- * to the progress of the game, so that it can provide a visual alternative
- * if the effect won't be heard.
- */
-
-/*
- * Get image preference setting - 1 = images can be displayed, 0 = images
- * are not being displayed because the user turned off images in the
- * preferences. This is, of course, irrelevant if images can't be
- * displayed at all.
- *
- * See the general notes on preferences queries above.
- */
-#define SYSINFO_PREF_IMAGES 11
-
-/*
- * Get digitized sound effect (WAV) preference setting - 1 = sounds can be
- * played, 0 = sounds are not being played because the user turned off
- * sounds in the preferences.
- *
- * See the general notes on preferences queries above.
- */
-#define SYSINFO_PREF_SOUNDS 12
-
-/*
- * Get music (MIDI) preference setting - 1 = music can be played, 0 = music
- * is not being played because the user turned off music in the
- * preferences.
- *
- * See the general notes on preferences queries above.
- */
-#define SYSINFO_PREF_MUSIC 13
-
-/*
- * Get link display preference setting - 0 = links are not being displayed
- * because the user set a preference item that suppresses all links (which
- * doesn't actually hide them, but merely displays them and otherwise
- * treats them as ordinary text). 1 = links are to be displayed normally.
- * 2 = links can be displayed temporarily by the user by pressing a key or
- * some similar action, but aren't being displayed at all times.
- *
- * See the general note on preferences queries above.
- */
-#define SYSINFO_PREF_LINKS 14
-
-/* can the system play MPEG sounds of any kind? */
-#define SYSINFO_MPEG 15
-
-/* can the system play MPEG audio 2.0 layer I/II/III sounds? */
-#define SYSINFO_MPEG1 16
-#define SYSINFO_MPEG2 17
-#define SYSINFO_MPEG3 18
-
-/*
- * is the system *currently* in HTML mode? os_get_sysinfo never gets
- * this code, since the portable output layer keeps track of this
- */
-#define SYSINFO_HTML_MODE 19
-
-/*
- * Does the system allow following external URL links of the various
- * types? These return true if the system is capable of following these
- * types of hypertext links, false if not. Note that, if the system is
- * capable of following these links, these should return true regardless
- * of any current mode settings; in particular, these should not be
- * sensitive to the current HTML mode or the current link display mode,
- * since the question is not whether a link now displayed can be
- * followed by the user, but rather whether the system has the
- * capability to follow these types of links at all.
- */
-#define SYSINFO_LINKS_HTTP 20
-#define SYSINFO_LINKS_FTP 21
-#define SYSINFO_LINKS_NEWS 22
-#define SYSINFO_LINKS_MAILTO 23
-#define SYSINFO_LINKS_TELNET 24
-
-/* is PNG transparency supported? */
-#define SYSINFO_PNG_TRANS 25
-
-/* is PNG alpha blending supported? */
-#define SYSINFO_PNG_ALPHA 26
-
-/* is the Ogg Vorbis audio format supported? */
-#define SYSINFO_OGG 27
-
-/* can the system display MNG's? */
-#define SYSINFO_MNG 28
-
-/* can the system display MNG's with transparency? */
-#define SYSINFO_MNG_TRANS 29
-
-/* can the system display MNG's with alpha blending? */
-#define SYSINFO_MNG_ALPHA 30
-
-/* can we display highlighted text in its own appearance? */
-#define SYSINFO_TEXT_HILITE 31
-
-/*
- * Can we display text colors? This returns a SYSINFO_TXC_xxx code
- * indicating the level of color support.
- *
- * The os_xxx interfaces don't presently support anything beyond the ANSI
- * colors; however, HTML-enabled interpreters generally support full RGB
- * colors, so we call this out as a separate level.
- */
-#define SYSINFO_TEXT_COLORS 32
-
-/* no text color support */
-#define SYSINFO_TXC_NONE 0
-
-/* parameterized color names only (OS_COLOR_P_TEXT, etc) */
-#define SYSINFO_TXC_PARAM 1
-
-/*
- * we support only the basic ANSI colors, foreground control only (white,
- * black, blue, red, green, yellow, cyan, magenta)
- */
-#define SYSINFO_TXC_ANSI_FG 2
-
-/* ANSI colors, foreground and background */
-#define SYSINFO_TXC_ANSI_FGBG 3
-
-/* full RGB support */
-#define SYSINFO_TXC_RGB 4
-
-/* are the os_banner_xxx() interfaces supported? */
-#define SYSINFO_BANNERS 33
-
-/* Interpreter Class - this returns one of the SYSINFO_ICLASS_xxx codes */
-#define SYSINFO_INTERP_CLASS 34
-
-/*
- * Interpreter class: Character-mode Text-Only. Interpreters of this class
- * use a single, fixed-pitch font to display all text, and use the
- * text-only HTML subset, and cannot display graphics.
- */
-#define SYSINFO_ICLASS_TEXT 1
-
-/*
- * Interpreter class: Text-Only GUI. Interpreters of this class are
- * traditional text-only interpreters running on graphical operating
- * systems. These interpreters might use multiple fonts (for example, they
- * might display highlighted text in boldface), and might use
- * proportionally-spaced text for some windows. These interpreters support
- * the text-only HTML subset, and cannot display graphics.
- *
- * Text-only GUI interpreters act essentially the same as character-mode
- * text-only interpreters, from the perspective of the client program.
- */
-#define SYSINFO_ICLASS_TEXTGUI 2
-
-/*
- * Interpreter class: HTML. Interpreters of this class can display
- * graphics and sounds, can display multiple fonts and font sizes, can use
- * proportional fonts, and support the full HTML TADS markup language for
- * formatting.
- */
-#define SYSINFO_ICLASS_HTML 3
-
-/*
- * Audio fade information.
- *
- * SYSINFO_AUDIO_FADE: basic fade-in and fade-out support. Interpreters
- * that don't support audio fade at all should return 0. Interpreters that
- * support fades should return a bitwise OR'd combination of
- * SYSINFO_AUDIOFADE_xxx flags below indicating which formats can be used
- * with fades.
- *
- * SYSINFO_AUDIO_CROSSFADE: cross-fades are supported (i.e., simultaneous
- * play of overlapping tracks, one fading out while the other fades in).
- * If cross-fades aren't supported, return 0. If they're supported, return
- * a combination of SYSINFO_AUDIOFADE_xxx flags indicating which formats
- * can be used with cross-fades.
- */
-#define SYSINFO_AUDIO_FADE 35
-#define SYSINFO_AUDIO_CROSSFADE 36
-
-/*
- * Specific audio fading features. These are bit flags that can be
- * combined to indicate the fading capabilities of the interpreter.
- */
-#define SYSINFO_AUDIOFADE_MPEG 0x0001 /* supported for MPEG audio */
-#define SYSINFO_AUDIOFADE_OGG 0x0002 /* supported for Ogg Vorbis */
-#define SYSINFO_AUDIOFADE_WAV 0x0004 /* supported for WAV */
-#define SYSINFO_AUDIOFADE_MIDI 0x0008 /* supported for MIDI */
-
-
-/* ------------------------------------------------------------------------ */
-/*
- * Integer division operators. For any compiler that follows ANSI C
- * rules, no definitions are required for these routine, since the
- * standard definitions below will work properly. However, if your
- * compiler does not follow ANSI standards with respect to integer
- * division of negative numbers, you must provide implementations of
- * these routines that produce the correct results.
- *
- * Division must "truncate towards zero," which means that any
- * fractional part is dropped from the result. If the result is
- * positive, the result must be the largest integer less than the
- * algebraic result: 11/3 yields 3. If the result is negative, the
- * result must be the smallest integer less than the result: (-11)/3
- * yields -3.
- *
- * The remainder must obey the relationship (a/b)*b + a%b == a, for any
- * integers a and b (b != 0).
- *
- * If your compiler does not obey the ANSI rules for the division
- * operators, make the following changes in your osxxx.h file
- *
- * - define the symbol OS_NON_ANSI_DIVIDE in the osxxx.h file
- *
- * - either define your own macros for os_divide_long() and
- * os_remainder_long(), or put actual prototypes for these functions
- * into your osxxx.h file and write appropriate implementations of these
- * functions in one of your osxxx.c or .cpp files.
- */
-/* long os_divide_long(long a, long b); // returns (a/b) with ANSI rules */
-/* long os_remainder_long(long a, long b); // returns (a%b) with ANSI rules */
-
-/* standard definitions for any ANSI compiler */
-#ifndef OS_NON_ANSI_DIVIDE
-#define os_divide_long(a, b) ((a) / (b))
-#define os_remainder_long(a, b) ((a) % (b))
-#endif
+/* open the error message file for reading */
+osfildef *oserrop(const char *arg0);
/* ------------------------------------------------------------------------ */
/*
diff --git a/engines/glk/tads/tads2/play.cpp b/engines/glk/tads/tads2/play.cpp
new file mode 100644
index 0000000000..115d2b6ac1
--- /dev/null
+++ b/engines/glk/tads/tads2/play.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/play.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+void plygo(runcxdef *run, voccxdef *voc, tiocxdef *tio, objnum preinit, char *restore_fname) {
+ // TODO
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/play.h b/engines/glk/tads/tads2/play.h
new file mode 100644
index 0000000000..9b1e9917b9
--- /dev/null
+++ b/engines/glk/tads/tads2/play.h
@@ -0,0 +1,46 @@
+/* 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.
+ *
+ */
+
+/*
+ * Implementation of main gameplay loop
+ */
+#ifndef GLK_TADS_TADS2_PLAY
+#define GLK_TADS_TADS2_PLAY
+
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/object.h"
+#include "glk/tads/tads2/run.h"
+#include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/tads2/tokenizer.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+void plygo(runcxdef *run, voccxdef *voc,
+ tiocxdef *tio, objnum preinit, char *restore_fname);
+
+} // 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
index ca29601190..cd501db1e2 100644
--- a/engines/glk/tads/tads2/post_compilation.cpp
+++ b/engines/glk/tads/tads2/post_compilation.cpp
@@ -28,439 +28,6 @@ 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/post_compilation.h b/engines/glk/tads/tads2/post_compilation.h
index 1dde838d02..fdd12619fb 100644
--- a/engines/glk/tads/tads2/post_compilation.h
+++ b/engines/glk/tads/tads2/post_compilation.h
@@ -21,9 +21,6 @@
*/
/* definitions for post-compilation setup
- *
- * Does post-compilation setup, such as setting up contents lists
- * faster run-time performance, but run - time errors could be disastrous.
*/
#ifndef GLK_TADS_TADS2_POST_COMPILATION
@@ -49,45 +46,43 @@ struct supcxdef {
};
/* set up contents list for one object for demand-on-load */
-void supcont(void *ctx, objnum obj, prpnum prp);
+inline void supcont(void *ctx, objnum obj, prpnum prp);
/* set up inherited vocabulary (called before executing game) */
-void supivoc(supcxdef *ctx);
+inline void supivoc(supcxdef *ctx);
/* find required objects/functions */
-void supfind(errcxdef *ctx, tokthdef *tab, voccxdef *voc,
+inline void supfind(errcxdef *ctx, tokthdef *tab, voccxdef *voc,
objnum *preinit, int warnlevel, int casefold);
/* set up reserved words */
-void suprsrv(supcxdef *sup, void (*bif[])(struct bifcxdef *, int),
+inline void suprsrv(supcxdef *sup, void (*bif[])(struct bifcxdef *, int),
toktdef *tab, int fncntmax, int v1compat, char *new_do,
int casefold);
/* set up built-in functions without symbol table (for run-time) */
-void supbif(supcxdef *sup, void (*bif[])(struct bifcxdef *, int),
+inline void supbif(supcxdef *sup, void (*bif[])(struct bifcxdef *, int),
int bifsiz);
/* log an undefined-object error */
-void sup_log_undefobj(mcmcxdef *mctx, errcxdef *ec, int err,
+inline void sup_log_undefobj(mcmcxdef *mctx, errcxdef *ec, int err,
char *sym_name, int sym_name_len, objnum objn);
/* set up inherited vocabulary for a particular object */
-void supivoc1(supcxdef *sup, voccxdef *ctx, vocidef *v, objnum target,
+inline void supivoc1(supcxdef *sup, voccxdef *ctx, vocidef *v, objnum target,
int inh_from_obj, int flags);
/* get name of an object out of symbol table */
-void supgnam(char *buf, tokthdef *tab, objnum objn);
+inline void supgnam(char *buf, tokthdef *tab, objnum objn);
/* table of built-in functions */
-typedef struct supbidef supbidef;
-struct supbidef
-{
+struct supbidef {
char *supbinam; /* name of function */
void (*supbifn)(struct bifcxdef *, int); /* C routine to call */
};
/* external definition for special token table */
-extern tokldef supsctab[];
+//extern tokldef supsctab[];
} // End of namespace TADS2
} // End of namespace TADS
diff --git a/engines/glk/tads/tads2/qa_scriptor.cpp b/engines/glk/tads/tads2/qa_scriptor.cpp
new file mode 100644
index 0000000000..ec4e770322
--- /dev/null
+++ b/engines/glk/tads/tads2/qa_scriptor.cpp
@@ -0,0 +1,120 @@
+/* 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/lib.h"
+#include "glk/tads/tads2/run.h"
+#include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/os_frob_tads.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+/*
+ * Globals for the script reader
+ */
+osfildef *scrfp = (osfildef *)0; /* script file */
+int scrquiet = 0; /* flag: true ==> script is NOT shown as read */
+
+int qasopn(char *scrnam, int quiet) {
+ if (scrfp) return 1; /* already reading from script */
+ if ((scrfp = osfoprt(scrnam, OSFTCMD)) == 0) return 1;
+ scrquiet = quiet;
+ return 0;
+}
+
+void qasclose() {
+ /* only close the script file if there's one open */
+ if (scrfp)
+ {
+ osfcls(scrfp);
+ scrfp = 0; /* no more script file */
+ scrquiet = 0;
+ }
+}
+
+char *qasgets(char *buf, int bufl) {
+ /* shouldn't be here at all if there's no script file */
+ if (scrfp == 0)
+ return 0;
+
+ /* update status line */
+ runstat();
+
+ /* keep going until we find something we like */
+ for (;;)
+ {
+ char c;
+
+ /*
+ * Read the next character of input. If it's not a newline,
+ * there's more on the same line, so read the rest and see what
+ * to do.
+ */
+ c = osfgetc(scrfp);
+ if (c != '\n' && c != '\r')
+ {
+ /* read the rest of the line */
+ if (!osfgets(buf, bufl, scrfp))
+ {
+ /* end of file: close the script and return eof */
+ qasclose();
+ return 0;
+ }
+
+ /* if the line started with '>', strip '\n' and return line */
+ if (c == '>')
+ {
+ int l;
+
+ /* remove the trailing newline */
+ if ((l = strlen(buf)) > 0
+ && (buf[l-1] == '\n' || buf[l-1] == '\r'))
+ buf[l-1] = 0;
+
+ /*
+ * if we're not in quiet mode, echo the command to the
+ * display
+ */
+ if (!scrquiet)
+ outformat(buf);
+
+ /* flush the current line without adding any blank lines */
+ outflushn(1);
+
+ /* return the command */
+ return buf;
+ }
+ }
+ else if (c == EOF )
+ {
+ /* end of file - close the script and return eof */
+ qasclose();
+ return 0;
+ }
+ }
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/run.h b/engines/glk/tads/tads2/run.h
index 732cfdf29b..6c303b81ce 100644
--- a/engines/glk/tads/tads2/run.h
+++ b/engines/glk/tads/tads2/run.h
@@ -362,7 +362,7 @@ void runsign(runcxdef *ctx, int err);
runsign(ctx,err))
/* draw status line */
-void runstat(void);
+void runstat();
/* initialize output status */
void runistat(struct voccxdef *vctx, struct runcxdef *rctx,
diff --git a/engines/glk/tads/tads2/runstat.cpp b/engines/glk/tads/tads2/runstat.cpp
new file mode 100644
index 0000000000..8b30138b0b
--- /dev/null
+++ b/engines/glk/tads/tads2/runstat.cpp
@@ -0,0 +1,89 @@
+/* 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"
+#include "glk/tads/tads2/os.h"
+#include "glk/tads/tads2/run.h"
+#include "glk/tads/tads2/tokenizer.h"
+#include "glk/tads/tads2/vocabulary.h"
+
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+static runcxdef *runctx;
+static voccxdef *vocctx;
+static tiocxdef *tioctx;
+
+void runstat(void)
+{
+ objnum locobj;
+ int savemoremode;
+
+ /* get the location of the Me object */
+ runppr(runctx, vocctx->voccxme, PRP_LOCATION, 0);
+
+ /* if that's no an object, there's nothing we can do */
+ if (runtostyp(runctx) != DAT_OBJECT)
+ {
+ rundisc(runctx);
+ return;
+ }
+
+ /* get Me.location */
+ locobj = runpopobj(runctx);
+
+ /* flush any pending output */
+ outflushn(0);
+
+ /* switch to output display mode 1 (status line) */
+ os_status(1);
+
+ /* turn off MORE mode */
+ savemoremode = setmore(0);
+
+ /* call the statusLine method of the current room */
+ runppr(runctx, locobj, PRP_STATUSLINE, 0);
+
+ /* if we're in the status line, make sure the line gets flushed */
+ if (os_get_status() != 0)
+ tioputs(tioctx, "\\n");
+ outflushn(0);
+
+ /* restore the previous MORE mode */
+ setmore(savemoremode);
+
+ /* switch to output display mode 0 (main text area) */
+ os_status(0);
+}
+
+void runistat(voccxdef *vctx, runcxdef *rctx, tiocxdef *tctx)
+{
+ runctx = rctx;
+ vocctx = vctx;
+ tioctx = tctx;
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/runtime_app.cpp b/engines/glk/tads/tads2/runtime_app.cpp
new file mode 100644
index 0000000000..d1bf5514af
--- /dev/null
+++ b/engines/glk/tads/tads2/runtime_app.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.
+ *
+ */
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+char G_tads_oem_app_name[] = "GlkTADS";
+char G_tads_oem_display_mode[] = "text-only";
+char G_tads_oem_dbg_name[] = "tdb";
+char G_tads_oem_author[] = "Maintained by ScummVM\n";
+int G_tads_oem_copyright_prefix = true;
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/runtime_app.h b/engines/glk/tads/tads2/runtime_app.h
new file mode 100644
index 0000000000..0e4e16351d
--- /dev/null
+++ b/engines/glk/tads/tads2/runtime_app.h
@@ -0,0 +1,113 @@
+/* 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_RUNTIME_APP
+#define GLK_TADS_TADS2_RUNTIME_APP
+
+#include "glk/tads/tads2/appctx.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+/* forward-declare structure types */
+struct runcxdef;
+
+/*
+ * Run-time version number
+ */
+#define TADS_RUNTIME_VERSION "2.5.17"
+
+extern char G_tads_oem_app_name[];
+extern char G_tads_oem_display_mode[];
+extern char G_tads_oem_dbg_name[];
+extern char G_tads_oem_author[];
+extern int G_tads_oem_copyright_prefix;
+
+
+/*
+ * Main run-time subsystem entrypoint. Runs the game specified in the
+ * argument vector; does not return until the game terminates. The
+ * application container context is optional; pass null if no context is
+ * required.
+ */
+int trdmain(int argc, char **argv, appctxdef *appctx, 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);
+
+/*
+ * close and delete the swap file
+ */
+void trd_close_swapfile(struct runcxdef *runctx);
+
+/*
+ * Define default memory sizes if no one else has.
+ */
+#ifndef TRD_HEAPSIZ
+# define TRD_HEAPSIZ 4096
+#endif
+#ifndef TRD_STKSIZ
+# define TRD_STKSIZ 200
+#endif
+#ifndef TRD_UNDOSIZ
+# define TRD_UNDOSIZ (16 * 1024)
+#endif
+
+
+#ifndef TDD_HEAPSIZ
+# define TDD_HEAPSIZ 4096
+#endif
+#ifndef TDD_STKSIZ
+# define TDD_STKSIZ 200
+#endif
+#ifndef TDD_UNDOSIZ
+# define TDD_UNDOSIZ (16 * 1024)
+#endif
+#ifndef TDD_POOLSIZ
+# define TDD_POOLSIZ (2 * 1024)
+#endif
+#ifndef TDD_LCLSIZ
+# define TDD_LCLSIZ 0
+#endif
+
+/*
+ * If the OS headers haven't defined any system-specific option usage
+ * messages, set up a dummy list. The usage display routine will show
+ * messages starting from the lower number up to and including the higher
+ * number; by default we'll make the ending number lower than the starting
+ * number so that we don't display any messages at all.
+ */
+#ifndef ERR_TRUS_OS_FIRST
+# define ERR_TRUS_OS_FIRST 100
+# define ERR_TRUS_OS_LAST 99
+#endif
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/runtime_driver.cpp b/engines/glk/tads/tads2/runtime_driver.cpp
new file mode 100644
index 0000000000..b2169ddf55
--- /dev/null
+++ b/engines/glk/tads/tads2/runtime_driver.cpp
@@ -0,0 +1,839 @@
+/* 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/character_map.h"
+#include "glk/tads/tads2/command_line.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/file_io.h"
+#include "glk/tads/tads2/memory_cache_heap.h"
+#include "glk/tads/tads2/os.h"
+#include "glk/tads/tads2/play.h"
+#include "glk/tads/tads2/post_compilation.h"
+#include "glk/tads/tads2/run.h"
+#include "glk/tads/tads2/runtime_app.h"
+#include "glk/tads/tads2/tokenizer.h"
+#include "glk/tads/tads2/tads2.h"
+#include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/os_frob_tads.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* dummy setup function */
+void supgnam(char *buf, tokthdef *tab, objnum sc)
+{
+ strcpy(buf, "???");
+}
+
+/* dummy file read functions */
+void tok_read_defines(tokcxdef *tctx, osfildef *fp, errcxdef *ec)
+{
+ errsig(ec, ERR_UNKRSC);
+}
+
+/* dummy debugger functions */
+void trchid(void) {}
+void trcsho(void) {}
+
+struct runsdef *dbgfrfind(dbgcxdef *ctx, objnum frobj, uint frofs)
+{
+ VARUSED(frobj);
+ VARUSED(frofs);
+ errsig(ctx->dbgcxerr, ERR_INACTFR);
+ return 0;
+}
+
+void dbgss(struct dbgcxdef *ctx, uint ofs, int instr, int err,
+ uchar *noreg *p)
+{
+ VARUSED(ctx);
+ VARUSED(ofs);
+ VARUSED(instr);
+ VARUSED(err);
+ VARUSED(p);
+}
+
+int dbgstart(struct dbgcxdef *ctx)
+{
+ VARUSED(ctx);
+ return TRUE;
+}
+
+/* printf-style formatting */
+static void trdptf(const char *fmt, ...)
+{
+ char buf[256];
+ va_list va;
+
+ /* format the string */
+ va_start(va, fmt);
+ vsprintf(buf, fmt, va);
+ va_end(va);
+
+ /* print the formatted buffer */
+ os_printz(buf);
+}
+
+
+/*
+ * display a range of usage messages
+ */
+static void trdusage_show_range(errcxdef *ec, int msg_first, int msg_last)
+{
+ int i;
+ char buf[128];
+
+ for (i = msg_first ; i <= msg_last ; ++i)
+ {
+ errmsg(ec, buf, (uint)sizeof(buf), i);
+ trdptf("%s\n", buf);
+ }
+}
+
+
+/*
+ * display a range of usage messages, then throw the usage error
+ */
+static void trdusage_range(errcxdef *ec, int msg_first, int msg_last)
+{
+ /* show the message range */
+ trdusage_show_range(ec, msg_first, msg_last);
+
+ /* signal the usage error */
+ errsig(ec, ERR_USAGE);
+}
+
+/*
+ * display general run-time usage information
+ */
+static void trdusage(errcxdef *ec)
+{
+ int first;
+
+ /*
+ * if we have an app display name, display it instead of the
+ * hard-coded text in the message identifying the app
+ */
+ first = ERR_TRUS1;
+ if (ec->errcxappctx != 0 && ec->errcxappctx->usage_app_name != 0)
+ {
+ char buf[128];
+ char buf2[128];
+ erradef argv[1];
+
+ /* get the parameterized usage message */
+ errmsg(ec, buf, (uint)sizeof(buf), ERR_TRUSPARM);
+
+ /* format in the application name */
+ argv[0].errastr = (char *)ec->errcxappctx->usage_app_name;
+ errfmt(buf2, (int)sizeof(buf2), buf, 1, argv);
+
+ /* display it */
+ trdptf("%s\n", buf2);
+
+ /* start at the next message */
+ ++first;
+ }
+
+ /* display the main option list messages */
+ trdusage_show_range(ec, first, ERR_TRUSL);
+
+ /* display the OS-specific option messages, if any */
+ trdusage_show_range(ec, ERR_TRUS_OS_FIRST, ERR_TRUS_OS_LAST);
+
+ /* display the usage footer messages */
+ trdusage_range(ec, ERR_TRUSFT1, ERR_TRUSFTL);
+}
+
+/*
+ * display -s suboptions
+ */
+static void trdusage_s(errcxdef *ec)
+{
+ trdusage_range(ec, ERR_TRSUS1, ERR_TRSUSL);
+}
+
+
+static void trdmain1(errcxdef *ec, int argc, char *argv[],
+ appctxdef *appctx, char *save_ext)
+{
+ osfildef *swapfp = (osfildef *)0;
+ runcxdef runctx;
+ bifcxdef bifctx;
+ voccxdef vocctx;
+ void (*bif[100])(struct bifcxdef *, int);
+ mcmcxdef *mctx = 0;
+ mcmcx1def *globalctx = 0;
+ dbgcxdef dbg;
+ supcxdef supctx;
+ char *swapname = 0;
+ char swapbuf[OSFNMAX];
+ char **argp;
+ char *arg;
+ char *infile;
+ char infile_abs[OSFNMAX]; /* fully-qualified input file name */
+ char infile_path[OSFNMAX]; /* absolute path to input file */
+ const char *exefile; /* try with executable file if no infile */
+ ulong swapsize = 0xffffffffL; /* allow unlimited swap space */
+ int swapena = OS_DEFAULT_SWAP_ENABLED; /* swapping enabled? */
+ int i;
+ int pause = FALSE; /* pause after finishing game */
+ fiolcxdef fiolctx;
+ noreg int loadopen = FALSE;
+ char inbuf[OSFNMAX];
+ ulong cachelimit = 0xffffffff;
+ ushort undosiz = TRD_UNDOSIZ; /* default undo context size 16k */
+ objucxdef *undoptr = 0;
+ uint flags; /* flags used to write the file we're reading */
+ objnum preinit; /* preinit object, if we need to execute it */
+ uint heapsiz = TRD_HEAPSIZ;
+ uint stksiz = TRD_STKSIZ;
+ runsdef *mystack;
+ uchar *myheap;
+ extern osfildef *cmdfile; /* hacky v1 qa interface - command log fp */
+ extern osfildef *logfp; /* hacky v1 qa interface - output log fp */
+ int preload = FALSE; /* TRUE => preload all objects */
+ ulong totsize;
+ extern voccxdef *main_voc_ctx;
+ int safety_read, safety_write; /* file I/O safety level */
+ char *restore_file = 0; /* .SAV file to restore */
+ char *charmap = 0; /* character map file */
+ int charmap_none; /* explicitly do not use a character set */
+ int doublespace = TRUE; /* formatter double-space setting */
+
+ NOREG((&loadopen))
+
+ /* initialize the output formatter */
+ out_init();
+
+ /* set safety level to 2 by default - read any/write current dir only */
+ safety_read = safety_write = 2;
+
+ /* no -ctab- yet */
+ charmap_none = FALSE;
+
+ /* parse arguments */
+ for (i = 1, argp = argv + 1 ; i < argc ; ++argp, ++i)
+ {
+ arg = *argp;
+ if (*arg == '-')
+ {
+ switch(*(arg+1))
+ {
+ case 'c':
+ if (!strcmp(arg+1, "ctab"))
+ {
+ /* get the character mapping table */
+ charmap = cmdarg(ec, &argp, &i, argc, 4, trdusage);
+ }
+ else if (!strcmp(arg+1, "ctab-"))
+ {
+ /* use the default mapping */
+ charmap_none = TRUE;
+ }
+ else
+ trdusage(ec);
+ break;
+
+ case 'r':
+ /* restore a game */
+ restore_file = cmdarg(ec, &argp, &i, argc, 1, trdusage);
+ break;
+
+ case 'i':
+ qasopn(cmdarg(ec, &argp, &i, argc, 1, trdusage), TRUE);
+ break;
+
+ case 'o':
+ cmdfile = osfopwt(cmdarg(ec, &argp, &i, argc, 1, trdusage),
+ OSFTCMD);
+ break;
+
+ case 'l':
+ logfp = osfopwt(cmdarg(ec, &argp, &i, argc, 1, trdusage),
+ OSFTCMD);
+ break;
+
+ case 'p':
+ if (!scumm_stricmp(arg, "-plain"))
+ {
+ os_plain();
+ break;
+ }
+ pause = cmdtog(ec, pause, arg, 1, trdusage);
+ break;
+
+ case 'd':
+ if (!scumm_strnicmp(arg, "-double", 7))
+ {
+ /* get the argument value */
+ doublespace = cmdtog(ec, doublespace, arg, 6, trdusage);
+
+ /* set the double-space mode in the formatter */
+ out_set_doublespace(doublespace);
+ break;
+ }
+ break;
+
+ case 's':
+ {
+ char *p;
+
+ /* get the option */
+ p = cmdarg(ec, &argp, &i, argc, 1, trdusage);
+
+ /* if they're asking for help, display detailed usage */
+ if (*p == '?')
+ trdusage_s(ec);
+
+ /* get the safety level from the argument */
+ safety_read = *p - '0';
+ safety_write = (*(p+1) != '\0' ? *(p+1) - '0' :
+ safety_read);
+
+ /* range-check the values */
+ if (safety_read < 0 || safety_read > 4
+ || safety_write < 0 || safety_write > 4)
+ trdusage_s(ec);
+
+ /* tell the host system about the setting */
+ if (appctx != 0 && appctx->set_io_safety_level != 0)
+ (*appctx->set_io_safety_level)
+ (appctx->io_safety_level_ctx,
+ safety_read, safety_write);
+ }
+ break;
+
+ case 'm':
+ switch(*(arg + 2))
+ {
+ case 's':
+ stksiz = atoi(cmdarg(ec, &argp, &i, argc, 2, trdusage));
+ break;
+
+ case 'h':
+ heapsiz = atoi(cmdarg(ec, &argp, &i, argc, 2, trdusage));
+ break;
+
+ default:
+ cachelimit = atol(cmdarg(ec, &argp, &i, argc, 1,
+ trdusage));
+ break;
+ }
+ break;
+
+ case 't':
+ /* swap file options: -tf file, -ts size, -t- (no swap) */
+ switch(*(arg+2))
+ {
+ case 'f':
+ swapname = cmdarg(ec, &argp, &i, argc, 2, trdusage);
+ break;
+
+ case 's':
+ swapsize = atol(cmdarg(ec, &argp, &i, argc, 2, trdusage));
+ break;
+
+ case 'p':
+ preload = cmdtog(ec, preload, arg, 2, trdusage);
+ break;
+
+ default:
+ swapena = cmdtog(ec, swapena, arg, 1, trdusage);
+ break;
+ }
+ break;
+
+ case 'u':
+ undosiz = atoi(cmdarg(ec, &argp, &i, argc, 1, trdusage));
+ break;
+
+ default:
+ trdusage(ec);
+ }
+ }
+ else break;
+ }
+
+ /* presume we won't take the .gam from the application executable */
+ exefile = 0;
+
+ /* get input name argument, and make sure it's the last argument */
+ if (i == argc)
+ {
+ osfildef *fp;
+ ulong curpos;
+ ulong endpos;
+ int use_exe;
+
+ /*
+ * There's no input name argument, so we need to find the game
+ * to play some other way. First, check to see if we have a
+ * game to restore, and if so whether it has the .GAM name
+ * encoded into it. Next, look to see if there's a game
+ * attached to the executable file; if so, use it. If not, see
+ * if the host system wants to provide a name through its
+ * callback.
+ */
+
+ /* presume we won't find a game attached to the executable file */
+ infile = 0;
+ use_exe = FALSE;
+
+ /*
+ * see if we have a saved game to restore, and it specifies the
+ * GAM file that saved it
+ */
+ if (restore_file != 0)
+ {
+ /* try getting the game name from the restore file */
+ if (fiorso_getgame(restore_file, inbuf, sizeof(inbuf)))
+ {
+ /* got it - use this file */
+ infile = inbuf;
+ }
+ }
+
+ /*
+ * it that didn't work, try to read from os-dependent part of
+ * program being executed
+ */
+ if (infile == 0)
+ {
+ /* try opening the executable file */
+ exefile = (argv && argv[0] ? argv[0] : "TRX");
+ fp = os_exeseek(exefile, "TGAM");
+ if (fp != 0)
+ {
+ /* see if there's a game file attached to the executable */
+ curpos = osfpos(fp);
+ osfseek(fp, 0L, OSFSK_END);
+ endpos = osfpos(fp);
+ osfcls(fp);
+
+ /* if we found it, use it */
+ if (endpos != curpos)
+ use_exe = TRUE;
+ }
+ }
+
+ /*
+ * if we didn't find a game in the executable, try the host
+ * system callback
+ */
+ if (infile == 0 && !use_exe)
+ {
+ /*
+ * ask the host system callback what to do - if we don't
+ * have a host system callback, or the callback
+ */
+ if (appctx != 0 && appctx->get_game_name != 0)
+ {
+ /* call the host system callback */
+ if ((*appctx->get_game_name)(appctx->get_game_name_ctx,
+ inbuf, sizeof(inbuf)))
+ {
+ /* the host system provided a name - use it */
+ infile = inbuf;
+ }
+ else
+ {
+ /*
+ * the host didn't provide a name - simply display a
+ * message indicating that no game file has been
+ * chosen, and return
+ */
+ trdptf("\n");
+ trdptf("(No game has been selected.)\n");
+ return;
+ }
+ }
+ else
+ {
+ /*
+ * we've run out of ways to get a filename - give the
+ * user the usage message and quit
+ */
+ trdusage(ec);
+ }
+ }
+ }
+ else
+ {
+ infile = *argp;
+ if (i + 1 != argc)
+ trdusage(ec);
+
+#ifndef OS_HATES_EXTENSIONS
+ /*
+ * If original name exists, use it; otherwise, try adding .GAM.
+ * Note that this code is ifdef'd so that platforms that don't
+ * use filename extensions in the manner conventional for DOS
+ * and Unix won't use this code.
+ */
+ if (osfacc(infile))
+ {
+ strcpy(inbuf, infile);
+ os_defext(inbuf, "gam");
+ infile = inbuf;
+ }
+#endif /* !defined(OS_HATES_EXTENSIONS) */
+ }
+
+ /* open up the swap file */
+ if (swapena && swapsize)
+ {
+ swapfp = os_create_tempfile(swapname, swapbuf);
+ if (swapname == 0) swapname = swapbuf;
+ if (swapfp == 0) errsig(ec, ERR_OPSWAP);
+ }
+
+ /* load the character map */
+ if (charmap_none)
+ cmap_override();
+ else if (cmap_load(charmap))
+ errsig(ec, ERR_INVCMAP);
+
+ ERRBEGIN(ec)
+
+ /* initialize cache manager context */
+ globalctx = mcmini(cachelimit, 128, swapsize, swapfp, swapname, ec);
+ mctx = mcmcini(globalctx, 128, fioldobj, &fiolctx,
+ objrevert, (void *)0);
+ mctx->mcmcxrvc = mctx;
+
+ /* set up an undo context */
+ if (undosiz)
+ undoptr = objuini(mctx, undosiz, vocdundo, vocdusz, &vocctx);
+ else
+ undoptr = (objucxdef *)0;
+
+ /* set up vocabulary context */
+ vocini(&vocctx, ec, mctx, &runctx, undoptr, 100, 100, 200);
+
+ /*
+ * save a pointer to the voc context globally, so that certain
+ * external routines (such as Unix-style signal handlers) can reach
+ * it
+ */
+ main_voc_ctx = &vocctx;
+
+ /* allocate stack and heap */
+ totsize = (ulong)stksiz * (ulong)sizeof(runsdef);
+ if (totsize != (size_t)totsize)
+ errsig1(ec, ERR_STKSIZE, ERRTINT, (uint)(65535/sizeof(runsdef)));
+ mystack = (runsdef *)mchalo(ec, (size_t)totsize, "runtime stack");
+ myheap = mchalo(ec, heapsiz, "runtime heap");
+
+ /* get the absolute path for the input file */
+ if (infile != 0)
+ os_get_abs_filename(infile_abs, sizeof(infile_abs), infile);
+ else if (exefile != 0)
+ os_get_abs_filename(infile_abs, sizeof(infile_abs), exefile);
+ else
+ infile_abs[0] = '\0';
+ os_get_path_name(infile_path, sizeof(infile_path), infile_abs);
+
+ /* set up execution context */
+ runctx.runcxerr = ec;
+ runctx.runcxmem = mctx;
+ runctx.runcxstk = mystack;
+ runctx.runcxstop = &mystack[stksiz];
+ runctx.runcxsp = mystack;
+ runctx.runcxbp = mystack;
+ runctx.runcxheap = myheap;
+ runctx.runcxhp = myheap;
+ runctx.runcxhtop = &myheap[heapsiz];
+ runctx.runcxundo = undoptr;
+ runctx.runcxbcx = &bifctx;
+ runctx.runcxbi = bif;
+ runctx.runcxtio = (tiocxdef *)0;
+ runctx.runcxdbg = &dbg;
+ runctx.runcxvoc = &vocctx;
+ runctx.runcxdmd = supcont;
+ runctx.runcxdmc = &supctx;
+ runctx.runcxext = 0;
+ runctx.runcxgamename = infile;
+ runctx.runcxgamepath = infile_path;
+
+ /* set up setup context */
+ supctx.supcxerr = ec;
+ supctx.supcxmem = mctx;
+ supctx.supcxtab = (tokthdef *)0;
+ supctx.supcxbuf = (uchar *)0;
+ supctx.supcxlen = 0;
+ supctx.supcxvoc = &vocctx;
+ supctx.supcxrun = &runctx;
+
+ /* set up debug context */
+ dbg.dbgcxtio = (tiocxdef *)0;
+ dbg.dbgcxmem = mctx;
+ dbg.dbgcxerr = ec;
+ dbg.dbgcxtab = (tokthdef *)0;
+ dbg.dbgcxfcn = 0;
+ dbg.dbgcxdep = 0;
+ dbg.dbgcxflg = 0;
+ dbg.dbgcxlin = (lindef *)0; /* no line sources yet */
+
+ /* set up built-in function context */
+ CLRSTRUCT(bifctx);
+ bifctx.bifcxerr = ec;
+ bifctx.bifcxrun = &runctx;
+ bifctx.bifcxtio = (tiocxdef *)0;
+ bifctx.bifcxrnd = 0;
+ bifctx.bifcxrndset = FALSE;
+ bifctx.bifcxappctx = appctx;
+ bifctx.bifcxsafetyr = safety_read;
+ bifctx.bifcxsafetyw = safety_write;
+ bifctx.bifcxsavext = save_ext;
+
+ /* initialize the regular expression parser context */
+ re_init(&bifctx.bifcxregex, ec);
+
+ /* add the built-in functions, keywords, etc */
+ supbif(&supctx, bif, (int)(sizeof(bif)/sizeof(bif[0])));
+
+ /* set up status line hack */
+ runistat(&vocctx, &runctx, (tiocxdef *)0);
+
+ /* turn on the "busy" cursor before loading */
+ os_csr_busy(TRUE);
+
+ /* read the game from the binary file */
+ fiord(mctx, &vocctx, (struct tokcxdef *)0,
+ infile, exefile, &fiolctx, &preinit, &flags,
+ (struct tokpdef *)0, (uchar **)0, (uint *)0, (uint *)0,
+ (preload ? 2 : 0), appctx, argv[0]);
+ loadopen = TRUE;
+
+ /* turn off the "busy" cursor */
+ os_csr_busy(FALSE);
+
+ /* play the game */
+ plygo(&runctx, &vocctx, (tiocxdef *)0, preinit, restore_file);
+
+ /* close load file */
+ fiorcls(&fiolctx);
+
+ if (pause)
+ {
+ trdptf("[press a key to exit]");
+ os_waitc();
+ trdptf("\n");
+ }
+
+ /* close and delete swapfile, if one was opened */
+ trd_close_swapfile(&runctx);
+
+ /* make sure the script file is closed, if we have one */
+ qasclose();
+
+ ERRCLEAN(ec)
+ /* close and delete swapfile, if one was opened */
+ trd_close_swapfile(&runctx);
+
+ /* close the load file if one was opened */
+ if (loadopen)
+ fiorcls(&fiolctx);
+
+ /* vocctx is going out of scope - forget the global reference to it */
+ main_voc_ctx = 0;
+
+ /* delete the voc context */
+ vocterm(&vocctx);
+
+ /* delete the undo context */
+ if (undoptr != 0)
+ objuterm(undoptr);
+
+ /* release the object cache structures */
+ if (mctx != 0)
+ mcmcterm(mctx);
+ if (globalctx != 0)
+ mcmterm(globalctx);
+ ERRENDCLN(ec)
+
+ /* vocctx is going out of scope - forget the global reference to it */
+ main_voc_ctx = 0;
+
+ /* delete the voc contxt */
+ vocterm(&vocctx);
+
+ /* delete the undo context */
+ if (undoptr != 0)
+ objuterm(undoptr);
+
+ /* release the object cache structures */
+ mcmcterm(mctx);
+ mcmterm(globalctx);
+}
+
+/*
+ * If the OS configuration so desires, use a less technical format for
+ * run-time error messages by leaving out the numeric error code. Note
+ * that we'll only do this if the error messages are linked directly
+ * into the run-time, since we need the numeric code as a last resort
+ * when the error message may not be present.
+ */
+#ifdef OS_SKIP_ERROR_CODES
+# ifdef ERR_LINK_MESSAGES
+# define TRDLOGERR_PREFIX "\n[An error has occurred within TADS: "
+# endif
+#endif
+
+/*
+ * If we didn't define a different error prefix format, use the default
+ * format with the numeric error code.
+ */
+#ifndef TRDLOGERR_PREFIX
+# define TRDLOGERR_PREFIX "\n[%s-%d: "
+#endif
+
+/* log an error */
+static void trdlogerr(void *ctx0, char *fac, int err,
+ int argc, erradef *argv)
+{
+ 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);
+}
+
+
+/*
+ * close and delete the swap file
+ */
+void trd_close_swapfile(runcxdef *runctx)
+{
+ extern voccxdef *main_voc_ctx;
+ mcmcxdef *mctx;
+ mcmcx1def *globalctx;
+ mcscxdef *mcsctx;
+
+ /* if no run context was supplied, find it from the main voc context */
+ if (runctx == 0)
+ {
+ /* if there is no main voc context, we're out of luck */
+ if (main_voc_ctx == 0)
+ return;
+
+ /* get the run context */
+ runctx = main_voc_ctx->voccxrun;
+ }
+
+ /* get the other relevant contexts */
+ mctx = runctx->runcxmem;
+ globalctx = mctx->mcmcxgl;
+ mcsctx = &globalctx->mcmcxswc;
+
+ /* if we have a swap file open, close it */
+ if (mcsctx->mcscxfp != 0)
+ {
+ /* close the file */
+ osfcls(mcsctx->mcscxfp);
+
+ /* forget about the file, so we don't try to close it again */
+ mcsctx->mcscxfp = (osfildef *)0;
+ }
+
+ /* if we have a filename, delete the file */
+ if (mcsctx->mcscxfname != 0)
+ {
+ /* delete the file */
+ osfdel_temp(mcsctx->mcscxfname);
+
+ /* forget the filename, so we don't try to delete the file again */
+ mchfre(mcsctx->mcscxfname);
+ mcsctx->mcscxfname = 0;
+ }
+}
+
+/* main - called by os main after setting up arguments */
+int trdmain(int argc, char *argv[], appctxdef *appctx, char *save_ext)
+{
+ errcxdef errctx;
+ int err;
+ osfildef *fp;
+
+ errctx.errcxlog = trdlogerr;
+ errctx.errcxlgc = &errctx;
+ errctx.errcxfp = (osfildef *)0;
+ errctx.errcxofs = 0;
+ errctx.errcxappctx = appctx;
+ fp = oserrop(argv[0]);
+ errini(&errctx, fp);
+
+ /* copyright-date-string */
+#ifndef NO_T2_COPYRIGHT_NOTICE
+ trdptf("%s - A %s TADS %s Interpreter.\n",
+ G_tads_oem_app_name, G_tads_oem_display_mode,
+ TADS_RUNTIME_VERSION);
+ trdptf("%sopyright (c) 1993, 2012 by Michael J. Roberts.\n",
+ G_tads_oem_copyright_prefix ? "TADS c" : "C");
+ trdptf("%s\n", G_tads_oem_author);
+#endif
+
+ ERRBEGIN(&errctx)
+ trdmain1(&errctx, argc, argv, appctx, save_ext);
+ ERRCATCH(&errctx, err)
+ /*
+ * log the error, unless it's usage (in which case we logged it
+ * already) or we're simply quitting the game
+ */
+ if (err != ERR_USAGE && err != ERR_RUNQUIT)
+ errclog(&errctx);
+
+ /* close the error file */
+ if (errctx.errcxfp != 0)
+ osfcls(errctx.errcxfp);
+
+ /* pause before exiting if the OS desires it */
+ os_expause();
+
+ /* return failure unless we're simply quitting the game */
+ return (err == ERR_RUNQUIT ? OSEXSUCC : OSEXFAIL);
+ ERREND(&errctx)
+
+ /* close the error file if we opened it */
+ if (errctx.errcxfp != 0)
+ osfcls(errctx.errcxfp);
+
+ /* successful completion */
+ return(OSEXSUCC);
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/tads2.cpp b/engines/glk/tads/tads2/tads2.cpp
index e1f59212b0..6203900642 100644
--- a/engines/glk/tads/tads2/tads2.cpp
+++ b/engines/glk/tads/tads2/tads2.cpp
@@ -21,12 +21,8 @@
*/
#include "glk/tads/tads2/tads2.h"
-#include "glk/tads/tads2/built_in.h"
-#include "glk/tads/tads2/debug.h"
-#include "glk/tads/tads2/file_io.h"
-#include "glk/tads/tads2/post_compilation.h"
-#include "glk/tads/tads2/run.h"
-#include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/tads2/appctx.h"
+#include "glk/tads/tads2/runtime_app.h"
namespace Glk {
namespace TADS {
@@ -36,549 +32,11 @@ TADS2::TADS2(OSystem *syst, const GlkGameDescription &gameDesc) : TADS(syst, gam
}
void TADS2::runGame() {
- errcxdef errctx;
- errctx.errcxlgc = &errctx;
- errctx.errcxfp = nullptr;
- errctx.errcxofs = 0;
- errctx.errcxappctx = nullptr;
+ char name[255];
+ strcpy(name, getFilename().c_str());
+ char *argv[2] = { nullptr, name };
- /* copyright-date-string */
-#ifdef T2_COPYRIGHT_NOTICE
- trdptf("%s - A %s TADS %s Interpreter.\n",
- G_tads_oem_app_name, G_tads_oem_display_mode,
- TADS_RUNTIME_VERSION);
- trdptf("%sopyright (c) 1993, 2012 by Michael J. Roberts.\n",
- G_tads_oem_copyright_prefix ? "TADS c" : "C");
- trdptf("%s\n", G_tads_oem_author);
-#endif
-
- trdmain1(&errctx);
-}
-
-void TADS2::trdmain1(errcxdef *errctx) {
-#ifdef TODO
- osfildef *swapfp = (osfildef *)0;
- runcxdef runctx;
- bifcxdef bifctx;
- voccxdef vocctx;
- void(*bif[100])(bifcxdef *, int);
- mcmcxdef *mctx = 0;
- mcmcx1def *globalctx = 0;
- dbgcxdef dbg;
- supcxdef supctx;
- char *swapname = 0;
- char swapbuf[OSFNMAX];
- char **argp;
- char *arg;
- char *infile;
- char infile_abs[OSFNMAX]; /* fully-qualified input file name */
- char infile_path[OSFNMAX]; /* absolute path to input file */
- char *exefile; /* try with executable file if no infile */
- ulong swapsize = 0xffffffffL; /* allow unlimited swap space */
- int swapena = OS_DEFAULT_SWAP_ENABLED; /* swapping enabled? */
- int i;
- int pause = FALSE; /* pause after finishing game */
- fiolcxdef fiolctx;
- noreg int loadopen = FALSE;
- char inbuf[OSFNMAX];
- ulong cachelimit = 0xffffffff;
- ushort undosiz = TRD_UNDOSIZ; /* default undo context size 16k */
- objucxdef *undoptr = 0;
- uint flags; /* flags used to write the file we're reading */
- objnum preinit; /* preinit object, if we need to execute it */
- uint heapsiz = TRD_HEAPSIZ;
- uint stksiz = TRD_STKSIZ;
- runsdef *mystack;
- uchar *myheap;
- extern osfildef *cmdfile; /* hacky v1 qa interface - command log fp */
- extern osfildef *logfp; /* hacky v1 qa interface - output log fp */
- int preload = FALSE; /* TRUE => preload all objects */
- ulong totsize;
- extern voccxdef *main_voc_ctx;
- int safety_read, safety_write; /* file I/O safety level */
- char *restore_file = 0; /* .SAV file to restore */
- char *charmap = 0; /* character map file */
- int charmap_none; /* explicitly do not use a character set */
- int doublespace = TRUE; /* formatter double-space setting */
-
- NOREG((&loadopen))
-
- /* initialize the output formatter */
- out_init();
-
- /* set safety level to 2 by default - read any/write current dir only */
- safety_read = safety_write = 2;
-
- /* no -ctab- yet */
- charmap_none = FALSE;
-
- /* parse arguments */
- for (i = 1, argp = argv + 1; i < argc; ++argp, ++i)
- {
- arg = *argp;
- if (*arg == '-')
- {
- switch (*(arg + 1))
- {
- case 'c':
- if (!strcmp(arg + 1, "ctab"))
- {
- /* get the character mapping table */
- charmap = cmdarg(ec, &argp, &i, argc, 4, trdusage);
- }
- else if (!strcmp(arg + 1, "ctab-"))
- {
- /* use the default mapping */
- charmap_none = TRUE;
- }
- else
- trdusage(ec);
- break;
-
- case 'r':
- /* restore a game */
- restore_file = cmdarg(ec, &argp, &i, argc, 1, trdusage);
- break;
-
- case 'i':
- qasopn(cmdarg(ec, &argp, &i, argc, 1, trdusage), TRUE);
- break;
-
- case 'o':
- cmdfile = osfopwt(cmdarg(ec, &argp, &i, argc, 1, trdusage),
- OSFTCMD);
- break;
-
- case 'l':
- logfp = osfopwt(cmdarg(ec, &argp, &i, argc, 1, trdusage),
- OSFTCMD);
- break;
-
- case 'p':
- if (!stricmp(arg, "-plain"))
- {
- os_plain();
- break;
- }
- pause = cmdtog(ec, pause, arg, 1, trdusage);
- break;
-
- case 'd':
- if (!strnicmp(arg, "-double", 7))
- {
- /* get the argument value */
- doublespace = cmdtog(ec, doublespace, arg, 6, trdusage);
-
- /* set the double-space mode in the formatter */
- out_set_doublespace(doublespace);
- break;
- }
- break;
-
- case 's':
- {
- char *p;
-
- /* get the option */
- p = cmdarg(ec, &argp, &i, argc, 1, trdusage);
-
- /* if they're asking for help, display detailed usage */
- if (*p == '?')
- trdusage_s(ec);
-
- /* get the safety level from the argument */
- safety_read = *p - '0';
- safety_write = (*(p + 1) != '\0' ? *(p + 1) - '0' :
- safety_read);
-
- /* range-check the values */
- if (safety_read < 0 || safety_read > 4
- || safety_write < 0 || safety_write > 4)
- trdusage_s(ec);
-
- /* tell the host system about the setting */
- if (appctx != 0 && appctx->set_io_safety_level != 0)
- (*appctx->set_io_safety_level)
- (appctx->io_safety_level_ctx,
- safety_read, safety_write);
- }
- break;
-
- case 'm':
- switch (*(arg + 2))
- {
- case 's':
- stksiz = atoi(cmdarg(ec, &argp, &i, argc, 2, trdusage));
- break;
-
- case 'h':
- heapsiz = atoi(cmdarg(ec, &argp, &i, argc, 2, trdusage));
- break;
-
- default:
- cachelimit = atol(cmdarg(ec, &argp, &i, argc, 1,
- trdusage));
- break;
- }
- break;
-
- case 't':
- /* swap file options: -tf file, -ts size, -t- (no swap) */
- switch (*(arg + 2))
- {
- case 'f':
- swapname = cmdarg(ec, &argp, &i, argc, 2, trdusage);
- break;
-
- case 's':
- swapsize = atol(cmdarg(ec, &argp, &i, argc, 2, trdusage));
- break;
-
- case 'p':
- preload = cmdtog(ec, preload, arg, 2, trdusage);
- break;
-
- default:
- swapena = cmdtog(ec, swapena, arg, 1, trdusage);
- break;
- }
- break;
-
- case 'u':
- undosiz = atoi(cmdarg(ec, &argp, &i, argc, 1, trdusage));
- break;
-
- default:
- trdusage(ec);
- }
- }
- else break;
- }
-
- /* presume we won't take the .gam from the application executable */
- exefile = 0;
-
- /* get input name argument, and make sure it's the last argument */
- if (i == argc)
- {
- osfildef *fp;
- ulong curpos;
- ulong endpos;
- int use_exe;
-
- /*
- * There's no input name argument, so we need to find the game
- * to play some other way. First, check to see if we have a
- * game to restore, and if so whether it has the .GAM name
- * encoded into it. Next, look to see if there's a game
- * attached to the executable file; if so, use it. If not, see
- * if the host system wants to provide a name through its
- * callback.
- */
-
- /* presume we won't find a game attached to the executable file */
- infile = 0;
- use_exe = FALSE;
-
- /*
- * see if we have a saved game to restore, and it specifies the
- * GAM file that saved it
- */
- if (restore_file != 0)
- {
- /* try getting the game name from the restore file */
- if (fiorso_getgame(restore_file, inbuf, sizeof(inbuf)))
- {
- /* got it - use this file */
- infile = inbuf;
- }
- }
-
- /*
- * it that didn't work, try to read from os-dependent part of
- * program being executed
- */
- if (infile == 0)
- {
- /* try opening the executable file */
- exefile = (argv && argv[0] ? argv[0] : "TRX");
- fp = os_exeseek(exefile, "TGAM");
- if (fp != 0)
- {
- /* see if there's a game file attached to the executable */
- curpos = osfpos(fp);
- osfseek(fp, 0L, OSFSK_END);
- endpos = osfpos(fp);
- osfcls(fp);
-
- /* if we found it, use it */
- if (endpos != curpos)
- use_exe = TRUE;
- }
- }
-
- /*
- * if we didn't find a game in the executable, try the host
- * system callback
- */
- if (infile == 0 && !use_exe)
- {
- /*
- * ask the host system callback what to do - if we don't
- * have a host system callback, or the callback
- */
- if (appctx != 0 && appctx->get_game_name != 0)
- {
- /* call the host system callback */
- if ((*appctx->get_game_name)(appctx->get_game_name_ctx,
- inbuf, sizeof(inbuf)))
- {
- /* the host system provided a name - use it */
- infile = inbuf;
- }
- else
- {
- /*
- * the host didn't provide a name - simply display a
- * message indicating that no game file has been
- * chosen, and return
- */
- trdptf("\n");
- trdptf("(No game has been selected.)\n");
- return;
- }
- }
- else
- {
- /*
- * we've run out of ways to get a filename - give the
- * user the usage message and quit
- */
- trdusage(ec);
- }
- }
- }
- else
- {
- infile = *argp;
- if (i + 1 != argc)
- trdusage(ec);
-
-#ifndef OS_HATES_EXTENSIONS
- /*
- * If original name exists, use it; otherwise, try adding .GAM.
- * Note that this code is ifdef'd so that platforms that don't
- * use filename extensions in the manner conventional for DOS
- * and Unix won't use this code.
- */
- if (osfacc(infile))
- {
- strcpy(inbuf, infile);
- os_defext(inbuf, "gam");
- infile = inbuf;
- }
-#endif /* !defined(OS_HATES_EXTENSIONS) */
- }
-
- /* open up the swap file */
- if (swapena && swapsize)
- {
- swapfp = os_create_tempfile(swapname, swapbuf);
- if (swapname == 0) swapname = swapbuf;
- if (swapfp == 0) errsig(ec, ERR_OPSWAP);
- }
-
- /* load the character map */
- if (charmap_none)
- cmap_override();
- else if (cmap_load(charmap))
- errsig(ec, ERR_INVCMAP);
-
- ERRBEGIN(ec)
-
- /* initialize cache manager context */
- globalctx = mcmini(cachelimit, 128, swapsize, swapfp, swapname, ec);
- mctx = mcmcini(globalctx, 128, fioldobj, &fiolctx,
- objrevert, (void *)0);
- mctx->mcmcxrvc = mctx;
-
- /* set up an undo context */
- if (undosiz)
- undoptr = objuini(mctx, undosiz, vocdundo, vocdusz, &vocctx);
- else
- undoptr = (objucxdef *)0;
-
- /* set up vocabulary context */
- vocini(&vocctx, ec, mctx, &runctx, undoptr, 100, 100, 200);
-
- /*
- * save a pointer to the voc context globally, so that certain
- * external routines (such as Unix-style signal handlers) can reach
- * it
- */
- main_voc_ctx = &vocctx;
-
- /* allocate stack and heap */
- totsize = (ulong)stksiz * (ulong)sizeof(runsdef);
- if (totsize != (size_t)totsize)
- errsig1(ec, ERR_STKSIZE, ERRTINT, (uint)(65535 / sizeof(runsdef)));
- mystack = (runsdef *)mchalo(ec, (size_t)totsize, "runtime stack");
- myheap = mchalo(ec, heapsiz, "runtime heap");
-
- /* get the absolute path for the input file */
- if (infile != 0)
- os_get_abs_filename(infile_abs, sizeof(infile_abs), infile);
- else if (exefile != 0)
- os_get_abs_filename(infile_abs, sizeof(infile_abs), exefile);
- else
- infile_abs[0] = '\0';
- os_get_path_name(infile_path, sizeof(infile_path), infile_abs);
-
- /* set up execution context */
- runctx.runcxerr = ec;
- runctx.runcxmem = mctx;
- runctx.runcxstk = mystack;
- runctx.runcxstop = &mystack[stksiz];
- runctx.runcxsp = mystack;
- runctx.runcxbp = mystack;
- runctx.runcxheap = myheap;
- runctx.runcxhp = myheap;
- runctx.runcxhtop = &myheap[heapsiz];
- runctx.runcxundo = undoptr;
- runctx.runcxbcx = &bifctx;
- runctx.runcxbi = bif;
- runctx.runcxtio = (tiocxdef *)0;
- runctx.runcxdbg = &dbg;
- runctx.runcxvoc = &vocctx;
- runctx.runcxdmd = supcont;
- runctx.runcxdmc = &supctx;
- runctx.runcxext = 0;
- runctx.runcxgamename = infile;
- runctx.runcxgamepath = infile_path;
-
- /* set up setup context */
- supctx.supcxerr = ec;
- supctx.supcxmem = mctx;
- supctx.supcxtab = (tokthdef *)0;
- supctx.supcxbuf = (uchar *)0;
- supctx.supcxlen = 0;
- supctx.supcxvoc = &vocctx;
- supctx.supcxrun = &runctx;
-
- /* set up debug context */
- dbg.dbgcxtio = (tiocxdef *)0;
- dbg.dbgcxmem = mctx;
- dbg.dbgcxerr = ec;
- dbg.dbgcxtab = (tokthdef *)0;
- dbg.dbgcxfcn = 0;
- dbg.dbgcxdep = 0;
- dbg.dbgcxflg = 0;
- dbg.dbgcxlin = (lindef *)0; /* no line sources yet */
-
- /* set up built-in function context */
- CLRSTRUCT(bifctx);
- bifctx.bifcxerr = ec;
- bifctx.bifcxrun = &runctx;
- bifctx.bifcxtio = (tiocxdef *)0;
- bifctx.bifcxrnd = 0;
- bifctx.bifcxrndset = FALSE;
- bifctx.bifcxappctx = appctx;
- bifctx.bifcxsafetyr = safety_read;
- bifctx.bifcxsafetyw = safety_write;
- bifctx.bifcxsavext = save_ext;
-
- /* initialize the regular expression parser context */
- re_init(&bifctx.bifcxregex, ec);
-
- /* add the built-in functions, keywords, etc */
- supbif(&supctx, bif, (int)(sizeof(bif) / sizeof(bif[0])));
-
- /* set up status line hack */
- runistat(&vocctx, &runctx, (tiocxdef *)0);
-
- /* turn on the "busy" cursor before loading */
- os_csr_busy(TRUE);
-
- /* read the game from the binary file */
- fiord(mctx, &vocctx, (struct tokcxdef *)0,
- infile, exefile, &fiolctx, &preinit, &flags,
- (struct tokpdef *)0, (uchar **)0, (uint *)0, (uint *)0,
- (preload ? 2 : 0), appctx, argv[0]);
- loadopen = TRUE;
-
- /* turn off the "busy" cursor */
- os_csr_busy(FALSE);
-
- /* play the game */
- plygo(&runctx, &vocctx, (tiocxdef *)0, preinit, restore_file);
-
- /* close load file */
- fiorcls(&fiolctx);
-
- if (pause)
- {
- trdptf("[press a key to exit]");
- os_waitc();
- trdptf("\n");
- }
-
- /* close and delete swapfile, if one was opened */
- trd_close_swapfile(&runctx);
-
- /* make sure the script file is closed, if we have one */
- qasclose();
-
- ERRCLEAN(ec)
- /* close and delete swapfile, if one was opened */
- trd_close_swapfile(&runctx);
-
- /* close the load file if one was opened */
- if (loadopen)
- fiorcls(&fiolctx);
-
- /* vocctx is going out of scope - forget the global reference to it */
- main_voc_ctx = 0;
-
- /* delete the voc context */
- vocterm(&vocctx);
-
- /* delete the undo context */
- if (undoptr != 0)
- objuterm(undoptr);
-
- /* release the object cache structures */
- if (mctx != 0)
- mcmcterm(mctx);
- if (globalctx != 0)
- mcmterm(globalctx);
- ERRENDCLN(ec)
-
- /* vocctx is going out of scope - forget the global reference to it */
- main_voc_ctx = 0;
-
- /* delete the voc contxt */
- vocterm(&vocctx);
-
- /* delete the undo context */
- if (undoptr != 0)
- objuterm(undoptr);
-
- /* release the object cache structures */
- mcmcterm(mctx);
- mcmterm(globalctx);
-#endif
-}
-
-void TADS2::trdptf(const char *fmt, ...) {
- va_list va;
-
- // format the string */
- va_start(va, fmt);
- Common::String msg = Common::String::vformat(fmt, va);
- va_end(va);
-
- // print the formatted buffer
- os_printz(msg);
+ trdmain(2, argv, nullptr, ".sav");
}
} // End of namespace TADS2
diff --git a/engines/glk/tads/tads2/tads2.h b/engines/glk/tads/tads2/tads2.h
index 0ea1662b00..b92bfab4c6 100644
--- a/engines/glk/tads/tads2/tads2.h
+++ b/engines/glk/tads/tads2/tads2.h
@@ -24,53 +24,15 @@
#define GLK_TADS_TADS2
#include "glk/tads/tads.h"
-#include "glk/tads/tads2/error_handling.h"
-#include "glk/tads/tads2/appctx.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
-/*
- * Run-time version number
- */
-#define TADS_RUNTIME_VERSION "2.5.17"
-
-# define TRD_HEAPSIZ 4096
-# define TRD_STKSIZ 200
-# define TRD_UNDOSIZ (16 * 1024)
-# define TDD_HEAPSIZ 4096
-# define TDD_STKSIZ 200
-# define TDD_UNDOSIZ (16 * 1024)
-# define TDD_POOLSIZ (2 * 1024)
-# define TDD_LCLSIZ 0
-
-# define ERR_TRUS_OS_FIRST 100
-# define ERR_TRUS_OS_LAST 99
-
/**
* TADS 2 game interpreter
*/
class TADS2 : public TADS {
-private:
- // STUBS
- void os_printz(const Common::String &s) {}
- void tio_set_html_expansion(unsigned int html_char_val,
- const char *expansion, size_t expansion_len) {}
-private:
- /**
- * \defgroup trd
- * @{
- */
-
- void trdmain1(errcxdef *errctx);
-
- /**
- * printf-style formatting
- */
- void trdptf(const char *fmt, ...);
-
- /**@}*/
public:
/**
* Constructor
diff --git a/engines/glk/tads/tads2/tokenizer.cpp b/engines/glk/tads/tads2/tokenizer.cpp
index 3f9dc6ec27..fc0ff01846 100644
--- a/engines/glk/tads/tads2/tokenizer.cpp
+++ b/engines/glk/tads/tads2/tokenizer.cpp
@@ -149,63 +149,6 @@ void tok_write_defines(tokcxdef *ctx, osfildef *fp, errcxdef *ec)
}
}
-/*
- * 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)
{
diff --git a/engines/glk/tads/tads2/tokenizer_hash.cpp b/engines/glk/tads/tads2/tokenizer_hash.cpp
new file mode 100644
index 0000000000..2cbdcc26f7
--- /dev/null
+++ b/engines/glk/tads/tads2/tokenizer_hash.cpp
@@ -0,0 +1,336 @@
+/* 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.
+ *
+ */
+
+/* hashed symbol table manipulation functions
+ *
+ * Implements hashed symbol tables. A hashed symbol table stores
+ * a table of pointers to linked lists of symbols; each entry in
+ * the table corresponds to a hash value, allowing a large table
+ * to be searched for a symbol rapidly.
+ *
+ * Notes: Separated from tokenizer.cpp to allow the run-time to link the hashed
+ * symbol table functions without needing to link the rest of the
+ * lexical analysis subsystem.
+ */
+
+#include "glk/tads/tads2/tokenizer.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/os_glk.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+/* compute a hash value */
+uint tokhsh(char *nam)
+{
+ uint hash = 0;
+
+ while (*nam) hash = ((hash + *nam++) & (TOKHASHSIZE - 1));
+ return(hash);
+}
+
+/* for allocation - size of tokshdef without name portion */
+struct toksh1def
+{
+ tokthpdef tokshnxt;
+ toks1def tokshsc;
+};
+typedef struct toksh1def toksh1def;
+
+/* initialize a hashed symbol table */
+void tokthini(errcxdef *errctx, mcmcxdef *memctx, toktdef *toktab1)
+{
+ tokthdef *toktab = (tokthdef *)toktab1; /* convert to correct type */
+ int i;
+
+ CLRSTRUCT(*toktab);
+ toktab->tokthsc.toktfadd = tokthadd; /* set add-symbol method */
+ toktab->tokthsc.toktfsea = tokthsea; /* set search-table method */
+ toktab->tokthsc.toktfset = tokthset; /* update symbol */
+ toktab->tokthsc.toktfeach = toktheach; /* call fn for all symbols */
+ toktab->tokthsc.tokterr = errctx; /* set error handling context */
+ toktab->tokthmem = memctx; /* memory manager context */
+ toktab->tokthcpool = mcmalo(memctx, (ushort)TOKTHSIZE,
+ &toktab->tokthpool[0]);
+ toktab->tokthpcnt = 0;
+ toktab->tokthsize = TOKTHSIZE;
+
+ /* set hash table entries to point to nothing (MCMONINV) */
+ for (i = 0 ; i < TOKHASHSIZE ; ++i)
+ toktab->tokthhsh[i].tokthpobj = MCMONINV;
+}
+
+/* add a symbol to a hashed symbol table */
+void tokthadd(toktdef *toktab1, char *name, int namel,
+ int typ, int val, int hash)
+{
+ int siz = sizeof(toksh1def) + namel;
+ toksdef *sym;
+ tokshdef *symh;
+ tokthdef *toktab = (tokthdef *)toktab1;
+
+ if (toktab->tokthsize < siz)
+ {
+ mcmcxdef *mctx = toktab->tokthmem;
+
+ /* insufficient space in current pool; add a new pool */
+ if (toktab->tokthpcnt >= TOKPOOLMAX)
+ errsig(toktab->tokthsc.tokterr, ERR_MANYSYM);
+
+ /* unlock current pool page, and note its final size */
+ mcmunlck(mctx, toktab->tokthpool[toktab->tokthpcnt]);
+ toktab->tokthfinal[toktab->tokthpcnt] = toktab->tokthofs;
+
+ /* allocate a new pool page, and leave it locked */
+ toktab->tokthcpool = mcmalo(mctx, (ushort)TOKTHSIZE,
+ &toktab->tokthpool[++(toktab->tokthpcnt)]);
+ toktab->tokthsize = TOKTHSIZE;
+ toktab->tokthofs = 0;
+ }
+ symh = (tokshdef *)(toktab->tokthcpool + toktab->tokthofs);
+ sym = &symh->tokshsc;
+
+ /* link into list for this hash value */
+ OSCPYSTRUCT(symh->tokshnxt, toktab->tokthhsh[hash]);
+ toktab->tokthhsh[hash].tokthpobj = toktab->tokthpool[toktab->tokthpcnt];
+ toktab->tokthhsh[hash].tokthpofs = toktab->tokthofs;
+
+ /* fill in rest of toksdef */
+ sym->toksval = val;
+ sym->tokslen = namel;
+ sym->tokstyp = typ;
+ sym->tokshsh = hash;
+ sym->toksfr = 0;
+ memcpy(sym->toksnam, name, (size_t)namel);
+
+ /* update free pool pointer */
+ siz = osrndsz(siz);
+ toktab->tokthofs += siz;
+ if (siz > toktab->tokthsize) toktab->tokthsize = 0;
+ else toktab->tokthsize -= siz;
+}
+
+/*
+ * Scan a hash chain, calling a callback for each entry. If the
+ * callback returns TRUE for any symbol, we stop there, and return TRUE.
+ * If the callback returns FALSE for a symbol, we keep going. If the
+ * callback returns FALSE for all symbols, we return FALSE.
+ */
+static int tokthscan(tokthdef *tab, uint hash,
+ int (*cb)(void *, toksdef *, mcmon),
+ void *cbctx)
+{
+ tokshdef *symh;
+ toksdef *sym;
+ tokthpdef p;
+ tokthpdef nxt;
+ uchar *pg = nullptr;
+ mcmcxdef *mctx = tab->tokthmem;
+ mcmon curobj;
+
+ /* get first object, and lock its page if there is one */
+ OSCPYSTRUCT(p, tab->tokthhsh[hash]);
+ if ((curobj = p.tokthpobj) != MCMONINV)
+ pg = mcmlck(mctx, curobj);
+
+ /* look for a match using the callback */
+ for ( ; p.tokthpobj != MCMONINV ; OSCPYSTRUCT(p, nxt))
+ {
+ symh = (tokshdef *)(pg + p.tokthpofs);
+ sym = &symh->tokshsc;
+ OSCPYSTRUCT(nxt, symh->tokshnxt);
+
+ /* check for a match; copy to return buffer if found */
+ if ((*cb)(cbctx, sym, p.tokthpobj))
+ {
+ mcmunlck(mctx, p.tokthpobj);
+ return(TRUE);
+ }
+
+ /* if the next page is different from this one, get new lock */
+ if (nxt.tokthpobj != curobj && nxt.tokthpobj != MCMONINV)
+ {
+ mcmunlck(mctx, curobj);
+ curobj = nxt.tokthpobj;
+ pg = mcmlck(mctx, curobj);
+ }
+ }
+
+ /* unlock last object, if we had a lock at all */
+ if (curobj != MCMONINV) mcmunlck(mctx, curobj);
+ return(FALSE);
+}
+
+struct tokseadef
+{
+ char *tokseanam;
+ toksdef tokseasym;
+ toksdef *toksearet;
+ mcmcxdef *tokseamctx;
+};
+typedef struct tokseadef tokseadef;
+
+/* search callback */
+static int tokthsea1(void *ctx0, toksdef *sym, mcmon objn)
+{
+ tokseadef *ctx = (tokseadef *)ctx0;
+
+ VARUSED(objn);
+
+ if (sym->tokslen == ctx->tokseasym.tokslen &&
+ !memcmp(sym->toksnam, ctx->tokseanam, ctx->tokseasym.tokslen))
+ {
+ memcpy(ctx->toksearet, sym,
+ (size_t)(sizeof(toks1def) + ctx->tokseasym.tokslen));
+ return(TRUE);
+ }
+ else
+ return(FALSE);
+}
+
+/* search a hashed symbol table for a symbol */
+int tokthsea(toktdef *tab1, char *name, int namel, int hash, toksdef *ret)
+{
+ tokseadef ctx;
+
+ ctx.tokseanam = name;
+ ctx.tokseasym.tokslen = namel;
+ ctx.toksearet = ret;
+ return(tokthscan((tokthdef *)tab1, hash, tokthsea1, &ctx));
+}
+
+/* callback for tokthset */
+static int tokthset1(void *ctx0, toksdef *sym, mcmon objn)
+{
+ tokseadef *ctx = (tokseadef *)ctx0;
+
+ if (sym->tokslen == ctx->tokseasym.tokslen
+ && !memcmp(sym->toksnam, ctx->tokseasym.toksnam,
+ ctx->tokseasym.tokslen))
+ {
+ sym->toksval = ctx->tokseasym.toksval;
+ sym->tokstyp = ctx->tokseasym.tokstyp;
+
+ /* touch object, since it's been changed */
+ mcmtch(ctx->tokseamctx, objn);
+ return(TRUE);
+ }
+ else
+ return(FALSE);
+}
+
+/* update a symbol in a hashed symbol table */
+void tokthset(toktdef *tab1, toksdef *newsym)
+{
+ tokseadef ctx;
+ tokthdef *tab = (tokthdef *)tab1;
+
+ OSCPYSTRUCT(ctx.tokseasym, *newsym);
+ ctx.tokseamctx = tab->tokthmem;
+ tokthscan((tokthdef *)tab1, newsym->tokshsh, tokthset1, &ctx);
+}
+
+/* callback for tokthfind */
+static int tokthfind1(void *ctx0, toksdef *sym, mcmon objn)
+{
+ tokseadef *ctx = (tokseadef *)ctx0;
+
+ VARUSED(objn);
+
+ if (sym->toksval == ctx->tokseasym.toksval
+ && sym->tokstyp == ctx->tokseasym.tokstyp)
+ {
+ memcpy(ctx->toksearet, sym,
+ (size_t)(sizeof(toks1def) + sym->tokslen));
+ return(TRUE);
+ }
+ else
+ return(FALSE);
+}
+
+/* find a symbol of a particular type and value */
+int tokthfind(toktdef *tab1, int typ, uint val, toksdef *ret)
+{
+ tokseadef ctx;
+ int i;
+
+ ctx.tokseasym.tokstyp = typ;
+ ctx.tokseasym.toksval = val;
+ ctx.toksearet = ret;
+
+ for (i = 0 ; i < TOKHASHSIZE ; ++i)
+ {
+ if (tokthscan((tokthdef *)tab1, i, tokthfind1, &ctx))
+ return(TRUE);
+ }
+ return(FALSE);
+}
+
+/* call a callback for each function in a hashed symbol table */
+void toktheach(toktdef *tab1,
+ void (*cb)(void *, toksdef *), void *ctx)
+{
+ tokthdef *tab = (tokthdef *)tab1;
+ uchar *p;
+ uint max;
+ uint ofs;
+ tokshdef *symh;
+ toksdef *sym;
+ uint siz;
+ uint i;
+
+ for (i = 0 ; i <= tab->tokthpcnt ; ++i)
+ {
+ /* lock the current page */
+ p = mcmlck(tab->tokthmem, tab->tokthpool[i]);
+
+ ERRBEGIN(tab1->tokterr)
+
+ max = (i == tab->tokthpcnt ? tab->tokthofs : tab->tokthfinal[i]);
+ for (ofs = 0 ; ofs < max ; )
+ {
+ /* get this symbol */
+ symh = (tokshdef *)(p + ofs);
+ sym = &symh->tokshsc;
+
+ /* call the user callback */
+ (*cb)(ctx, sym);
+
+ /* advance to the next symbol on this page */
+ siz = sizeof(toksh1def) + sym->tokslen;
+ ofs += osrndsz(siz);
+ }
+
+ ERRCLEAN(tab1->tokterr)
+ mcmunlck(tab->tokthmem, tab->tokthpool[i]);
+ ERRENDCLN(tab1->tokterr)
+
+ /* done with current page; unlock it */
+ mcmunlck(tab->tokthmem, tab->tokthpool[i]);
+ }
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/vocabulary_parser.cpp b/engines/glk/tads/tads2/vocabulary_parser.cpp
new file mode 100644
index 0000000000..c0ff75df57
--- /dev/null
+++ b/engines/glk/tads/tads2/vocabulary_parser.cpp
@@ -0,0 +1,8169 @@
+/* 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"
+#include "glk/tads/tads2/list.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/vocabulary.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+static char *type_names[] =
+{
+ "article", "adj", "noun", "prep", "verb", "special", "plural",
+ "unknown"
+};
+
+/* array of flag values for words by part of speech */
+static int voctype[] =
+{ 0, 0, VOCT_VERB, VOCT_NOUN, VOCT_ADJ, VOCT_PREP, VOCT_ARTICLE };
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Allocate and push a list, given the number of bytes needed for the
+ * elements of the list.
+ */
+uchar *voc_push_list_siz(voccxdef *ctx, uint lstsiz)
+{
+ runcxdef *rcx = ctx->voccxrun;
+ runsdef val;
+ uchar *lstp;
+
+ /* add in the size needed for the list's length prefix */
+ lstsiz += 2;
+
+ /* allocate space in the heap */
+ runhres(rcx, lstsiz, 0);
+
+ /* set up a stack value to push */
+ val.runstyp = DAT_LIST;
+ val.runsv.runsvstr = lstp = ctx->voccxrun->runcxhp;
+
+ /* set up the list's length prefix */
+ oswp2(lstp, lstsiz);
+ lstp += 2;
+
+ /* commit the space in the heap */
+ rcx->runcxhp += lstsiz;
+
+ /* push the list value (repush, since we can use the original copy) */
+ runrepush(rcx, &val);
+
+ /* return the list element pointer */
+ return lstp;
+}
+
+/*
+ * Allocate and push a list. Returns a pointer to the space for the
+ * list's first element in the heap.
+ */
+static uchar *voc_push_list(voccxdef *ctx, int ele_count, int ele_size)
+{
+ uint lstsiz;
+
+ /*
+ * Figure the list size - we need space for the given number of
+ * elements of the given size; in addition, each element requires
+ * one byte of overhead for its type prefix.
+ */
+ lstsiz = (uint)(ele_count * (1 + ele_size));
+
+ /* allocate and return the list */
+ return voc_push_list_siz(ctx, lstsiz);
+}
+
+/*
+ * Push a list of numbers
+ */
+static void voc_push_numlist(voccxdef *ctx, uint numlist[], int cnt)
+{
+ int i;
+ uchar *lstp;
+
+ /* allocate space for the list of numbers */
+ lstp = voc_push_list(ctx, cnt, 4);
+
+ /* enter the list elements */
+ for (i = 0 ; i < cnt ; ++i)
+ {
+ /* add the type prefix */
+ *lstp++ = DAT_NUMBER;
+
+ /* add the value */
+ oswp4(lstp, numlist[i]);
+ lstp += 4;
+ }
+}
+
+/*
+ * Push a list of object ID's obtained from a vocoldef array
+ */
+void voc_push_vocoldef_list(voccxdef *ctx, vocoldef *objlist, int cnt)
+{
+ int i;
+ uchar *lstp;
+ uint lstsiz;
+
+ /*
+ * count the size - we need 3 bytes per object (1 for type plus 2
+ * for the value), and 1 byte per nil
+ */
+ for (lstsiz = 0, i = 0 ; i < cnt ; ++i)
+ lstsiz += (objlist[i].vocolobj == MCMONINV ? 1 : 3);
+
+ /* allocate space for the list */
+ lstp = voc_push_list_siz(ctx, lstsiz);
+
+ /* enter the list elements */
+ for (i = 0 ; i < cnt ; ++i)
+ {
+ if (objlist[i].vocolobj == MCMONINV)
+ {
+ /* store the nil */
+ *lstp++ = DAT_NIL;
+ }
+ else
+ {
+ /* add the type prefix */
+ *lstp++ = DAT_OBJECT;
+
+ /* add the value */
+ oswp2(lstp, objlist[i].vocolobj);
+ lstp += 2;
+ }
+ }
+}
+
+/*
+ * Push a list of object ID's
+ */
+void voc_push_objlist(voccxdef *ctx, objnum objlist[], int cnt)
+{
+ int i;
+ uchar *lstp;
+ uint lstsiz;
+
+ /*
+ * count the size - we need 3 bytes per object (1 for type plus 2
+ * for the value), and 1 byte per nil
+ */
+ for (lstsiz = 0, i = 0 ; i < cnt ; ++i)
+ lstsiz += (objlist[i] == MCMONINV ? 1 : 3);
+
+ /* allocate space for the list */
+ lstp = voc_push_list_siz(ctx, lstsiz);
+
+ /* enter the list elements */
+ for (i = 0 ; i < cnt ; ++i)
+ {
+ if (objlist[i] == MCMONINV)
+ {
+ /* store the nil */
+ *lstp++ = DAT_NIL;
+ }
+ else
+ {
+ /* add the type prefix */
+ *lstp++ = DAT_OBJECT;
+
+ /* add the value */
+ oswp2(lstp, objlist[i]);
+ lstp += 2;
+ }
+ }
+}
+
+/*
+ * Push a list of strings, where the strings are stored in memory, one
+ * after the other, each string separated from the next with a null
+ * byte. The list is bounded by firstwrd and lastwrd, inclusive of
+ * both.
+ */
+static void voc_push_strlist(voccxdef *ctx, char *firstwrd, char *lastwrd)
+{
+ size_t curlen;
+ char *p;
+ uint lstsiz;
+ uchar *lstp;
+
+ /*
+ * Determine how much space we need for the word list. For each
+ * entry, we need one byte for the type prefix, two bytes for the
+ * length prefix, and the bytes of the string itself.
+ */
+ for (lstsiz = 0, p = firstwrd ; p != 0 && p <= lastwrd ; p += curlen + 1)
+ {
+ curlen = strlen(p);
+ lstsiz += curlen + (1+2);
+ }
+
+ /* allocate space for the word list */
+ lstp = voc_push_list_siz(ctx, lstsiz);
+
+ /* enter the list elements */
+ for (p = firstwrd ; p != 0 && p <= lastwrd ; p += curlen + 1)
+ {
+ /* add the type prefix */
+ *lstp++ = DAT_SSTRING;
+
+ /* add the length prefix for this string */
+ curlen = strlen(p);
+ oswp2(lstp, curlen + 2);
+ lstp += 2;
+
+ /* add this string */
+ memcpy(lstp, p, curlen);
+ lstp += curlen;
+ }
+}
+
+/*
+ * Push a list of strings, taking the strings from an array.
+ */
+static void voc_push_strlist_arr(voccxdef *ctx, char *wordlist[], int cnt)
+{
+ int i;
+ char **p;
+ uint lstsiz;
+ uchar *lstp;
+
+ /*
+ * Add up the lengths of the strings in the array. For each
+ * element, we need space for the string's bytes, plus two bytes for
+ * the length prefix, plus one byte for the type prefix.
+ */
+ for (lstsiz = 0, p = wordlist, i = 0 ; i < cnt ; ++i, ++p)
+ lstsiz += strlen(*p) + 3;
+
+ /* allocate space for the list */
+ lstp = voc_push_list_siz(ctx, lstsiz);
+
+ /* enter the list elements */
+ for (p = wordlist, i = 0 ; i < cnt ; ++i, ++p)
+ {
+ size_t curlen;
+
+ /* add the type prefix */
+ *lstp++ = DAT_SSTRING;
+
+ /* add the length prefix for this string */
+ curlen = strlen(*p);
+ oswp2(lstp, curlen + 2);
+ lstp += 2;
+
+ /* add this string */
+ memcpy(lstp, *p, curlen);
+ lstp += curlen;
+ }
+}
+
+/*
+ * Push a list of strings, taking the strings from an array that was
+ * prepared by the parser tokenizer. This is almost the same as pushing
+ * a regular string array, with the difference that we must recognize
+ * the special format that the tokenizer uses to store string tokens.
+ */
+static void voc_push_toklist(voccxdef *ctx, char *wordlist[], int cnt)
+{
+ int i;
+ char **p;
+ uint lstsiz;
+ uchar *lstp;
+ size_t cur_len;
+
+ /*
+ * Add up the lengths of the strings in the array. For each
+ * element, we need space for the string's bytes, plus two bytes for
+ * the length prefix, plus one byte for the type prefix.
+ */
+ for (lstsiz = 0, p = wordlist, i = 0 ; i < cnt ; ++i, ++p)
+ {
+ /*
+ * get the length of the current token - check what kind of
+ * token we have, since we must sense the length of different
+ * token types in different ways
+ */
+ if (**p == '"')
+ {
+ /*
+ * It's a string token - the string follows with a two-byte
+ * length prefix; add two bytes for the open and close quote
+ * characters that we'll add to the output string. Note
+ * that we must deduct two bytes from the prefix length,
+ * because the prefix includes the size of the prefix
+ * itself, which we're not copying and will account for
+ * separately in the result string.
+ */
+ cur_len = osrp2(*p + 1) - 2 + 2;
+ }
+ else
+ {
+ /* for anything else, it's just a null-terminated string */
+ cur_len = strlen(*p);
+ }
+
+ /* add the current length to the total so far */
+ lstsiz += cur_len + 3;
+ }
+
+ /* allocate space for the list */
+ lstp = voc_push_list_siz(ctx, lstsiz);
+
+ /* enter the list elements */
+ for (p = wordlist, i = 0 ; i < cnt ; ++i, ++p)
+ {
+ char *cur_ptr;
+ size_t copy_len;
+
+ /* add the type prefix */
+ *lstp++ = DAT_SSTRING;
+
+ /* get the information for the string based on the type */
+ if (**p == '"')
+ {
+ /*
+ * it's a string - use the length prefix (deducting two
+ * bytes for the prefix itself, which we're not copying)
+ */
+ copy_len = osrp2(*p + 1) - 2;
+
+ /* add space in the result for the open and close quotes */
+ cur_len = copy_len + 2;
+
+ /* the string itself follows the length prefix and '"' flag */
+ cur_ptr = *p + 3;
+ }
+ else
+ {
+ /* for anything else, it's just a null-terminated string */
+ cur_len = copy_len = strlen(*p);
+ cur_ptr = *p;
+ }
+
+ /* write the length prefix for this string */
+ oswp2(lstp, cur_len + 2);
+ lstp += 2;
+
+ /* add the open quote if this is a quoted string */
+ if (**p == '"')
+ *lstp++ = '"';
+
+ /* add this string */
+ memcpy(lstp, cur_ptr, copy_len);
+ lstp += copy_len;
+
+ /* add the close quote if it's a quoted string */
+ if (**p == '"')
+ *lstp++ = '"';
+ }
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Read a command from the keyboard, doing all necessary output flushing
+ * and prompting.
+ */
+int vocread(voccxdef *ctx, objnum actor, objnum verb,
+ char *buf, int bufl, int type)
+{
+ char *prompt;
+ int ret;
+
+ /* presume we'll return success */
+ ret = VOCREAD_OK;
+
+ /* make sure output capturing is off */
+ tiocapture(ctx->voccxtio, (mcmcxdef *)0, FALSE);
+ tioclrcapture(ctx->voccxtio);
+
+ /*
+ * Clear out the command buffer. This is important for the
+ * timeout-based command reader, since it will take what's in the
+ * buffer as the initial contents of the command line; this lets us
+ * remember any partial line that the player entered before a
+ * timeout interrupted their typing and redisplay the original
+ * partial line on the next command line. Initially, there's no
+ * partial line, so clear it out.
+ */
+ buf[0] = '\0';
+
+ /* show the game-defined prompt, if appropriate */
+ if (ctx->voccxprom != MCMONINV)
+ {
+ runpnum(ctx->voccxrun, (long)type);
+ runfn(ctx->voccxrun, ctx->voccxprom, 1);
+ tioflushn(ctx->voccxtio, 0);
+ prompt = "";
+ }
+ else
+ {
+ /* there's no game-defined prompt - use our default */
+ tioblank(tio);
+ prompt = ">";
+ }
+
+ /* get a line of input */
+ if (tiogets(ctx->voccxtio, prompt, buf, bufl))
+ errsig(ctx->voccxerr, ERR_RUNQUIT);
+
+ /* abort immediately if we see the special panic command */
+ if (!strcmp(buf, "$$ABEND"))
+ {
+ /* make sure any script file is closed */
+ qasclose();
+
+ /* use the OS-level termination */
+ os_term(OSEXFAIL);
+
+ /* if that returned, signal a quit */
+ errsig(ctx->voccxerr, ERR_RUNQUIT);
+ }
+
+ /* call the post-prompt function if defined */
+ if (ctx->voccxpostprom != MCMONINV)
+ {
+ runpnum(ctx->voccxrun, (long)type);
+ runfn(ctx->voccxrun, ctx->voccxpostprom, 1);
+ }
+
+ /*
+ * If this isn't a type "0" input, and preparseExt() is defined, call
+ * it. Don't call preparseExt() for type "0" inputs, since these will
+ * be handled via the conventional preparse().
+ */
+ if (ctx->voccxpre2 != MCMONINV && type != 0)
+ {
+ uchar *s;
+ size_t len;
+
+ /* push the arguments - actor, verb, str, type */
+ runpnum(ctx->voccxrun, (long)type);
+ runpstr(ctx->voccxrun, buf, (int)strlen(buf), 0);
+ runpobj(ctx->voccxrun, verb);
+ runpobj(ctx->voccxrun, actor);
+
+ /* call preparseExt() */
+ runfn(ctx->voccxrun, ctx->voccxpre2, 4);
+
+ /* check the result */
+ switch(runtostyp(ctx->voccxrun))
+ {
+ case DAT_SSTRING:
+ /*
+ * They returned a string. Replace the input buffer we read
+ * with the new string. Pop the new string and scan its length
+ * prefix.
+ */
+ s = runpopstr(ctx->voccxrun);
+ len = osrp2(s) - 2;
+ s += 2;
+
+ /*
+ * limit the size we copy to our buffer length (leaving space
+ * for null termination)
+ */
+ if (len > (size_t)bufl - 1)
+ len = bufl - 1;
+
+ /* copy the new command string into our buffer */
+ memcpy(buf, s, len);
+
+ /* null-terminate the result */
+ buf[len] = '\0';
+
+ /* proceed as normal with the new string */
+ break;
+
+ case DAT_TRUE:
+ /*
+ * they simply want to keep the current string as it is -
+ * proceed as normal
+ */
+ break;
+
+ case DAT_NIL:
+ /*
+ * They want to skip the special interpretation of the input
+ * and proceed directly to treating the input as a brand new
+ * command. The caller will have to take care of the details;
+ * we need only indicate this to the caller through our "redo"
+ * result code.
+ */
+ ret = VOCREAD_REDO;
+ break;
+ }
+ }
+
+ /* return our result */
+ return ret;
+}
+
+/*
+ * Compare a pair of words, truncated to six characters or the
+ * length of the first word, whichever is longer. (The first word is
+ * 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)
+{
+ uint i;
+
+ if (l1 == 0 && l2 == 0) return(TRUE); /* both NULL - a match */
+ if (l1 == 0 || l2 == 0) return(FALSE); /* one NULL only - not a match */
+ if (l1 >= 6 && l2 >= l1) l2 = l1;
+ if (l1 != l2) return(FALSE); /* ==> not equal */
+ for (i = 0 ; i < l1 ; i++)
+ if (*s1++ != *s2++) return(FALSE);
+ return(TRUE); /* strings match */
+}
+
+/* find the next word in a search */
+vocwdef *vocfnw(voccxdef *voccx, vocseadef *search_ctx)
+{
+ vocdef *v, *vf;
+ vocwdef *vw, *vwf = nullptr;
+ vocdef *c = search_ctx->v;
+ int first;
+
+ /* continue with current word's vocwdef list if anything is left */
+ first = TRUE;
+ vw = vocwget(voccx, search_ctx->vw->vocwnxt);
+
+ /* keep going until we run out of hash chain entries or find a match */
+ for (v = c, vf = 0 ; v != 0 && vf == 0 ; v = v->vocnxt, first = FALSE)
+ {
+ /* if this word matches, look at the objects in its list */
+ if (first
+ || (voceq(search_ctx->wrd1, search_ctx->len1,
+ v->voctxt, v->voclen)
+ && voceq(search_ctx->wrd2, search_ctx->len2,
+ v->voctxt + v->voclen, v->vocln2)))
+ {
+ /*
+ * on the first time through, vw has already been set up
+ * with the next vocwdef in the current list; on subsequent
+ * times through the loop, start at the head of the current
+ * word's list
+ */
+ if (!first)
+ vw = vocwget(voccx, v->vocwlst);
+
+ /* search the list from vw forward */
+ for ( ; vw ; vw = vocwget(voccx, vw->vocwnxt))
+ {
+ if (search_ctx->vw->vocwtyp == vw->vocwtyp
+ && !(vw->vocwflg & VOCFCLASS)
+ && !(vw->vocwflg & VOCFDEL))
+ {
+ /*
+ * remember the first vocdef that we found, and
+ * remember this, the first matching vocwdef, then
+ * stop scanning
+ */
+ vf = v;
+ vwf = vw;
+ break;
+ }
+ }
+ }
+ }
+
+ /* return the first vocwdef in this word's list */
+ search_ctx->v = vf;
+ search_ctx->vw = (vf ? vwf : 0);
+ return(search_ctx->vw);
+}
+
+/* find the first vocdef matching a set of words */
+vocwdef *vocffw(voccxdef *ctx, char *wrd, int len, char *wrd2, int len2,
+ int p, vocseadef *search_ctx)
+{
+ uint hshval;
+ vocdef *v, *vf;
+ vocwdef *vw, *vwf = nullptr;
+
+ /* get the word's hash value */
+ hshval = vochsh((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))
+ {
+ /* look for a suitable object in the vocwdef list */
+ for (vw = vocwget(ctx, v->vocwlst) ; vw ;
+ vw = vocwget(ctx, vw->vocwnxt))
+ {
+ if (vw->vocwtyp == p && !(vw->vocwflg & VOCFCLASS)
+ && !(vw->vocwflg & VOCFDEL))
+ {
+ /*
+ * remember the first vocdef that we found, and
+ * remember this, the first matching vocwdef; then
+ * stop scanning, since we have a match
+ */
+ vf = v;
+ vwf = vw;
+ break;
+ }
+ }
+ }
+ }
+
+ /* set up the caller-provided search structure for next time */
+ vw = (vf != 0 ? vwf : 0);
+ if (search_ctx)
+ {
+ /* save the search position */
+ search_ctx->v = vf;
+ search_ctx->vw = vw;
+
+ /* save the search criteria */
+ search_ctx->wrd1 = (uchar *)wrd;
+ search_ctx->len1 = len;
+ search_ctx->wrd2 = (uchar *)wrd2;
+ search_ctx->len2 = len2;
+ }
+
+ /* return the match */
+ return vw;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * vocerr_va information structure. This is initialized in the call to
+ * vocerr_va_prep(), and must then be passed to vocerr_va().
+ */
+struct vocerr_va_info
+{
+ /* parseError/parseErrorParam result */
+ char user_msg[400];
+
+ /* the sprintf-style format string to display */
+ char *fmt;
+
+ /*
+ * Pointer to the output buffer to use to format the string 'fmt' with
+ * its arguments, using vsprintf. The prep function will set this up
+ * to point to user_msg[].
+ */
+ char *outp;
+
+ /* size of the output buffer, in bytes */
+ size_t outsiz;
+};
+
+/*
+ * General parser error formatter - preparation. This must be called to
+ * initialize the context before the message can be displayed with
+ * vocerr_va().
+ */
+static void vocerr_va_prep(voccxdef *ctx, struct vocerr_va_info *info,
+ int err, char *f, va_list argptr)
+{
+ /*
+ * presume that we'll use the given format string, instead of one
+ * provided by the program
+ */
+ info->fmt = f;
+
+ /* use the output buffer from the info structure */
+ info->outp = info->user_msg;
+ info->outsiz = sizeof(info->user_msg);
+
+ /*
+ * if the user has a parseError or parseErrorParam function, see if it
+ * provides a msg
+ */
+ if (ctx->voccxper != MCMONINV || ctx->voccxperp != MCMONINV)
+ {
+ runcxdef *rcx = ctx->voccxrun;
+ dattyp typ;
+ size_t len;
+ int argc;
+
+ /* start off with the two arguments that are always present */
+ argc = 2;
+
+ /*
+ * if we're calling parseErrorParam, and we have additional
+ * arguments, push them as well
+ */
+ if (ctx->voccxperp != MCMONINV)
+ {
+ enum typ_t
+ {
+ ARGBUF_STR, ARGBUF_INT, ARGBUF_CHAR
+ };
+ struct argbuf_t
+ {
+ enum typ_t typ;
+ union
+ {
+ char *strval;
+ int intval;
+ char charval;
+ } val;
+ };
+ struct argbuf_t args[5];
+ struct argbuf_t *argp;
+ char *p;
+
+ /*
+ * Retrieve the arguments by examining the format string. We
+ * must buffer up the arguments before pushing them, because
+ * we need to push them in reverse order (last to first); so,
+ * we must scan all arguments before we push the first one.
+ */
+ for (p = f, argp = args ; *p != '\0' ; ++p)
+ {
+ /* check if this is a parameter */
+ if (*p == '%')
+ {
+ /* find out what type it is */
+ switch(*++p)
+ {
+ case 's':
+ /* string - save the char pointer */
+ argp->val.strval = va_arg(argptr, char *);
+ argp->typ = ARGBUF_STR;
+
+ /* consume an argument slot */
+ ++argp;
+ break;
+
+ case 'd':
+ /* integer - save the integer */
+ argp->val.intval = va_arg(argptr, int);
+ argp->typ = ARGBUF_INT;
+
+ /* consume an argument slot */
+ ++argp;
+ break;
+
+ case 'c':
+ /* character */
+ argp->val.charval = (char)va_arg(argptr, int);
+ argp->typ = ARGBUF_CHAR;
+
+ /* consume an argument slot */
+ ++argp;
+ break;
+
+ default:
+ /*
+ * ignore other types (there shouldn't be any
+ * other types anyway)
+ */
+ break;
+ }
+ }
+ }
+
+ /*
+ * Push the arguments - keep looping until we get back to the
+ * first argument slot
+ */
+ while (argp != args)
+ {
+ /* move to the next argument, working backwards */
+ --argp;
+
+ /* push this argument */
+ switch(argp->typ)
+ {
+ case ARGBUF_STR:
+ /* push the string value */
+ runpstr(rcx, argp->val.strval,
+ (int)strlen(argp->val.strval), 0);
+ break;
+
+ case ARGBUF_INT:
+ /* push the number value */
+ runpnum(rcx, argp->val.intval);
+ break;
+
+ case ARGBUF_CHAR:
+ /* push the character as a one-character string */
+ runpstr(rcx, &argp->val.charval, 1, 0);
+ break;
+ }
+
+ /* count the argument */
+ ++argc;
+ }
+ }
+
+ /* push standard arguments: error code and default message */
+ runpstr(rcx, f, (int)strlen(f), 0); /* 2nd arg: default msg */
+ runpnum(rcx, (long)err); /* 1st arg: error number */
+
+ /* invoke parseErrorParam if it's defined, otherwise parseError */
+ runfn(rcx, (objnum)(ctx->voccxperp == MCMONINV
+ ? ctx->voccxper : ctx->voccxperp), argc);
+
+ /* see what the function returned */
+ typ = runtostyp(rcx);
+ if (typ == DAT_SSTRING)
+ {
+ char *p;
+
+ /*
+ * they returned a string - use it as the error message
+ * instead of the default message
+ */
+ p = (char *)runpopstr(rcx);
+ len = osrp2(p) - 2;
+ p += 2;
+ if (len > sizeof(info->user_msg) - 1)
+ len = sizeof(info->user_msg) - 1;
+ memcpy(info->user_msg, p, len);
+ info->user_msg[len] = '\0';
+
+ /* use the returned string as the message to display */
+ info->fmt = info->user_msg;
+
+ /* use the remainder of the buffer for the final formatting */
+ info->outp = info->user_msg + len + 1;
+ info->outsiz = sizeof(info->user_msg) - len - 1;
+ }
+ else
+ {
+ /* ignore other return values */
+ rundisc(rcx);
+ }
+ }
+
+}
+
+/*
+ * General parser error formatter.
+ *
+ * Before calling this routine, callers MUST invoke vocerr_va_prep() to
+ * prepare the information structure. Because both this routine and the
+ * prep routine need to look at the varargs list ('argptr'), the caller
+ * must call va_start/va_end around the prep call, and then AGAIN on this
+ * call. va_start/va_end must be used twice to ensure that the argptr is
+ * 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)
+{
+ char *buf;
+
+ /* turn on output */
+ (void)tioshow(ctx->voccxtio);
+
+ /* build the string to display */
+ if (os_vasprintf(&buf, info->fmt, argptr) >= 0)
+ {
+ /* display it */
+ tioputs(ctx->voccxtio, buf);
+
+ /* free the buffer */
+ osfree(buf);
+ }
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * display a parser informational message
+ */
+void vocerr_info(voccxdef *ctx, int err, char *f, ...)
+{
+ va_list argptr;
+ struct vocerr_va_info info;
+
+ /* prepare to format the message */
+ va_start(argptr, f);
+ vocerr_va_prep(ctx, &info, err, f, argptr);
+ va_end(argptr);
+
+ /* call the general vocerr formatter */
+ va_start(argptr, f);
+ vocerr_va(ctx, &info, err, f, argptr);
+ va_end(argptr);
+}
+
+/*
+ * display a parser error
+ */
+void vocerr(voccxdef *ctx, int err, char *f, ...)
+{
+ va_list argptr;
+ struct vocerr_va_info info;
+
+ /*
+ * If the unknown word flag is set, suppress this error, because
+ * we're going to be trying the whole parsing from the beginning
+ * again anyway.
+ */
+ if (ctx->voccxunknown > 0)
+ return;
+
+ /* prepare to format the message */
+ va_start(argptr, f);
+ vocerr_va_prep(ctx, &info, err, f, argptr);
+ va_end(argptr);
+
+ /* call the general vocerr formatter */
+ va_start(argptr, f);
+ vocerr_va(ctx, &info, err, f, argptr);
+ va_end(argptr);
+}
+
+/*
+ * Handle an unknown verb or sentence structure. We'll call this when
+ * we encounter a sentence where we don't know the verb word, or we
+ * don't know the combination of verb and verb preposition, or we don't
+ * recognize the sentence structure (for example, an indirect object is
+ * present, but we don't have a template defined using an indirect
+ * object for the verb).
+ *
+ * This function calls the game-defined function parseUnknownVerb, if it
+ * exists. If the function doesn't exist, we'll simply display the
+ * given error message, using the normal parseError mechanism. The
+ * function should use "abort" or "exit" if it wants to cancel further
+ * processing of the command.
+ *
+ * We'll return true if the function exists, in which case normal
+ * processing should continue with any remaining command on the command
+ * line. We'll return false if the function doesn't exist, in which
+ * case the remainder of the command should be aborted.
+ *
+ * 'wrdcnt' is the number of words in the cmd[] array. If wrdcnt is
+ * zero, we'll automatically count the array entries, with the end of
+ * the array indicated by a null pointer entry.
+ *
+ * 'next_start' is a variable that we may fill in with the index of the
+ * next word in the command to be parsed. If the user function
+ * indicates the number of words it consumes, we'll use 'next_start' to
+ * communicate this back to the caller, so that the caller can resume
+ * parsing after the part parsed by the function.
+ */
+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 show_msg;
+ va_list argptr;
+
+ /* presume we won't show the message */
+ show_msg = FALSE;
+
+ /* determine the word count if the caller passed in zero */
+ if (wrdcnt == 0)
+ {
+ /* count the words before the terminating null entry */
+ for ( ; cmd[wrdcnt] != 0 ; ++wrdcnt) ;
+ }
+
+ /* if parseUnknownVerb exists, call it */
+ if (ctx->voccxpuv != MCMONINV)
+ {
+ int err;
+ int i;
+ //int do_fuses;
+
+ /* no error has occurred yet */
+ err = 0;
+
+ /* presume we will run the fuses */
+ do_fuses = TRUE;
+
+ /* push the error number argument */
+ runpnum(ctx->voccxrun, (long)vocerr_err);
+
+ /* push a list of the word types */
+ voc_push_numlist(ctx, (uint *)typelist, wrdcnt);
+
+ /* push a list of the words */
+ voc_push_toklist(ctx, cmd, wrdcnt);
+
+ /* use "Me" as the default actor */
+ if (actor == MCMONINV)
+ actor = ctx->voccxme;
+
+ /* push the actor argument */
+ runpobj(ctx->voccxrun, actor);
+
+ /* catch any errors that occur while calling the function */
+ ERRBEGIN(ctx->voccxerr)
+ {
+ /* invoke the function */
+ runfn(ctx->voccxrun, ctx->voccxpuv, 4);
+
+ /* get the return value */
+ switch(runtostyp(ctx->voccxrun))
+ {
+ case DAT_TRUE:
+ /* the command was handled */
+ rundisc(ctx->voccxrun);
+
+ /* consume the entire command */
+ *next_start = wrdcnt;
+
+ /*
+ * since the command has now been handled, forget about
+ * any unknown words
+ */
+ ctx->voccxunknown = 0;
+ break;
+
+ case DAT_NUMBER:
+ /*
+ * The command was handled, and the function indicated
+ * the number of words it wants to skip. Communicate
+ * this information back to the caller in *next_start.
+ * Since the routine returns the 1-based index of the
+ * next entry, we must subtract one to get the number of
+ * words actually consumed.
+ */
+ *next_start = runpopnum(ctx->voccxrun);
+ if (*next_start > 0)
+ --(*next_start);
+
+ /* make sure the value is in range */
+ if (*next_start < 0)
+ *next_start = 0;
+ else if (*next_start > wrdcnt)
+ *next_start = wrdcnt;
+
+ /*
+ * forget about any unknown words in the list up to the
+ * next word
+ */
+ for (i = 0 ; i < *next_start ; ++i)
+ {
+ /* if this word was unknown, forget about that now */
+ if ((typelist[i] & VOCT_UNKNOWN) != 0
+ && ctx->voccxunknown > 0)
+ --(ctx->voccxunknown);
+ }
+ break;
+
+ default:
+ /* treat anything else like nil */
+
+ case DAT_NIL:
+ /* nil - command not handled; show the message */
+ rundisc(ctx->voccxrun);
+ show_msg = TRUE;
+ break;
+ }
+ }
+ ERRCATCH(ctx->voccxerr, err)
+ {
+ /* check the error */
+ switch(err)
+ {
+ case ERR_RUNEXIT:
+ case ERR_RUNEXITOBJ:
+ /*
+ * Exit or exitobj was executed - skip to the fuses.
+ * Forget about any unknown words, since we've finished
+ * processing this command and we don't want to allow
+ * "oops" processing.
+ */
+ ctx->voccxunknown = 0;
+ break;
+
+ case ERR_RUNABRT:
+ /*
+ * abort was executed - skip to the end of the command,
+ * but do not execute the fuses
+ */
+ do_fuses = FALSE;
+
+ /*
+ * Since we're aborting the command, ignore any
+ * remaining unknown words - we're skipping out of the
+ * command entirely, so we don't care that there were
+ * unknown words in the command.
+ */
+ ctx->voccxunknown = 0;
+ break;
+
+ default:
+ /* re-throw any other errors */
+ errrse(ctx->voccxerr);
+ }
+ }
+ ERREND(ctx->voccxerr);
+
+ /* if we're not showing the message, process fuses and daemons */
+ if (!show_msg)
+ {
+ /* execute fuses and daemons */
+ if (exe_fuses_and_daemons(ctx, err, do_fuses,
+ actor, MCMONINV, 0, 0,
+ MCMONINV, MCMONINV) != 0)
+ {
+ /*
+ * aborted from fuses and daemons - return false to tell
+ * the caller not to execute anything left on the
+ * command line
+ */
+ return FALSE;
+ }
+
+ /* indicate that the game code successfully handled the command */
+ return TRUE;
+ }
+ }
+
+ /*
+ * If we made it here, it means we're showing the default message.
+ * If we have unknown words, suppress the message so that we show
+ * the unknown word error instead after returning.
+ */
+ if (ctx->voccxunknown == 0)
+ {
+ struct vocerr_va_info info;
+
+ /* prepare to format the message */
+ va_start(argptr, vocerr_msg);
+ vocerr_va_prep(ctx, &info, vocerr_err, vocerr_msg, argptr);
+ va_end(argptr);
+
+ /* format the mesage */
+ va_start(argptr, vocerr_msg);
+ vocerr_va(ctx, &info, vocerr_err, vocerr_msg, argptr);
+ va_end(argptr);
+ }
+
+ /* indicate that the remainder of the command should be aborted */
+ return FALSE;
+}
+
+
+/* determine if a tokenized word is a special internal word flag */
+/* int vocisspec(char *wrd); */
+#define vocisspec(wrd) \
+ (vocisupper(*wrd) || (!vocisalpha(*wrd) && *wrd != '\'' && *wrd != '-'))
+
+static vocspdef vocsptab[] =
+{
+ { "of", VOCW_OF },
+ { "and", VOCW_AND },
+ { "then", VOCW_THEN },
+ { "all", VOCW_ALL },
+ { "everyt", VOCW_ALL },
+ { "both", VOCW_BOTH },
+ { "but", VOCW_BUT },
+ { "except", VOCW_BUT },
+ { "one", VOCW_ONE },
+ { "ones", VOCW_ONES },
+ { "it", VOCW_IT },
+ { "them", VOCW_THEM },
+ { "him", VOCW_HIM },
+ { "her", VOCW_HER },
+ { "any", VOCW_ANY },
+ { "either", VOCW_ANY },
+ { 0, 0 }
+};
+
+/* test a word to see if it's a particular special word */
+static int voc_check_special(voccxdef *ctx, char *wrd, int checktyp)
+{
+ /* search the user or built-in special table, as appropriate */
+ if (ctx->voccxspp)
+ {
+ char *p;
+ char *endp;
+ char typ;
+ int len;
+ int wrdlen = strlen((char *)wrd);
+
+ for (p = ctx->voccxspp, endp = p + ctx->voccxspl ;
+ p < endp ; )
+ {
+ typ = *p++;
+ len = *p++;
+
+ /* if this word matches in type and text, we have a match */
+ if (typ == checktyp
+ && len == wrdlen && !memcmp(p, wrd, (size_t)len))
+ return TRUE;
+
+ /* no match - keep going */
+ p += len;
+ }
+ }
+ else
+ {
+ 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))
+ return TRUE;
+ }
+ }
+
+ /* didn't find a match for the text and type */
+ return FALSE;
+}
+
+
+/* tokenize a command line - returns number of words in command */
+int voctok(voccxdef *ctx, char *cmd, char *outbuf, char **wrd,
+ int lower, int cvt_ones, int show_errors)
+{
+ int i;
+ vocspdef *x;
+ int l;
+ char *p;
+ char *w;
+ uint len;
+
+ for (i = 0 ;; )
+ {
+ while (vocisspace(*cmd)) cmd++;
+ if (!*cmd)
+ {
+ wrd[i] = outbuf;
+ *outbuf = '\0';
+ return(i);
+ }
+
+ wrd[i++] = outbuf;
+ if (vocisalpha(*cmd) || *cmd == '-')
+ {
+ while(vocisalpha(*cmd) || vocisdigit(*cmd) ||
+ *cmd=='\'' || *cmd=='-')
+ {
+ *outbuf++ = (vocisupper(*cmd) && lower) ? tolower(*cmd) : *cmd;
+ ++cmd;
+ }
+
+ /*
+ * Check for a special case: abbreviations that end in a
+ * period. For example, "Mr. Patrick J. Wayne." We wish
+ * to absorb the period after "Mr" and the one after "J"
+ * into the respective words; we detect this condition by
+ * actually trying to find a word in the dictionary that
+ * has the period.
+ */
+ w = wrd[i-1];
+ len = outbuf - w;
+ if (*cmd == '.')
+ {
+ *outbuf++ = *cmd++; /* add the period to the word */
+ *outbuf = '\0'; /* null-terminate it */
+ ++len;
+ if (!vocffw(ctx, (char *)w, len, 0, 0, PRP_NOUN,
+ (vocseadef *)0)
+ && !vocffw(ctx, (char *)w, len, 0, 0, PRP_ADJ,
+ (vocseadef *)0))
+ {
+ /* no word with period in dictionary - remove period */
+ --outbuf;
+ --cmd;
+ --len;
+ }
+ }
+
+ /* null-terminate the buffer */
+ *outbuf = '\0';
+
+ /* find compound words and glue them together */
+ for (p = ctx->voccxcpp, l = ctx->voccxcpl ; l ; )
+ {
+ uint l1 = osrp2(p);
+ char *p2 = p + l1; /* get second word */
+ uint l2 = osrp2(p2);
+ char *p3 = p2 + l2; /* get compound word */
+ uint l3 = osrp2(p3);
+
+ if (i > 1 && len == (l2 - 2)
+ && !memcmp(w, p2 + 2, (size_t)len)
+ && strlen((char *)wrd[i-2]) == (l1 - 2)
+ && !memcmp(wrd[i-2], p + 2, (size_t)(l1 - 2)))
+ {
+ memcpy(wrd[i-2], p3 + 2, (size_t)(l3 - 2));
+ *(wrd[i-2] + l3 - 2) = '\0';
+ --i;
+ break;
+ }
+
+ /* move on to the next word */
+ l -= l1 + l2 + l3;
+ p = p3 + l3;
+ }
+
+ /*
+ * Find any special keywords, and set to appropriate flag
+ * char. Note that we no longer convert "of" in this
+ * fashion; "of" is now handled separately in order to
+ * facilitate its use as an ordinary preposition.
+ */
+ if (ctx->voccxspp)
+ {
+ //char *p;
+ char *endp;
+ char typ;
+ //int len;
+ uint wrdlen = strlen((char *)wrd[i-1]);
+
+ for (p = ctx->voccxspp, endp = p + ctx->voccxspl ;
+ p < endp ; )
+ {
+ typ = *p++;
+ len = *p++;
+ if (len == wrdlen && !memcmp(p, wrd[i-1], (size_t)len)
+ && (cvt_ones || (typ != VOCW_ONE && typ != VOCW_ONES))
+ && typ != VOCW_OF)
+ {
+ *wrd[i-1] = typ;
+ *(wrd[i-1] + 1) = '\0';
+ break;
+ }
+ p += len;
+ }
+ }
+ else
+ {
+ for (x = vocsptab ; x->vocspin ; ++x)
+ {
+ if (!strncmp((char *)wrd[i-1], (char *)x->vocspin,
+ (size_t)6)
+ && (cvt_ones ||
+ (x->vocspout != VOCW_ONE
+ && x->vocspout != VOCW_ONES))
+ && x->vocspout != VOCW_OF)
+ {
+ *wrd[i-1] = x->vocspout;
+ *(wrd[i-1] + 1) = '\0';
+ break;
+ }
+ }
+ }
+
+ /* make sure the output pointer is fixed up to the right spot */
+ outbuf = wrd[i-1];
+ outbuf += strlen((char *)outbuf);
+ }
+ else if (vocisdigit( *cmd ))
+ {
+ while(vocisdigit(*cmd) || vocisalpha(*cmd)
+ || *cmd == '\'' || *cmd == '-')
+ *outbuf++ = *cmd++;
+ }
+ else switch( *cmd )
+ {
+ case '.':
+ case '!':
+ case '?':
+ case ';':
+ *outbuf++ = VOCW_THEN;
+ ++cmd;
+ break;
+
+ case ',':
+ case ':':
+ *outbuf++ = VOCW_AND;
+ ++cmd;
+ break;
+
+ case '"':
+ case '\'':
+ {
+ char *lenptr;
+ char quote = *cmd++;
+
+ /*
+ * remember that this is a quoted string (it doesn't
+ * matter whether they're actually using single or
+ * double quotes - in either case, we use '"' as the
+ * flag to indicate that it's a quote string)
+ */
+ *outbuf++ = '"';
+
+ /* make room for the length prefix */
+ lenptr = outbuf;
+ outbuf += 2;
+
+ /* copy up to the matching close quote */
+ while (*cmd && *cmd != quote)
+ {
+ char c;
+
+ /* get this character */
+ c = *cmd++;
+
+ /* escape the character if necessary */
+ switch(c)
+ {
+ case '\\':
+ *outbuf++ = '\\';
+ break;
+ }
+
+ /* copy this character */
+ *outbuf++ = c;
+ }
+
+ oswp2(lenptr, ((int)(outbuf - lenptr)));
+ if (*cmd == quote) cmd++;
+ break;
+ }
+
+ default:
+ /* display an error if appropriate */
+ if (show_errors)
+ {
+ int hmode = tio_is_html_mode();
+
+ /*
+ * if we're in HTML mode, switch out momentarily, so that
+ * we show the character literally, even if it's a
+ * markup-significant character (such as '<' or '&')
+ */
+ if (hmode)
+ tioputs(ctx->voccxtio, "\\H-");
+
+ /* show the message */
+ vocerr(ctx, VOCERR(1),
+ "I don't understand the punctuation \"%c\".", *cmd);
+
+ /* restore HTML mode if appropriate */
+ if (hmode)
+ tioputs(ctx->voccxtio, "\\H+");
+ }
+
+ /* return failure */
+ return -1;
+ }
+
+ /* null-terminate the result */
+ *outbuf++ = '\0';
+ }
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Look up a word's type. If 'of_is_spec' is true, we'll treat OF as
+ * being of type special if it's not otherwise defined.
+ */
+static int voc_lookup_type(voccxdef *ctx, char *p, int len, int of_is_spec)
+{
+ int t;
+
+ /* check for a special word */
+ if (vocisspec(p))
+ {
+ /* it's a special word - this is its type */
+ t = VOCT_SPEC;
+ }
+ else
+ {
+ vocwdef *vw;
+ vocdef *v;
+
+ /*
+ * Now check the various entries of this word to get the word
+ * type flag bits. The Noun and Adjective flags can be set for
+ * any word which matches this word in the first six letters (or
+ * more if more were provided by the player), but the Plural
+ * flag can only be set if the plural word matches exactly.
+ * Note that this pass only matches the first word in two-word
+ * verbs; the second word is considered later during the
+ * semantic analysis.
+ */
+ for (t = 0, v = ctx->voccxhsh[vochsh((uchar *)p, len)] ; v != 0 ;
+ v = v->vocnxt)
+ {
+ /* if this hash chain entry matches, add it to our types */
+ if (voceq((uchar *)p, len, v->voctxt, v->voclen))
+ {
+ /* we have a match - look through relation list for word */
+ for (vw = vocwget(ctx, v->vocwlst) ; vw != 0 ;
+ vw = vocwget(ctx, vw->vocwnxt))
+ {
+ /* skip this word if it's been deleted */
+ if (vw->vocwflg & VOCFDEL)
+ continue;
+
+ /* we need a special check for plurals */
+ if (vw->vocwtyp == PRP_PLURAL)
+ {
+ /* plurals must be exact (non-truncated) match */
+ if (len == v->voclen)
+ {
+ /* plurals also count as nouns */
+ t |= (VOCT_NOUN | VOCT_PLURAL);
+ }
+ }
+ else
+ {
+ /* add this type bit to our type value */
+ t |= voctype[vw->vocwtyp];
+ }
+ }
+ }
+ }
+ }
+
+ /* check for "of" if the caller wants us to */
+ if (of_is_spec && t == 0 && voc_check_special(ctx, p, VOCW_OF))
+ t = VOCT_SPEC;
+
+ /* return the type */
+ return t;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Display an unknown word error, and read a new command, allowing the
+ * user to respond with the special OOPS command to correct the unknown
+ * word. Returns a pointer to the start of the replacement text if the
+ * player entered a correction via OOPS, or a null pointer if the player
+ * simply entered a new command.
+ */
+static char *voc_read_oops(voccxdef *ctx, char *oopsbuf, size_t oopsbuflen,
+ const char *unknown_word)
+{
+ char *p;
+
+ /* display the error */
+ vocerr(ctx, VOCERR(2), "I don't know the word \"%s\".", unknown_word);
+
+ /* read a new command */
+ if (vocread(ctx, MCMONINV, MCMONINV,
+ oopsbuf, (int)oopsbuflen, 1) == VOCREAD_REDO)
+ {
+ /*
+ * we've already decided it's not an OOPS input - return null to
+ * indicate to the caller that we have a new command
+ */
+ return 0;
+ }
+
+ /* lower-case the string */
+ for (p = oopsbuf ; *p != '\0' ; ++p)
+ *p = (vocisupper(*p) ? tolower(*p) : *p);
+
+ /* skip leading spaces */
+ for (p = oopsbuf ; vocisspace(*p) ; ++p) ;
+
+ /*
+ * See if they are saying "oops". Allow "oops" or simply "o",
+ * followed by either a space or a comma.
+ */
+ if ((strlen(p) > 5 && memcmp(p, "oops ", 5) == 0)
+ || (strlen(p) > 5 && memcmp(p, "oops,", 5) == 0))
+ {
+ /* we found "OOPS" - move to the next character */
+ p += 5;
+ }
+ else if ((strlen(p) > 2 && memcmp(p, "o ", 2) == 0)
+ || (strlen(p) > 2 && memcmp(p, "o,", 2) == 0))
+ {
+ /* we found "O" - move to the next character */
+ p += 2;
+ }
+ else
+ {
+ /*
+ * we didn't find any form of "OOPS" response - return null to
+ * indicate to the caller that the player entered a new command
+ */
+ return 0;
+ }
+
+ /* skip spaces before the replacement text */
+ for ( ; vocisspace(*p) ; ++p) ;
+
+ /* return a pointer to the start of the replacement text */
+ return p;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * figure out what parts of speech are associated with each
+ * word in a tokenized command list
+ */
+int vocgtyp(voccxdef *ctx, char *cmd[], int types[], char *orgbuf)
+{
+ int cur;
+ int t;
+ char *p;
+ int len;
+ int unknown_count = 0;
+
+startover:
+ if (ctx->voccxflg & VOCCXFDBG)
+ tioputs(ctx->vocxtio, ". Checking words:\\n");
+
+ for (cur = 0 ; cmd[cur] ; ++cur)
+ {
+ /* get the word */
+ p = cmd[cur];
+ len = strlen(p);
+
+ /* look it up */
+ t = voc_lookup_type(ctx, p, len, FALSE);
+
+ /* see if the word was found */
+ if (t == 0 && !voc_check_special(ctx, p, VOCW_OF))
+ {
+ /*
+ * We didn't find the word. For now, set its type to
+ * "unknown".
+ */
+ t = VOCT_UNKNOWN;
+
+ /*
+ * If the unknown word count is already non-zero, it means
+ * that we've tried to let the game resolve this word using
+ * the parseUnknownDobj/Iobj mechanism, but it wasn't able
+ * to do so, thus we've come back here to use the normal
+ * "oops" processing instead.
+ *
+ * Don't generate a message until we get to the first
+ * unknown word from the original list that we weren't able
+ * to resolve. We may have been able to handle one or more
+ * of the original list of unknown words (through
+ * parseNounPhrase or other means), so we don't want to
+ * generate a message for any words we ended up handling.
+ * The number we resolved is the last full unknown count
+ * minus the remaining unknown count.
+ */
+ if (ctx->voccxunknown != 0
+ && unknown_count >= ctx->voccxlastunk - ctx->voccxunknown)
+ {
+ char oopsbuf[VOCBUFSIZ];
+ char *p1;
+
+ /*
+ * we can try using the parseUnknownDobj/Iobj again
+ * after this, so clear the unknown word count for now
+ */
+ ctx->voccxunknown = 0;
+
+ /* display an error, and ask for a new command */
+ p1 = voc_read_oops(ctx, oopsbuf, sizeof(oopsbuf), p);
+
+ /* if they responded with replacement text, apply it */
+ if (p1 != 0)
+ {
+ char redobuf[200];
+ char *q;
+ int i;
+ int wc;
+ char **w;
+ char *outp;
+
+ /*
+ * copy words from the original string, replacing
+ * the unknown word with what follows the "oops" in
+ * the new command
+ */
+ for (outp = redobuf, i = 0, w = cmd ; *w != 0 ; ++i, ++w)
+ {
+
+ /* see what we have */
+ if (i == cur)
+ {
+ /*
+ * We've reached the word to be replaced.
+ * Ignore the original token, and replace it
+ * with the word or words from the OOPS
+ * command
+ */
+ for (q = p1, len = 0 ;
+ *q != '\0' && *q != '.' && *q != ','
+ && *q != '?' && *q != '!' ; ++q, ++len) ;
+ memcpy(outp, p1, (size_t)len);
+ outp += len;
+ }
+ else if (**w == '"')
+ {
+ char *strp;
+ char *p2;
+ char qu;
+ int rem;
+
+ /*
+ * It's a string - add a quote mark, then
+ * copy the string as indicated by the
+ * length prefix, then add another quote
+ * mark. Get the length by reading the
+ * length prefix following the quote mark,
+ * and get a pointer to the text of the
+ * string, which immediately follows the
+ * length prefix.
+ */
+ len = osrp2(*w + 1) - 2;
+ strp = *w + 3;
+
+ /*
+ * We need to figure out what kind of quote
+ * mark to use. If the string contains any
+ * embedded double quotes, use single quotes
+ * to delimit the string; otherwise, use
+ * double quotes. Presume we'll use double
+ * quotes as the delimiter, then scan the
+ * string for embedded double quotes.
+ */
+ for (qu = '"', p2 = strp, rem = len ; rem != 0 ;
+ --rem, ++p2)
+ {
+ /*
+ * if this is an embedded double quote,
+ * use single quotes to delimite the
+ * string
+ */
+ if (*p2 == '"')
+ {
+ /* use single quotes as delimiters */
+ qu = '\'';
+
+ /* no need to look any further */
+ break;
+ }
+ }
+
+ /* add the open quote */
+ *outp++ = qu;
+
+ /* copy the string */
+ memcpy(outp, strp, len);
+ outp += len;
+
+ /* add the close quote */
+ *outp++ = qu;
+ }
+ else
+ {
+ /*
+ * it's an ordinary token - copy the
+ * null-terminated string for the token from
+ * the original command list
+ */
+ len = strlen(*w);
+ memcpy(outp, *w, (size_t)len);
+ outp += len;
+ }
+
+ /* add a space between words */
+ *outp++ = ' ';
+ }
+
+ /* terminate the new string */
+ *outp = '\0';
+
+ /* try tokenizing the string */
+ *(cmd[0]) = '\0';
+ if ((wc = voctok(ctx, redobuf, cmd[0],
+ cmd, FALSE, FALSE, TRUE)) <= 0)
+ return 1;
+ cmd[wc] = 0;
+
+ /* start over with the typing */
+ goto startover;
+ }
+ else
+ {
+ /*
+ * They didn't start the command with "oops", so
+ * this must be a brand new command. Replace the
+ * original command with the new command.
+ */
+ strcpy(orgbuf, oopsbuf);
+
+ /*
+ * forget we had an unknown word so that we're sure
+ * to start over with a new command
+ */
+ ctx->voccxunknown = 0;
+
+ /*
+ * set the "redo" flag to start over with the new
+ * command
+ */
+ ctx->voccxredo = 1;
+
+ /*
+ * return an error to indicate the current command
+ * has been aborted
+ */
+ return 1;
+ }
+ }
+ else
+ {
+ /*
+ * We've now encountered an unknown word, and we're
+ * going to defer resolution. Remember this; we'll
+ * count the unknown word in the context when we return
+ * (do so only locally for now, since we may encounter
+ * more unknown words before we return, in which case we
+ * want to know that this is still the first pass).
+ */
+ ++unknown_count;
+ }
+ }
+
+ /* display if in debug mode */
+ if (ctx->voccxflg & VOCCXFDBG)
+ {
+ char buf[128];
+ size_t i;
+ //char *p;
+ int cnt;
+
+ (void)tioshow(ctx->voccxtio);
+ sprintf(buf, "... %s (", cmd[cur]);
+ p = buf + strlen(buf);
+ cnt = 0;
+ for (i = 0 ; i < sizeof(type_names)/sizeof(type_names[0]) ; ++i)
+ {
+ if (t & (1 << i))
+ {
+ if (cnt) *p++ = ',';
+ strcpy(p, type_names[i]);
+ p += strlen(p);
+ ++cnt;
+ }
+ }
+ *p++ = ')';
+ *p++ = '\\';
+ *p++ = 'n';
+ *p = '\0';
+ tioputs(ctx->voccxtio, buf);
+ }
+
+ types[cur] = t; /* record type of this word */
+ }
+
+ /* if we found any unknown words, note this in our context */
+ ctx->voccxunknown = unknown_count;
+ ctx->voccxlastunk = unknown_count;
+
+ /* successful acquisition of types */
+ return 0;
+}
+
+/*
+ * intersect - takes two lists and puts the intersection of them into
+ * the first list.
+ */
+static int vocisect(objnum *list1, objnum *list2)
+{
+ int i, j, k;
+
+ for (i = k = 0 ; list1[i] != MCMONINV ; ++i)
+ {
+ for (j = 0 ; list2[j] != MCMONINV ; ++j)
+ {
+ if (list1[i] == list2[j])
+ {
+ list1[k++] = list1[i];
+ break;
+ }
+ }
+ }
+ list1[k] = MCMONINV;
+ return(k);
+}
+
+/*
+ * Intersect lists, including parallel flags lists. The flags from the
+ * two lists for any matching object are OR'd together.
+ */
+static int vocisect_flags(objnum *list1, uint *flags1,
+ objnum *list2, uint *flags2)
+{
+ int i, j, k;
+
+ for (i = k = 0 ; list1[i] != MCMONINV ; ++i)
+ {
+ for (j = 0 ; list2[j] != MCMONINV ; ++j)
+ {
+ if (list1[i] == list2[j])
+ {
+ list1[k] = list1[i];
+ flags1[k] = flags1[i] | flags2[j];
+ ++k;
+ break;
+ }
+ }
+ }
+ list1[k] = MCMONINV;
+ return(k);
+}
+
+/*
+ * get obj list: build a list of the objects that are associated with a
+ * given word of player input.
+ */
+static int vocgol(voccxdef *ctx, objnum *list, uint *flags, char **wrdlst,
+ int *typlst, int first, int cur, int last, int ofword)
+{
+ char *wrd;
+ int typ;
+ vocwdef *v;
+ int cnt;
+ int len;
+ vocseadef search_ctx;
+ int try_plural;
+ int try_noun_before_num;
+ int try_endadj;
+ int trying_endadj;
+ int wrdtyp;
+
+ /* get the current word and its type */
+ wrd = wrdlst[cur];
+ typ = typlst[cur];
+
+ /* get the length of the word */
+ len = strlen(wrd);
+
+ /*
+ * Get word type: figure out the correct part of speech, given by
+ * context, for a given word. If it could count as only a
+ * noun/plural or only an adjective, we use that. If it could count
+ * as either a noun/plural or an adjective, we will treat it as a
+ * noun/plural if it is the last word in the name or the last word
+ * before "of", otherwise as an adjective.
+ *
+ * If the word is unknown, treat it as a noun or adjective - treat
+ * it as part of the current noun phrase. One unknown word renders
+ * the whole noun phrase unknown.
+ */
+ try_plural = (typ & VOCT_PLURAL);
+
+ /* presume we won't retry this word as an adjective */
+ try_endadj = FALSE;
+
+ /* presume we won't retry this as a noun before a number */
+ try_noun_before_num = FALSE;
+
+ /* we're not yet trying with adjective-at-end */
+ trying_endadj = FALSE;
+
+ /* check to see what parts of speech are defined for this word */
+ if ((typ & (VOCT_NOUN | VOCT_PLURAL)) && (typ & VOCT_ADJ))
+ {
+ /*
+ * This can be either an adjective or a plural/noun. If this is
+ * the last word in the noun phrase, treat it as a noun/plural if
+ * possible. Otherwise, treat it as an adjective.
+ */
+ if (cur + 1 == last || cur == ofword - 1)
+ {
+ /*
+ * This is the last word in the entire phrase, or the last word
+ * before an 'of' (which makes it the last word of its
+ * subphrase). Treat it as a noun if possible, otherwise as a
+ * plural
+ */
+ wrdtyp = ((typ & VOCT_NOUN) ? PRP_NOUN : PRP_PLURAL);
+
+ /*
+ * If this can be an adjective, too, make a note to come back
+ * and try it again as an adjective. We prefer not to end a
+ * noun phrase with an adjective, but we allow it, since it's
+ * often convenient to abbreviate a noun phrase to just the
+ * adjectives (as in TAKE RED, where there's only one object
+ * nearby to which RED applies).
+ */
+ if ((typ & VOCT_ADJ) != 0)
+ try_endadj = TRUE;
+ }
+ else if ((cur + 2 == last || cur == ofword - 2)
+ && vocisdigit(wrdlst[cur+1][0]))
+ {
+ /*
+ * This is the second-to-last word, and the last word is
+ * numeric. In this case, try this word as BOTH a noun and an
+ * adjective. Try it as an adjective first, but make a note to
+ * go back and try it again as a noun.
+ */
+ wrdtyp = PRP_ADJ;
+ try_noun_before_num = TRUE;
+ }
+ else
+ {
+ /*
+ * This isn't the last word, so it can only be an adjective.
+ * Look at it only as an adjective.
+ */
+ wrdtyp = PRP_ADJ;
+ }
+ }
+ else if (typ & VOCT_NOUN)
+ wrdtyp = PRP_NOUN;
+ else if (typ & VOCT_UNKNOWN)
+ wrdtyp = PRP_UNKNOWN;
+ else
+ {
+ /* it's just an adjective */
+ wrdtyp = PRP_ADJ;
+
+ /*
+ * if this is the last word in the phrase, flag it as an ending
+ * adjective
+ */
+ if (cur + 1 == last || cur == ofword - 1)
+ trying_endadj = TRUE;
+ }
+
+ /* display debugger information if appropriate */
+ if (ctx->voccxflg & VOCCXFDBG)
+ {
+ char buf[128];
+
+ sprintf(buf, "... %s (treating as %s%s)\\n", wrd,
+ (wrdtyp == PRP_ADJ ? "adjective" :
+ wrdtyp == PRP_NOUN ? "noun" :
+ wrdtyp == PRP_INVALID ? "unknown" : "plural"),
+ (wrdtyp == PRP_NOUN && try_plural ? " + plural" : ""));
+ tioputs(ctx->vocxtio, buf);
+ }
+
+ /* if this is an unknown word, it doesn't have any objects */
+ if (wrdtyp == PRP_UNKNOWN)
+ {
+ list[0] = MCMONINV;
+ return 0;
+ }
+
+ /* we have nothing in the list yet */
+ cnt = 0;
+
+add_words:
+ for (v = vocffw(ctx, wrd, len, (char *)0, 0, wrdtyp, &search_ctx)
+ ; v != 0 ; v = vocfnw(ctx, &search_ctx))
+ {
+ int i;
+
+ /* add the matching object to the output list */
+ list[cnt] = v->vocwobj;
+
+ /* clear the flags */
+ flags[cnt] = 0;
+
+ /* set the PLURAL flag if this is the plural vocabulary usage */
+ if (wrdtyp == PRP_PLURAL)
+ flags[cnt] |= VOCS_PLURAL;
+
+ /* set the ADJECTIVE AT END flag if appropriate */
+ if (wrdtyp == PRP_ADJ && trying_endadj)
+ flags[cnt] |= VOCS_ENDADJ;
+
+ /*
+ * if this is not an exact match for the word, but is merely a
+ * long-enough leading substring, flag it as truncated
+ */
+ if (len < search_ctx.v->voclen)
+ flags[cnt] |= VOCS_TRUNC;
+
+ /* count the additional word in the list */
+ ++cnt;
+
+ /*
+ * if this object is already in the list with the same flags,
+ * don't add it again
+ */
+ for (i = 0 ; i < cnt - 1 ; ++i)
+ {
+ /* check for an identical entry */
+ if (list[i] == list[cnt-1] && flags[i] == flags[cnt-1])
+ {
+ /* take it back out of the list */
+ --cnt;
+
+ /* no need to continue looking for the duplicate */
+ break;
+ }
+ }
+
+ /* make sure we haven't overflowed the list */
+ if (cnt >= VOCMAXAMBIG)
+ {
+ vocerr(ctx, VOCERR(3),
+ "The word \"%s\" refers to too many objects.", wrd);
+ list[0] = MCMONINV;
+ return -1;
+ }
+ }
+
+ /*
+ * if we want to go back and try the word again as a noun before a
+ * number (as in "button 5"), do so now
+ */
+ if (try_noun_before_num && wrdtyp == PRP_ADJ)
+ {
+ /* change the word type to noun */
+ wrdtyp = PRP_NOUN;
+
+ /* don't try this again */
+ try_noun_before_num = FALSE;
+
+ /* add the words for the noun usage */
+ goto add_words;
+ }
+
+ /*
+ * if we're interpreting the word as a noun, and the word can be a
+ * plural, add in the plural interpretation as well
+ */
+ if (try_plural && wrdtyp != PRP_PLURAL)
+ {
+ /* change the word type to plural */
+ wrdtyp = PRP_PLURAL;
+
+ /* don't try plurals again */
+ try_plural = FALSE;
+
+ /* add the words for the plural usage */
+ goto add_words;
+ }
+
+ /*
+ * if this was the last word in the phrase, and it could have been
+ * an adjective, try it again as an adjective
+ */
+ if (try_endadj && wrdtyp != PRP_ADJ)
+ {
+ /* change the word type to adjective */
+ wrdtyp = PRP_ADJ;
+
+ /* note that we're retrying as an adjective */
+ trying_endadj = TRUE;
+
+ /* don't try this again */
+ try_endadj = FALSE;
+
+ /* add the words for the adjective usage */
+ goto add_words;
+ }
+
+ /*
+ * If we're interpreting the word as an adjective, and it's
+ * numeric, include objects with "#" in their adjective list --
+ * these objects allow arbitrary numbers as adjectives. Don't do
+ * this if there's only the one word.
+ */
+ if (vocisdigit(wrd[0]) && wrdtyp == PRP_ADJ && first + 1 != last)
+ {
+ wrd = "#";
+ len = 1;
+ goto add_words;
+ }
+
+ list[cnt] = MCMONINV;
+ return cnt;
+}
+
+/*
+ * Add the user-defined word for "of" to a buffer. If no such word is
+ * defined by the user (with the specialWords construct), add "of".
+ */
+static void vocaddof(voccxdef *ctx, char *buf)
+{
+ if (ctx->voccxspp)
+ {
+ size_t len = ctx->voccxspp[1];
+ size_t oldlen = strlen(buf);
+ memcpy(buf + oldlen, ctx->voccxspp + 2, len);
+ buf[len + oldlen] = '\0';
+ }
+ else
+ strcat(buf, "of");
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Call the parseNounPhrase user function, if defined, to attempt to
+ * parse a noun phrase.
+ *
+ * Returns VOC_PNP_ERROR if the hook function indicates that an error
+ * occurred; PNP_DEFAULT if the hook function told us to use the default
+ * list; or PNP_SUCCESS to indicate that the hook function provided a
+ * list to use.
+ */
+static int voc_pnp_hook(voccxdef *ctx, char *cmd[], int typelist[],
+ int cur, int *next, int complain,
+ vocoldef *out_nounlist, int *out_nouncount,
+ int chkact, int *no_match)
+{
+ runcxdef *rcx = ctx->voccxrun;
+ runsdef val;
+ int wordcnt;
+ char **cmdp;
+ int outcnt;
+ vocoldef *outp;
+ int i;
+
+ /* if parseNounPhrase isn't defined, use the default handling */
+ if (ctx->voccxpnp == MCMONINV)
+ return VOC_PNP_DEFAULT;
+
+ /* push the actor-check flag */
+ val.runstyp = (chkact ? DAT_TRUE : DAT_NIL);
+ runpush(rcx, val.runstyp, &val);
+
+ /* push the complain flag */
+ val.runstyp = (complain ? DAT_TRUE : DAT_NIL);
+ runpush(rcx, val.runstyp, &val);
+
+ /* push the current index (adjusted to 1-based user convention) */
+ runpnum(rcx, cur + 1);
+
+ /* count the entries in the command list */
+ for (wordcnt = 0, cmdp = cmd ; *cmdp != 0 && **cmdp != '\0' ;
+ ++wordcnt, ++cmdp) ;
+
+ /* push the type list */
+ voc_push_numlist(ctx, (uint *)typelist, wordcnt);
+
+ /* push the command word list */
+ voc_push_strlist_arr(ctx, cmd, wordcnt);
+
+ /* call the method */
+ runfn(rcx, ctx->voccxpnp, 5);
+
+ /* check the return value */
+ if (runtostyp(rcx) == DAT_NUMBER)
+ {
+ /* return the status code directly from the hook function */
+ return (int)runpopnum(rcx);
+ }
+ else if (runtostyp(rcx) == DAT_LIST)
+ {
+ uchar *lstp;
+ uint lstsiz;
+
+ /* pop the list */
+ lstp = runpoplst(rcx);
+
+ /* read and skip the size prefix */
+ lstsiz = osrp2(lstp);
+ lstsiz -= 2;
+ lstp += 2;
+
+ /* the first element should be the next index */
+ if (lstsiz > 1 && *lstp == DAT_NUMBER)
+ {
+ /* set the 'next' pointer, adjusting to 0-based indexing */
+ *next = osrp4(lstp+1) - 1;
+
+ /*
+ * If 'next' is out of range, force it into range. We can't
+ * go backwards (so 'next' must always be at least 'cur'),
+ * and we can't go past the null element at the end of the
+ * list.
+ */
+ if (*next < cur)
+ *next = cur;
+ else if (*next > wordcnt)
+ *next = wordcnt;
+
+ /* skip the list entry */
+ lstadv(&lstp, &lstsiz);
+ }
+ else
+ {
+ /* ignore the list and use the default parsing */
+ return VOC_PNP_DEFAULT;
+ }
+
+ /* read the list entries and store them in the output array */
+ for (outcnt = 0, outp = out_nounlist ; lstsiz > 0 ; )
+ {
+ /* make sure we have room for another entry */
+ if (outcnt >= VOCMAXAMBIG - 1)
+ break;
+
+ /* get the next list entry, and store it in the output array */
+ if (*lstp == DAT_NIL)
+ {
+ /* set the list entry */
+ outp->vocolobj = MCMONINV;
+
+ /* skip the entry */
+ lstadv(&lstp, &lstsiz);
+ }
+ else if (*lstp == DAT_OBJECT)
+ {
+ /* set the list entry */
+ outp->vocolobj = osrp2(lstp+1);
+
+ /* skip the list entry */
+ lstadv(&lstp, &lstsiz);
+ }
+ else
+ {
+ /* ignore other types in the list */
+ lstadv(&lstp, &lstsiz);
+ continue;
+ }
+
+ /* check for a flag entry */
+ if (lstsiz > 0 && *lstp == DAT_NUMBER)
+ {
+ /* set the flags */
+ outp->vocolflg = (int)osrp4(lstp+1);
+
+ /* skip the number */
+ lstadv(&lstp, &lstsiz);
+ }
+ else
+ {
+ /* no flags were specified - use the default */
+ outp->vocolflg = 0;
+ }
+
+ /* set the word list boundaries */
+ outp->vocolfst = cmd[cur];
+ outp->vocollst = cmd[*next - 1];
+
+ /* count the entry */
+ ++outp;
+ ++outcnt;
+ }
+
+ /* terminate the list */
+ outp->vocolobj = MCMONINV;
+ outp->vocolflg = 0;
+
+ /* set the output count */
+ *out_nouncount = outcnt;
+
+ /*
+ * set "no_match" appropriately -- set "no_match" true if we're
+ * returning an empty list and we parsed one or more words
+ */
+ if (no_match != 0)
+ *no_match = (outcnt == 0 && *next > cur);
+
+ /*
+ * Adjust the unknown word count in the context. If the routine
+ * parsed any unknown words, decrement the unknown word count in
+ * the context by the number of unknown words parsed, since
+ * these have now been dealt with. If the return list contains
+ * any objects flagged as having unknown words, add the count of
+ * such objects back into the context, since we must still
+ * resolve these at disambiguation time.
+ */
+ for (i = cur ; i < *next ; ++i)
+ {
+ /* if this parsed word was unknown, remove it from the count */
+ if ((typelist[i] & VOCT_UNKNOWN) != 0)
+ --(ctx->voccxunknown);
+ }
+ for (i = 0, outp = out_nounlist ; i < outcnt ; ++i)
+ {
+ /* if this object has the unknown flag, count it */
+ if ((outp->vocolflg & VOCS_UNKNOWN) != 0)
+ ++(ctx->voccxunknown);
+ }
+
+ /* indicate that the hook provided a list */
+ return VOC_PNP_SUCCESS;
+ }
+ else
+ {
+ /*
+ * ignore any other return value - consider others equivalent to
+ * DEFAULT
+ */
+ rundisc(rcx);
+ return VOC_PNP_DEFAULT;
+ }
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Build an object name from the words in a command
+ */
+void voc_make_obj_name(voccxdef *ctx, char *namebuf, char *cmd[],
+ int firstwrd, int lastwrd)
+{
+ int i;
+
+ /* run through the range of words, and add them to the buffer */
+ for (i = firstwrd, namebuf[0] = '\0' ; i < lastwrd ; ++i)
+ {
+ if (voc_check_special(ctx, cmd[i], VOCW_OF))
+ vocaddof(ctx, namebuf);
+ else
+ strcat(namebuf, cmd[i]);
+
+ if (cmd[i][strlen(cmd[i])-1] == '.' && i + 1 < lastwrd)
+ strcat(namebuf, "\\");
+
+ if (i + 1 < lastwrd)
+ strcat(namebuf, " ");
+ }
+}
+
+/*
+ * 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)
+{
+ int i, i1, i2;
+
+ /* find the cmd indices */
+ for (i = i1 = i2 = 0 ; cmd[i] != 0 && *cmd[i] != 0 ; ++i)
+ {
+ if (cmd[i] == firstwrd)
+ i1 = i;
+ if (cmd[i] == lastwrd)
+ i2 = i + 1;
+ }
+
+ /* build the name */
+ voc_make_obj_name(ctx, namebuf, cmd, i1, i2);
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * get 1 obj - attempts to figure out the limits of a single noun
+ * phrase. Aside from dealing with special words here ("all", "it",
+ * "them", string objects, numeric objects), we will accept a basic noun
+ * phrase of the form [article][adjective*][noun]["of" [noun-phrase]].
+ * (Note that this is not actually recursive; only one "of" can occur in
+ * a noun phrase.) If successful, we will construct a list of all
+ * objects that have all the adjectives and nouns in the noun phrase.
+ * Note that plurals are treated basically like nouns, except that we
+ * will flag them so that the disambiguator knows to include all objects
+ * that work with the plural.
+ *
+ * Note that we also allow the special constructs "all [of]
+ * <noun-phrase>" and "both [of] <noun-phrase>"; these are treated
+ * identically to normal plurals.
+ *
+ * If no_match is not null, we'll set it to true if we found valid
+ * syntax but no matching objects, false otherwise.
+ */
+static int vocg1o(voccxdef *ctx, char *cmd[], int typelist[],
+ int cur, int *next, int complain, vocoldef *nounlist,
+ int chkact, int *no_match)
+{
+ int l1;
+ int firstwrd;
+ int i;
+ int ofword = -1;
+ int hypothetical_last = -1;
+ int trim_flags = 0;
+ int outcnt = 0;
+ objnum *list1;
+ uint *flags1;
+ objnum *list2;
+ uint *flags2;
+ char *namebuf;
+ int has_any = FALSE;
+ uchar *save_sp;
+ int found_plural;
+ int unknown_count;
+ int trying_count = FALSE;
+ int retry_with_count;
+
+ voc_enter(ctx, &save_sp);
+ VOC_MAX_ARRAY(ctx, objnum, list1);
+ VOC_MAX_ARRAY(ctx, uint, flags1);
+ VOC_MAX_ARRAY(ctx, objnum, list2);
+ VOC_MAX_ARRAY(ctx, uint, flags2);
+ VOC_STK_ARRAY(ctx, char, namebuf, VOCBUFSIZ);
+
+ /* presume we'll find a match */
+ if (no_match != 0)
+ *no_match = FALSE;
+
+ /* start at the first word */
+ *next = cur;
+
+ /* if we're at the end of the command, return no objects in list */
+ if (cur == -1 || !cmd[cur]) { VOC_RETVAL(ctx, save_sp, 0); }
+
+ /* 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);
+ }
+
+ /* try the user parseNounPhrase hook */
+ switch(voc_pnp_hook(ctx, cmd, typelist, cur, next, complain,
+ nounlist, &outcnt, chkact, no_match))
+ {
+ case VOC_PNP_DEFAULT:
+ /* continue on to the default processing */
+ break;
+
+ case VOC_PNP_ERROR:
+ default:
+ /* return an error */
+ VOC_RETVAL(ctx, save_sp, -1);
+
+ case VOC_PNP_SUCCESS:
+ /* use their list */
+ VOC_RETVAL(ctx, save_sp, outcnt);
+ }
+
+ /* check for a quoted string */
+ if (*cmd[cur] == '"')
+ {
+ /* can't use a quoted string as an actor */
+ if (chkact) { VOC_RETVAL(ctx, save_sp, 0); }
+
+ if (ctx->voccxflg & VOCCXFDBG)
+ tioputs(ctx->vocxtio, "... found quoted string\\n");
+
+ nounlist[outcnt].vocolobj = MCMONINV;
+ nounlist[outcnt].vocolflg = VOCS_STR;
+ nounlist[outcnt].vocolfst = nounlist[outcnt].vocollst = cmd[cur];
+ *next = ++cur;
+ ++outcnt;
+ VOC_RETVAL(ctx, save_sp, outcnt);
+ }
+
+ /* check for ALL/ANY/BOTH/EITHER [OF] <plural> contruction */
+ if ((vocspec(cmd[cur], VOCW_ALL)
+ || vocspec(cmd[cur], VOCW_BOTH)
+ || vocspec(cmd[cur], VOCW_ANY)) &&
+ cmd[cur+1] != (char *)0)
+ {
+ int nxt;
+ int n = cur+1;
+ int has_of;
+
+ /* can't use ALL as an actor */
+ if (chkact) { VOC_RETVAL(ctx, save_sp, 0); }
+
+ /* remember whether we have "any" or "either" */
+ has_any = vocspec(cmd[cur], VOCW_ANY);
+
+ /* check for optional 'of' */
+ if (voc_check_special(ctx, cmd[n], VOCW_OF))
+ {
+ if (ctx->voccxflg & VOCCXFDBG)
+ tioputs(ctx->vocxtio, "... found ALL/ANY/BOTH/EITHER OF\\n");
+
+ has_of = TRUE;
+ n++;
+ if (!cmd[n])
+ {
+ char *p;
+ int ver;
+
+ if (vocspec(cmd[cur], VOCW_ALL))
+ {
+ ver = VOCERR(4);
+ p = "I think you left something out after \"all of\".";
+ }
+ else if (vocspec(cmd[cur], VOCW_ANY))
+ {
+ ver = VOCERR(29);
+ p = "I think you left something out after \"any of\".";
+ }
+ else
+ {
+ ver = VOCERR(5);
+ p = "There's something missing after \"both of\".";
+ }
+ vocerr(ctx, ver, p);
+ VOC_RETVAL(ctx, save_sp, -1);
+ }
+ }
+ else
+ has_of = FALSE;
+
+ nxt = n;
+ if (typelist[n] & VOCT_ARTICLE) ++n; /* skip leading article */
+ for ( ;; )
+ {
+ if (!cmd[n])
+ break;
+
+ if (voc_check_special(ctx, cmd[n], VOCW_OF))
+ {
+ ++n;
+ if (!cmd[n])
+ {
+ vocerr(ctx, VOCERR(6), "I expected a noun after \"of\".");
+ VOC_RETVAL(ctx, save_sp, -1);
+ }
+ if (*cmd[n] & VOCT_ARTICLE) ++n;
+ }
+ else if (typelist[n] & (VOCT_ADJ | VOCT_NOUN))
+ ++n;
+ else
+ break;
+ }
+
+ /*
+ * Accept the ALL if the last word is a plural. Accept the ANY
+ * if either we don't have an OF (ANY NOUN is okay even without
+ * a plural), or if we have OF and a plural. (More simply put,
+ * accept the ALL or ANY if the last word is a plural, or if we
+ * have ANY but not OF).
+ */
+ if (n > cur && ((typelist[n-1] & VOCT_PLURAL)
+ || (has_any && !has_of)))
+ {
+ if (ctx->voccxflg & VOCCXFDBG)
+ tioputs(ctx->vocxtio,
+ "... found ALL/ANY/BOTH/EITHER + noun phrase\\n");
+
+ cur = nxt;
+ }
+ }
+
+ if (vocspec(cmd[cur], VOCW_ALL) && !has_any)
+ {
+ /* can't use ALL as an actor */
+ if (chkact)
+ {
+ VOC_RETVAL(ctx, save_sp, 0);
+ }
+
+ if (ctx->voccxflg & VOCCXFDBG)
+ tioputs(ctx->vocxtio, "... found ALL\\n");
+
+ nounlist[outcnt].vocolobj = MCMONINV;
+ nounlist[outcnt].vocolflg = VOCS_ALL;
+ nounlist[outcnt].vocolfst = nounlist[outcnt].vocollst = cmd[cur];
+ ++outcnt;
+ ++cur;
+
+ if (cmd[cur] && vocspec(cmd[cur], VOCW_BUT))
+ {
+ int cnt;
+ //int i;
+ vocoldef *xlist;
+ uchar *inner_save_sp;
+
+ if (ctx->voccxflg & VOCCXFDBG)
+ tioputs(ctx->vocxtio, "... found ALL EXCEPT\\n");
+
+ voc_enter(ctx, &inner_save_sp);
+ VOC_MAX_ARRAY(ctx, vocoldef, xlist);
+
+ cur++;
+ cnt = vocgobj(ctx, cmd, typelist, cur, next, complain, xlist, 1,
+ chkact, 0);
+ if (cnt < 0)
+ {
+ /*
+ * An error occurred - return it. Note that, since
+ * we're returning from the entire function, we're
+ * popping the save_sp frame, NOT the inner_save_sp
+ * frame -- the inner frame is nested within the save_sp
+ * frame, and we want to pop the entire way out since
+ * we're exiting the entire function.
+ */
+ VOC_RETVAL(ctx, save_sp, cnt);
+ }
+ cur = *next;
+ for (i = 0 ; i < cnt ; ++i)
+ {
+ OSCPYSTRUCT(nounlist[outcnt], xlist[i]);
+ nounlist[outcnt].vocolflg |= VOCS_EXCEPT;
+ ++outcnt;
+ }
+
+ voc_leave(ctx, inner_save_sp);
+ }
+ *next = cur;
+ nounlist[outcnt].vocolobj = MCMONINV;
+ nounlist[outcnt].vocolflg = 0;
+ VOC_RETVAL(ctx, save_sp, outcnt);
+ }
+
+ switch(*cmd[cur])
+ {
+ case VOCW_IT:
+ nounlist[outcnt].vocolflg = VOCS_IT;
+ goto do_special;
+ case VOCW_THEM:
+ nounlist[outcnt].vocolflg = VOCS_THEM;
+ goto do_special;
+ case VOCW_HIM:
+ nounlist[outcnt].vocolflg = VOCS_HIM;
+ goto do_special;
+ case VOCW_HER:
+ nounlist[outcnt].vocolflg = VOCS_HER;
+ /* FALLTHRU */
+ do_special:
+ if (ctx->voccxflg & VOCCXFDBG)
+ tioputs(ctx->vocxtio, "... found pronoun\\n");
+
+ *next = cur + 1;
+ nounlist[outcnt].vocolobj = MCMONINV;
+ nounlist[outcnt].vocolfst = nounlist[outcnt].vocollst = cmd[cur];
+ ++outcnt;
+ VOC_RETVAL(ctx, save_sp, outcnt);
+ default:
+ break;
+ }
+
+ if (((typelist[cur]
+ & (VOCT_ARTICLE | VOCT_ADJ | VOCT_NOUN | VOCT_UNKNOWN)) == 0)
+ && !vocisdigit(*cmd[cur]))
+ {
+ VOC_RETVAL(ctx, save_sp, 0);
+ }
+
+ if (typelist[cur] & VOCT_ARTICLE)
+ {
+ ++cur;
+ if (cmd[cur] == (char *)0
+ || ((typelist[cur] & (VOCT_ADJ | VOCT_NOUN | VOCT_UNKNOWN)) == 0
+ && !vocisdigit(*cmd[cur])))
+ {
+ vocerr(ctx, VOCERR(7), "An article must be followed by a noun.");
+ *next = cur;
+ VOC_RETVAL(ctx, save_sp, -1);
+ }
+ }
+
+ /* start at the current word */
+ firstwrd = cur;
+
+ /* scan words for inclusion in this noun phrase */
+ for (found_plural = FALSE, unknown_count = 0, l1 = 0 ; ; )
+ {
+ if (cmd[cur] == (char *)0)
+ break;
+
+ if (typelist[cur] & VOCT_ADJ)
+ ++cur;
+ else if (typelist[cur] & VOCT_UNKNOWN)
+ {
+ /*
+ * Remember that we found an unknown word, but include it in
+ * the noun phrase - this will render the entire noun phrase
+ * unknown, but we'll resolve that later.
+ */
+ ++unknown_count;
+ ++cur;
+ }
+ else if (typelist[cur] & VOCT_NOUN)
+ {
+ ++cur;
+ if (cmd[cur] == (char *)0) break;
+ if (vocisdigit(*cmd[cur])) ++cur;
+ if (cmd[cur] == (char *)0) break;
+ if (!voc_check_special(ctx, cmd[cur], VOCW_OF)) break;
+ }
+ else if (vocisdigit(*cmd[cur]))
+ ++cur;
+ else if (voc_check_special(ctx, cmd[cur], VOCW_OF))
+ {
+ ++cur;
+ if (ofword != -1)
+ {
+ /* there's already one 'of' - we must be done */
+ --cur;
+ break;
+ }
+ ofword = cur-1;
+ if (typelist[cur] & VOCT_ARTICLE) /* allow article after "of" */
+ ++cur;
+ }
+ else
+ break;
+
+ /* note whether we found anything that might be a plural */
+ if (cmd[cur] != 0 && (typelist[cur] & VOCT_PLURAL) != 0)
+ found_plural = TRUE;
+ }
+
+try_again:
+ /*
+ * build a printable string consisting of the words in the noun
+ * phrase, for displaying error messages
+ */
+ voc_make_obj_name(ctx, namebuf, cmd, firstwrd, cur);
+
+ /* remember the next word after this noun phrase */
+ *next = cur;
+
+ /*
+ * If we have any unknown words, we won't be able to match any
+ * objects for the noun phrase. Return with one entry in the list,
+ * but use an invalid object and mark the object as containing
+ * unknown words.
+ */
+ if (unknown_count > 0)
+ {
+ /*
+ * Add one unknown object for each unknown word. This lets us
+ * communicate the number of unknown words that we found to the
+ * disambiguator, which will later attempt to resolve the
+ * reference. Each object we add is the same; they're here only
+ * for the word count.
+ */
+ for ( ; unknown_count > 0 ; --unknown_count)
+ {
+ nounlist[outcnt].vocolobj = MCMONINV;
+ nounlist[outcnt].vocolflg = VOCS_UNKNOWN;
+ nounlist[outcnt].vocolfst = cmd[firstwrd];
+ nounlist[outcnt].vocollst = cmd[cur-1];
+ ++outcnt;
+ }
+ nounlist[outcnt].vocolobj = MCMONINV;
+ nounlist[outcnt].vocolflg = 0;
+ VOC_RETVAL(ctx, save_sp, outcnt);
+ }
+
+ /* get the list of objects that match the first word */
+ l1 = vocgol(ctx, list1, flags1, cmd, typelist,
+ firstwrd, firstwrd, cur, ofword);
+
+ /*
+ * Allow retrying with a count plus a plural if the first word is a
+ * number, and we have something plural in the list. Only treat "1"
+ * this way if more words follow in the noun phrase.
+ */
+ retry_with_count = ((vocisdigit(*cmd[firstwrd]) && found_plural)
+ || (vocisdigit(*cmd[firstwrd])
+ && cur != firstwrd+1
+ && atoi(cmd[firstwrd]) == 1));
+
+ /* see if we found anything on the first word */
+ if (l1 <= 0)
+ {
+ if (chkact) { VOC_RETVAL(ctx, save_sp, 0); }
+
+ if (vocisdigit(*cmd[firstwrd]))
+ {
+ if (retry_with_count)
+ {
+ /* interpret it as a count plus a plural */
+ trying_count = TRUE;
+
+ /* don't try this again */
+ retry_with_count = FALSE;
+ }
+ else
+ {
+ /* not a plural - take the number as the entire noun phrase */
+ nounlist[outcnt].vocolobj = MCMONINV;
+ nounlist[outcnt].vocolflg = VOCS_NUM;
+ nounlist[outcnt].vocolfst = nounlist[outcnt].vocollst =
+ cmd[firstwrd];
+ *next = firstwrd + 1;
+ ++outcnt;
+ nounlist[outcnt].vocolobj = MCMONINV;
+ nounlist[outcnt].vocolflg = 0;
+ VOC_RETVAL(ctx, save_sp, outcnt);
+ }
+ }
+ else
+ {
+ /*
+ * display a message if we didn't already (if vocgol
+ * returned less than zero, it already displayed its own
+ * error message)
+ */
+ if (l1 == 0)
+ vocerr(ctx, VOCERR(9), "I don't see any %s here.", namebuf);
+
+ /* return failure */
+ VOC_RETVAL(ctx, save_sp, -1);
+ }
+ }
+
+retry_exclude_first:
+ for (i = firstwrd + 1 ; i < cur ; ++i)
+ {
+ int l2;
+
+ if (voc_check_special(ctx, cmd[i], VOCW_OF)
+ || (typelist[i] & VOCT_ARTICLE))
+ continue;
+
+ /* get the list for this new object */
+ l2 = vocgol(ctx, list2, flags2, cmd, typelist,
+ firstwrd, i, cur, ofword);
+
+ /* if that failed and displayed an error, return failure */
+ if (l2 < 0)
+ {
+ /* return failure */
+ VOC_RETVAL(ctx, save_sp, -1);
+ }
+
+ /*
+ * Intersect the last list with the new list. If the previous
+ * list didn't have anything in it, it must mean that the word
+ * list started with a number, in which case we're trying to
+ * interpret this as a count plus a plural. So, don't intersect
+ * the list if there was nothing in the first list.
+ */
+ if (l1 == 0)
+ {
+ /* just copy the new list */
+ l1 = l2;
+ memcpy(list1, list2, (size_t)((l1+1) * sizeof(list1[0])));
+ memcpy(flags1, flags2, (size_t)(l1 * sizeof(flags1[0])));
+ }
+ else
+ {
+ /* intersect the two lists */
+ l1 = vocisect_flags(list1, flags1, list2, flags2);
+ }
+
+ /*
+ * If there's nothing in the list, it means that there's no
+ * object that defines all of these words.
+ */
+ if (l1 == 0)
+ {
+ if (ctx->voccxflg & VOCCXFDBG)
+ tioputs(ctx->vocxtio,
+ "... can't find any objects matching these words\\n");
+ /*
+ * If there's an "of", remove the "of" and everything that
+ * follows, and go back and reprocess the part up to the
+ * "of" -- treat it as a sentence that has two objects, with
+ * "of" as the preposition introducing the indirect object.
+ */
+ if (ofword != -1)
+ {
+ if (ctx->voccxflg & VOCCXFDBG)
+ tioputs(ctx->vocxtio,
+ "... dropping the part after OF and retrying\\n");
+
+ /*
+ * drop the part from 'of' on - scan only from firstwrd
+ * to the word before 'of'
+ */
+ hypothetical_last = cur;
+ trim_flags |= VOCS_TRIMPREP;
+ cur = ofword;
+
+ /* forget that we have an 'of' phrase at all */
+ ofword = -1;
+
+ /* retry with the new, shortened phrase */
+ goto try_again;
+ }
+
+ /*
+ * Try again with the count + plural interpretation, if
+ * possible
+ */
+ if (retry_with_count)
+ {
+ if (ctx->voccxflg & VOCCXFDBG)
+ tioputs(ctx->vocxtio,
+ "... treating the number as a count and retrying\\n");
+
+ /* we've exhausted our retries */
+ retry_with_count = FALSE;
+ trying_count = TRUE;
+
+ /* go try it */
+ goto retry_exclude_first;
+ }
+
+ /*
+ * If one of the words will work as a preposition, and we
+ * took it as an adjective, go back and try the word again
+ * as a preposition.
+ */
+ for (i = cur - 1; i > firstwrd ; --i)
+ {
+ if (typelist[i] & VOCT_PREP)
+ {
+ if (ctx->voccxflg & VOCCXFDBG)
+ tioputs(ctx->vocxtio,
+ "... changing word to prep and retrying\\n");
+
+ hypothetical_last = cur;
+ trim_flags |= VOCS_TRIMPREP;
+ cur = i;
+ goto try_again;
+ }
+ }
+
+ /* if just checking actor, don't display an error */
+ if (chkact) { VOC_RETVAL(ctx, save_sp, 0); }
+
+ /*
+ * tell the player about it unless supressing complaints,
+ * and return an error
+ */
+ if (complain)
+ vocerr(ctx, VOCERR(9), "I don't see any %s here.", namebuf);
+ if (no_match != 0)
+ *no_match = TRUE;
+ VOC_RETVAL(ctx, save_sp, 0);
+ }
+ }
+
+ /*
+ * We have one or more objects, so make a note of how we found
+ * them.
+ */
+ if (ctx->voccxflg & VOCCXFDBG)
+ tioputs(ctx->vocxtio, "... found objects matching vocabulary:\\n");
+
+ /* store the list of objects that match all of our words */
+ for (i = 0 ; i < l1 ; ++i)
+ {
+ if (ctx->voccxflg & VOCCXFDBG)
+ {
+ tioputs(ctx->voccxtio, "..... ");
+ runppr(ctx->voccxrun, list1[i], PRP_SDESC, 0);
+ tioflushn(ctx->voccxtio, 1);
+ }
+
+ nounlist[outcnt].vocolfst = cmd[firstwrd];
+ nounlist[outcnt].vocollst = cmd[cur-1];
+ nounlist[outcnt].vocolflg =
+ flags1[i] | (trying_count ? VOCS_COUNT : 0) | trim_flags;
+ if (trim_flags)
+ nounlist[outcnt].vocolhlst = cmd[hypothetical_last - 1];
+ if (has_any)
+ nounlist[outcnt].vocolflg |= VOCS_ANY;
+ nounlist[outcnt++].vocolobj = list1[i];
+ if (outcnt > VOCMAXAMBIG)
+ {
+ vocerr(ctx, VOCERR(10),
+ "You're referring to too many objects with \"%s\".",
+ namebuf);
+ VOC_RETVAL(ctx, save_sp, -2);
+ }
+ }
+
+ /*
+ * If we have a one-word noun phrase, and the word is a number, add
+ * the number object into the list of objects we're considering,
+ * even though we found an object that matches. We'll probably
+ * easily disambiguate this later, but we need to keep open the
+ * possibility that they're just referring to a number rather than a
+ * numbered adjective for now.
+ */
+ if (firstwrd + 1 == cur && vocisdigit(*cmd[firstwrd]))
+ {
+ /* add just the number as an object */
+ nounlist[outcnt].vocolobj = ctx->voccxnum;
+ nounlist[outcnt].vocolflg = VOCS_NUM;
+ nounlist[outcnt].vocolfst = nounlist[outcnt].vocollst =
+ cmd[firstwrd];
+ ++outcnt;
+ }
+
+ /* terminate the list */
+ nounlist[outcnt].vocolobj = MCMONINV;
+ nounlist[outcnt].vocolflg = 0;
+
+ /* return the number of objects in our match list */
+ VOC_RETVAL(ctx, save_sp, outcnt);
+}
+
+/*
+ * get obj - gets one or more noun lists (a flag, "multi", says whether
+ * we should allow multiple lists). We use vocg1o() to read noun lists
+ * one at a time, and keep going (if "multi" is true) as long as there
+ * are more "and <noun-phrase>" clauses.
+ *
+ * If no_match is not null, we'll set it to true if the syntax was okay
+ * but we didn't find any match for the list of words, false otherwise.
+ */
+int vocgobj(voccxdef *ctx, char *cmd[], int typelist[],
+ int cur, int *next, int complain, vocoldef *nounlist,
+ int multi, int chkact, int *no_match)
+{
+ int cnt;
+ int outcnt = 0;
+ int i;
+ int again = FALSE;
+ int lastcur = 0;
+ vocoldef *tmplist;
+ uchar *save_sp;
+
+ voc_enter(ctx, &save_sp);
+ VOC_MAX_ARRAY(ctx, vocoldef, tmplist);
+
+ for ( ;; )
+ {
+ /* try getting a single object */
+ cnt = vocg1o(ctx, cmd, typelist, cur, next, complain,
+ tmplist, chkact, no_match);
+
+ /* if we encountered a syntax error, return failure */
+ if (cnt < 0)
+ {
+ VOC_RETVAL(ctx, save_sp, cnt);
+ }
+
+ /* if we got any objects, store them in our output list */
+ if (cnt > 0)
+ {
+ for (i = 0 ; i < cnt ; ++i)
+ {
+ OSCPYSTRUCT(nounlist[outcnt], tmplist[i]);
+ if (++outcnt > VOCMAXAMBIG)
+ {
+ vocerr(ctx, VOCERR(11),
+ "You're referring to too many objects.");
+ VOC_RETVAL(ctx, save_sp, -1);
+ }
+ }
+ }
+
+ /* if we didn't find any objects, stop looking */
+ if (cnt == 0)
+ {
+ if (again)
+ *next = lastcur;
+ break;
+ }
+
+ /*
+ * if the caller only wanted a single object (or is getting an
+ * actor, in which case they implicitly want only a single
+ * object), stop looking for additional noun phrases
+ */
+ if (!multi || chkact)
+ break;
+
+ /* skip past the previous noun phrase */
+ cur = *next;
+
+ /*
+ * if we're looking at a noun phrase separator ("and" or a
+ * comma), get the next noun phrase; otherwise, we're done
+ */
+ if (cur != -1 && cmd[cur] != 0 && vocspec(cmd[cur], VOCW_AND))
+ {
+ lastcur = cur;
+ while (cmd[cur] && vocspec(cmd[cur], VOCW_AND)) ++cur;
+ again = TRUE;
+ if (complain) complain = 2;
+ }
+ else
+ {
+ /* end of line, or not at a separator - we're done */
+ break;
+ }
+ }
+
+ /* terminate the list and return the number of objects we found */
+ nounlist[outcnt].vocolobj = MCMONINV;
+ nounlist[outcnt].vocolflg = 0;
+ VOC_RETVAL(ctx, save_sp, outcnt);
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * TADS program code interface - tokenize a string. Returns a list of
+ * strings, with each string giving a token in the command.
+ */
+void voc_parse_tok(voccxdef *ctx)
+{
+ uchar *save_sp;
+ runcxdef *rcx = ctx->voccxrun;
+ char **cmd;
+ char *inbuf;
+ char *outbuf;
+ uchar *p;
+ uint len;
+ int cnt;
+
+ /* enter our stack frame */
+ voc_enter(ctx, &save_sp);
+
+ /* get the string argument */
+ p = runpopstr(rcx);
+
+ /* get and skip the length prefix */
+ len = osrp2(p) - 2;
+ p += 2;
+
+ /*
+ * Allocate space for the original string, and space for the token
+ * pointers and the tokenized string buffer. We could potentially
+ * have one token per character in the original string, and we could
+ * potentially need one extra null terminator for each character in
+ * the original string; allocate accordingly.
+ */
+ VOC_STK_ARRAY(ctx, char, inbuf, len + 1);
+ VOC_STK_ARRAY(ctx, char, outbuf, len*2);
+ VOC_STK_ARRAY(ctx, char *, cmd, len*2);
+
+ /* copy the string into our buffer, and null-terminate it */
+ memcpy(inbuf, p, len);
+ inbuf[len] = '\0';
+
+ /* tokenize the string */
+ cnt = voctok(ctx, inbuf, outbuf, cmd, TRUE, FALSE, FALSE);
+
+ /* check the result */
+ if (cnt < 0)
+ {
+ /* negative count - it's an error, so return nil */
+ runpnil(rcx);
+ }
+ else
+ {
+ /* push the return list */
+ voc_push_toklist(ctx, cmd, cnt);
+ }
+
+ /* leave our stack frame */
+ voc_leave(ctx, save_sp);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * TADS program code interface - get the list of types for a list words.
+ * The words are simply strings of the type returned from the tokenizer.
+ * The return value is a list of types, with each entry in the return
+ * list giving the types of the corresponding entry in the word list.
+ */
+void voc_parse_types(voccxdef *ctx)
+{
+ runcxdef *rcx = ctx->voccxrun;
+ uchar *wrdp;
+ uint wrdsiz;
+ uchar *typp;
+ uchar *lstp;
+ uint lstsiz;
+ int wrdcnt;
+
+ /* get the list of words from the stack */
+ wrdp = runpoplst(rcx);
+
+ /* read and skip the size prefix */
+ wrdsiz = osrp2(wrdp) - 2;
+ wrdp += 2;
+
+ /* scan the list and count the number of string entries */
+ for (wrdcnt = 0, lstp = wrdp, lstsiz = wrdsiz ; lstsiz != 0 ;
+ lstadv(&lstp, &lstsiz))
+ {
+ /* if this is a string, count it */
+ if (*lstp == DAT_SSTRING)
+ ++wrdcnt;
+ }
+
+ /* allocate the return list - one number entry per word */
+ typp = voc_push_list(ctx, wrdcnt, 4);
+
+ /* look up each word and set the corresponding element in the type list */
+ for (lstp = wrdp, lstsiz = wrdsiz ; lstsiz != 0 ; lstadv(&lstp, &lstsiz))
+ {
+ /* if this is a string, look it up in the dictionary */
+ if (*lstp == DAT_SSTRING)
+ {
+ char buf[256];
+ int curtyp;
+ uint len;
+
+ /* make sure it fits in our buffer */
+ len = osrp2(lstp+1) - 2;
+ if (len < sizeof(buf))
+ {
+ /* extract the word into our buffer */
+ memcpy(buf, lstp+3, len);
+
+ /* null-terminate it */
+ buf[len] = '\0';
+
+ /* get the type */
+ curtyp = voc_lookup_type(ctx, buf, len, TRUE);
+
+ /* if they didn't find a type at all, set it to UNKNOWN */
+ if (curtyp == 0)
+ curtyp = VOCT_UNKNOWN;
+ }
+ else
+ {
+ /* the string is too big - just mark it as unknown */
+ curtyp = VOCT_UNKNOWN;
+ }
+
+ /* add this type to the return list */
+ *typp++ = DAT_NUMBER;
+ oswp4s(typp, curtyp);
+ typp += 4;
+ }
+ }
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Parse a noun phrase from TADS program code. Takes a list of words
+ * and a list of types from the stack, uses the standard noun phrase
+ * parser, and returns a list of matching objects. The object list is
+ * not disambiguated, but merely reflects all matching objects. The
+ * entire standard parsing algorithm applies, including parseNounPhrase
+ * invocation if appropriate.
+ */
+void voc_parse_np(voccxdef *ctx)
+{
+ runcxdef *rcx = ctx->voccxrun;
+ vocoldef *objlist;
+ uchar *save_sp;
+ uchar *wordp;
+ uint wordsiz;
+ uchar *typp;
+ uint typsiz;
+ int cnt;
+ char **wordarr;
+ int wordcnt;
+ char *wordchararr;
+ uint wordcharsiz;
+ int *typarr;
+ int complain;
+ int chkact;
+ int multi;
+ int no_match;
+ int next;
+ int cur;
+ uchar *lstp;
+ uint lstsiz;
+ char *p;
+ int i;
+ int old_unknown, old_lastunk;
+
+ /* allocate stack arrays */
+ voc_enter(ctx, &save_sp);
+ VOC_MAX_ARRAY(ctx, vocoldef, objlist);
+
+ /*
+ * Save the original context unknown values, since we don't want to
+ * affect the context information in this game-initiated call, then
+ * clear the unknown word count for the duration of the call.
+ */
+ old_unknown = ctx->voccxunknown;
+ old_lastunk = ctx->voccxlastunk;
+ ctx->voccxunknown = ctx->voccxlastunk = 0;
+
+ /* get the list of words, and read its length prefix */
+ wordp = runpoplst(rcx);
+ wordsiz = osrp2(wordp) - 2;
+ wordp += 2;
+
+ /* get the list of types, and read its length prefix */
+ typp = runpoplst(rcx);
+ typsiz = osrp2(typp) - 2;
+ typp += 2;
+
+ /* get the starting index (adjusting for zero-based indexing) */
+ cur = runpopnum(rcx) - 1;
+ next = cur;
+
+ /* get the flag arguments */
+ complain = runpoplog(rcx);
+ multi = runpoplog(rcx);
+ chkact = runpoplog(rcx);
+
+ /* count the words in the word list */
+ for (wordcnt = 0, lstp = wordp, wordcharsiz = 0, lstsiz = wordsiz ;
+ lstsiz != 0 ; lstadv(&lstp, &lstsiz))
+ {
+ /* count the word */
+ ++wordcnt;
+
+ /*
+ * count the space needed for the word - count the bytes of the
+ * string plus a null terminator
+ */
+ if (*lstp == DAT_SSTRING)
+ wordcharsiz += osrp2(lstp+1) + 1;
+ }
+
+ /* allocate space for the word arrays */
+ VOC_STK_ARRAY(ctx, char, wordchararr, wordcharsiz);
+ VOC_STK_ARRAY(ctx, char *, wordarr, wordcnt+1);
+ VOC_STK_ARRAY(ctx, int, typarr, wordcnt+1);
+
+ /* build the word array */
+ for (i = 0, p = wordchararr, lstp = wordp, lstsiz = wordsiz ;
+ lstsiz != 0 ; lstadv(&lstp, &lstsiz))
+ {
+ /* add the word to the word array */
+ if (*lstp == DAT_SSTRING)
+ {
+ uint len;
+
+ /* add this entry to the word array */
+ wordarr[i] = p;
+
+ /* copy the word to the character array and null-terminate it */
+ len = osrp2(lstp + 1) - 2;
+ memcpy(p, lstp + 3, len);
+ p[len] = '\0';
+
+ /* move the write pointer past this entry */
+ p += len + 1;
+
+ /* bump the index */
+ ++i;
+ }
+ }
+
+ /* store an empty last entry */
+ wordarr[i] = 0;
+
+ /* build the type array */
+ for (i = 0, lstp = typp, lstsiz = typsiz ;
+ lstsiz != 0 && i < wordcnt ; lstadv(&lstp, &lstsiz))
+ {
+ /* add this entry to the type array */
+ if (*lstp == DAT_NUMBER)
+ {
+ /* set the entry */
+ typarr[i] = (int)osrp4(lstp + 1);
+
+ /* bump the index */
+ ++i;
+ }
+ }
+
+ /* parse the noun phrase */
+ cnt = vocgobj(ctx, wordarr, typarr, cur, &next, complain, objlist,
+ multi, chkact, &no_match);
+
+ /* restore context unknown values */
+ ctx->voccxunknown = old_unknown;
+ ctx->voccxlastunk = old_lastunk;
+
+ /* check the return value */
+ if (cnt < 0)
+ {
+ /* syntax error; return nil to indicate an error */
+ runpnil(rcx);
+ }
+ else if (cnt == 0)
+ {
+ /*
+ * No objects found. Return a list consisting only of the next
+ * index. If the next index is equal to the starting index,
+ * this will tell the caller that no noun phrase is
+ * syntactically present; otherwise, it will tell the caller
+ * that a noun phrase is present but there are no matching
+ * objects.
+ *
+ * Note that we must increment the returned element index to
+ * conform with the 1-based index values that the game function
+ * uses.
+ */
+ ++next;
+ voc_push_numlist(ctx, (uint *)&next, 1);
+ }
+ else
+ {
+ /*
+ * We found one or more objects. Return a list whose first
+ * element is the index of the next word to be parsed, and whose
+ * remaining elements are sublists. Each sublist contains a
+ * match for one noun phrase; for each AND adding another noun
+ * phrase, there's another sublist. Each sublist contains the
+ * index of the first word of its noun phrase, the index of the
+ * last word of its noun phrase, and then the objects. For each
+ * object, there is a pair of entries: the object itself, and
+ * the flags for the object.
+ *
+ * First, figure out how much space we need by scanning the
+ * return list.
+ */
+ for (lstsiz = 0, i = 0 ; i < cnt ; )
+ {
+ int j;
+
+ /*
+ * count the entries in this sublist by looking for the next
+ * entry whose starting word is different
+ */
+ for (j = i ;
+ j < cnt && objlist[j].vocolfst == objlist[i].vocolfst ;
+ ++j)
+ {
+ /*
+ * for this entry, we need space for the object (1 + 2
+ * for an object, or just 1 for nil) and flags (1 + 4)
+ */
+ if (objlist[j].vocolobj == MCMONINV)
+ lstsiz += 1;
+ else
+ lstsiz += 3;
+ lstsiz += 5;
+ }
+
+ /*
+ * For this sublist, we need space for the first index (type
+ * prefix + number = 1 + 4 = 5) and the last index (5).
+ * We've already counted space for the objects in the list.
+ * Finally, we need space for the list type and length
+ * prefixes (1 + 2) for the sublist itself.
+ */
+ lstsiz += (5 + 5) + 3;
+
+ /* skip to the next element */
+ i = j;
+ }
+
+ /*
+ * finally, we need space for the first element of the list,
+ * which is the index of the next word to be parsed (1+4)
+ */
+ lstsiz += 5;
+
+ /* allocate space for the list */
+ lstp = voc_push_list_siz(ctx, lstsiz);
+
+ /*
+ * store the first element - the index of the next word to parse
+ * (adjusting to 1-based indexing)
+ */
+ *lstp++ = DAT_NUMBER;
+ oswp4s(lstp, next + 1);
+ lstp += 4;
+
+ /* build the list */
+ for (i = 0 ; i < cnt ; )
+ {
+ uchar *sublstp;
+ int j;
+ int firstidx = 0;
+ int lastidx = 0;
+
+ /* store the list type prefix */
+ *lstp++ = DAT_LIST;
+
+ /*
+ * remember where the length prefix is, then skip it - we'll
+ * come back and fill it in when we're done with the sublist
+ */
+ sublstp = lstp;
+ lstp += 2;
+
+ /* search the array to find the indices of the boundary words */
+ for (j = 0 ; j < wordcnt ; ++j)
+ {
+ /* if this is the first word, remember the index */
+ if (wordarr[j] == objlist[i].vocolfst)
+ firstidx = j;
+
+ /* if this is the last word, remember the index */
+ if (wordarr[j] == objlist[i].vocollst)
+ {
+ /* remember the index */
+ lastidx = j;
+
+ /*
+ * we can stop looking now, since the words are
+ * always in order (so the first index will never be
+ * after the last index)
+ */
+ break;
+ }
+ }
+
+ /* add the first and last index, adjusting to 1-based indexing */
+ *lstp++ = DAT_NUMBER;
+ oswp4s(lstp, firstidx + 1);
+ lstp += 4;
+ *lstp++ = DAT_NUMBER;
+ oswp4s(lstp, lastidx + 1);
+ lstp += 4;
+
+ /* add the objects */
+ for (j = i ;
+ j < cnt && objlist[j].vocolfst == objlist[i].vocolfst ;
+ ++j)
+ {
+ /* add this object */
+ if (objlist[j].vocolobj == MCMONINV)
+ {
+ /* just store a nil */
+ *lstp++ = DAT_NIL;
+ }
+ else
+ {
+ /* store the object */
+ *lstp++ = DAT_OBJECT;
+ oswp2(lstp, objlist[j].vocolobj);
+ lstp += 2;
+ }
+
+ /* add the flags */
+ *lstp++ = DAT_NUMBER;
+ oswp4s(lstp, objlist[i].vocolflg);
+ lstp += 4;
+ }
+
+ /* fix up the sublist's length prefix */
+ oswp2(sublstp, lstp - sublstp);
+
+ /* move on to the next sublist */
+ i = j;
+ }
+ }
+
+ /* exit the stack frame */
+ voc_leave(ctx, save_sp);
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * TADS program code interface - given a list of words and a list of
+ * their types, produce a list of objects that match all of the words.
+ */
+void voc_parse_dict_lookup(voccxdef *ctx)
+{
+ uchar *save_sp;
+ runcxdef *rcx = ctx->voccxrun;
+ uchar *wrdp;
+ uint wrdsiz;
+ uchar *typp;
+ uint typsiz;
+ objnum *list1;
+ objnum *list2;
+ int cnt1;
+ int cnt2;
+
+ /* enter our stack frame and allocate stack arrays */
+ voc_enter(ctx, &save_sp);
+ VOC_MAX_ARRAY(ctx, objnum, list1);
+ VOC_MAX_ARRAY(ctx, objnum, list2);
+
+ /* get the word list, and read and skip its size prefix */
+ wrdp = runpoplst(rcx);
+ wrdsiz = osrp2(wrdp) - 2;
+ wrdp += 2;
+
+ /* get the type list, and read and skip its size prefix */
+ typp = runpoplst(rcx);
+ typsiz = osrp2(typp) - 2;
+ typp += 2;
+
+ /* nothing in the main list yet */
+ cnt1 = 0;
+
+ /* go through the word list */
+ while (wrdsiz > 0)
+ {
+ int curtyp;
+ int type_prop;
+ char *curword;
+ uint curwordlen;
+ char *curword2;
+ uint curwordlen2;
+ vocwdef *v;
+ char *p;
+ uint len;
+ vocseadef search_ctx;
+
+ /* if this entry is a string, consider it */
+ if (*wrdp == DAT_SSTRING)
+ {
+ /* get the current word's text string */
+ curword = (char *)(wrdp + 3);
+ curwordlen = osrp2(wrdp+1) - 2;
+
+ /* check for an embedded space */
+ for (p = curword, len = curwordlen ; len != 0 && !t_isspace(*p) ;
+ ++p, --len) ;
+ if (len != 0)
+ {
+ /* get the second word */
+ curword2 = p + 1;
+ curwordlen2 = len - 1;
+
+ /* truncate the first word accordingly */
+ curwordlen -= len;
+ }
+ else
+ {
+ /* no embedded space -> no second word */
+ curword2 = 0;
+ curwordlen2 = 0;
+ }
+
+ /* presume we won't find a valid type property */
+ type_prop = PRP_INVALID;
+
+ /*
+ * get this type entry, if there's another entry in the
+ * list, and it's of the appropriate type
+ */
+ if (typsiz > 0 && *typp == DAT_NUMBER)
+ {
+ /*
+ * Figure out what type property we'll be using. We'll
+ * consider only one meaning for each word, and we'll
+ * arbitrarily pick one if the type code has more than
+ * one type, because we expect the caller to provide
+ * exactly one type per word.
+ */
+ int i;
+ struct typemap_t
+ {
+ int flag;
+ int prop;
+ };
+ static struct typemap_t typemap[] =
+ {
+ { VOCT_ARTICLE, PRP_ARTICLE },
+ { VOCT_ADJ, PRP_ADJ },
+ { VOCT_NOUN, PRP_NOUN },
+ { VOCT_PREP, PRP_PREP },
+ { VOCT_VERB, PRP_VERB },
+ { VOCT_PLURAL, PRP_PLURAL }
+ };
+ struct typemap_t *mapp;
+
+ /* get the type */
+ curtyp = (int)osrp4(typp+1);
+
+ /* search for a type */
+ for (mapp = typemap, i = sizeof(typemap)/sizeof(typemap[0]) ;
+ i != 0 ; ++mapp, --i)
+ {
+ /* if this flag is set, use this type property */
+ if ((curtyp & mapp->flag) != 0)
+ {
+ /* use this one */
+ type_prop = mapp->prop;
+ break;
+ }
+ }
+ }
+
+ /* nothing in the new list yet */
+ cnt2 = 0;
+
+ /* scan for matching words */
+ for (v = vocffw(ctx, curword, curwordlen, curword2, curwordlen2,
+ type_prop, &search_ctx) ;
+ v != 0 ;
+ v = vocfnw(ctx, &search_ctx))
+ {
+ int i;
+
+ /* make sure we have room in our list */
+ if (cnt2 >= VOCMAXAMBIG - 1)
+ break;
+
+ /* make sure that this entry isn't already in our list */
+ for (i = 0 ; i < cnt2 ; ++i)
+ {
+ /* if this entry matches, stop looking */
+ if (list2[i] == v->vocwobj)
+ break;
+ }
+
+ /* if it's not already in the list, add it now */
+ if (i == cnt2)
+ {
+ /* add it to our list */
+ list2[cnt2++] = v->vocwobj;
+ }
+ }
+
+ /* terminate the list */
+ list2[cnt2] = MCMONINV;
+
+ /*
+ * if there's nothing in the first list, simply copy this
+ * into the first list; otherwise, intersect the two lists
+ */
+ if (cnt1 == 0)
+ {
+ /* this is the first list -> copy it into the main list */
+ memcpy(list1, list2, (cnt2+1)*sizeof(list2[0]));
+ cnt1 = cnt2;
+ }
+ else
+ {
+ /* intersect the two lists */
+ cnt1 = vocisect(list1, list2);
+ }
+
+ /*
+ * if there's nothing in the result list now, there's no
+ * need to look any further, because further intersection
+ * will yield nothing
+ */
+ if (cnt1 == 0)
+ break;
+ }
+
+ /* advance the word list */
+ lstadv(&wrdp, &wrdsiz);
+
+ /* if there's anything left in the type list, advance it as well */
+ if (typsiz > 0)
+ lstadv(&typp, &typsiz);
+ }
+
+ /* push the result list */
+ voc_push_objlist(ctx, list1, cnt1);
+
+ /* exit our stack frame */
+ voc_leave(ctx, save_sp);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * TADS program code interface - disambiguate a noun list. We take the
+ * kind of complex object list returned by voc_parse_np(), and produce a
+ * fully-resolved list of objects.
+ */
+void voc_parse_disambig(voccxdef *ctx)
+{
+ voccxdef ctx_copy;
+ uchar *save_sp;
+ runcxdef *rcx = ctx->voccxrun;
+ vocoldef *inlist;
+ vocoldef *outlist;
+ int objcnt;
+ char **cmd;
+ char *cmdbuf;
+ char *oopsbuf;
+ objnum actor;
+ objnum verb;
+ objnum prep;
+ objnum otherobj;
+ prpnum defprop;
+ prpnum accprop;
+ prpnum verprop;
+ uchar *tokp;
+ uint toksiz;
+ uchar *objp;
+ uint objsiz;
+ uchar *lstp;
+ uint lstsiz;
+ int tokcnt;
+ char *p;
+ int i;
+ int silent;
+ int err;
+ int unknown_count;
+
+ /* allocate our stack arrays */
+ voc_enter(ctx, &save_sp);
+ VOC_MAX_ARRAY(ctx, vocoldef, outlist);
+ VOC_STK_ARRAY(ctx, char, cmdbuf, VOCBUFSIZ);
+ VOC_STK_ARRAY(ctx, char, oopsbuf, VOCBUFSIZ);
+
+ /* get the actor, verb, prep, and otherobj arguments */
+ actor = runpopobj(rcx);
+ verb = runpopobj(rcx);
+ prep = runpopobjnil(rcx);
+ otherobj = runpopobjnil(rcx);
+
+ /*
+ * get the usage parameter, and use it to select the appropriate
+ * defprop and accprop
+ */
+ switch(runpopnum(rcx))
+ {
+ case VOC_PRO_RESOLVE_DOBJ:
+ default:
+ defprop = PRP_DODEFAULT;
+ accprop = PRP_VALIDDO;
+ break;
+
+ case VOC_PRO_RESOLVE_IOBJ:
+ defprop = PRP_IODEFAULT;
+ accprop = PRP_VALIDIO;
+ break;
+
+ case VOC_PRO_RESOLVE_ACTOR:
+ defprop = PRP_DODEFAULT;
+ accprop = PRP_VALIDACTOR;
+ break;
+ }
+
+ /* get the verprop argument */
+ verprop = runpopprp(rcx);
+
+ /* pop the token list, and read and skip the length prefix */
+ tokp = runpoplst(rcx);
+ toksiz = osrp2(tokp) - 2;
+ tokp += 2;
+
+ /* pop the object list, and read and skip the length prefix */
+ objp = runpoplst(rcx);
+ objsiz = osrp2(objp) - 2;
+ objp += 2;
+
+ /* pop the "silent" flag */
+ silent = runpoplog(rcx);
+
+ /* count the tokens */
+ for (lstp = tokp, lstsiz = toksiz, tokcnt = 0 ;
+ lstsiz != 0 ; lstadv(&lstp, &lstsiz))
+ {
+ /* if this is a string, count it */
+ if (*lstp == DAT_SSTRING)
+ ++tokcnt;
+ }
+
+ /* allocate stack space for the command pointers and buffer */
+ VOC_STK_ARRAY(ctx, char *, cmd, tokcnt + 1);
+
+ /* extract the tokens into our pointer buffer */
+ for (lstp = tokp, lstsiz = toksiz, i = 0, p = cmdbuf ;
+ lstsiz != 0 ; lstadv(&lstp, &lstsiz))
+ {
+ /* if this is a string, count and size it */
+ if (*lstp == DAT_SSTRING)
+ {
+ uint len;
+
+ /* store a pointer to the word in the command buffer */
+ cmd[i++] = p;
+
+ /* copy the token into the command buffer and null-terminate it */
+ len = osrp2(lstp + 1) - 2;
+ memcpy(p, lstp + 3, len);
+ p[len] = '\0';
+
+ /* move the buffer pointer past this word */
+ p += len + 1;
+ }
+ }
+
+ /* store a null at the end of the command pointer list */
+ cmd[i] = 0;
+
+ /*
+ * The object list is provided in the same format as the list
+ * returned by voc_parse_np(), but the leading index number is
+ * optional. We don't need the leading index for anything, so if
+ * it's there, simply skip it so that we can start with the first
+ * sublist.
+ */
+ if (objsiz > 0 && *objp == DAT_NUMBER)
+ lstadv(&objp, &objsiz);
+
+ /*
+ * Count the objects in the object list, so that we can figure out
+ * how much we need to allocate for the input object list.
+ */
+ for (lstp = objp, lstsiz = objsiz, objcnt = 0 ; lstsiz != 0 ;
+ lstadv(&lstp, &lstsiz))
+ {
+ /* if this is a sublist, parse it */
+ if (*lstp == DAT_LIST)
+ {
+ uchar *subp;
+ uint subsiz;
+
+ /* get the sublist pointer, and read and skip the size prefix */
+ subp = lstp + 1;
+ subsiz = osrp2(subp) - 2;
+ subp += 2;
+
+ /* scan the sublist */
+ while (subsiz > 0)
+ {
+ /* if this is an object, count it */
+ if (*subp == DAT_OBJECT || *subp == DAT_NIL)
+ ++objcnt;
+
+ /* skip this element */
+ lstadv(&subp, &subsiz);
+ }
+ }
+ }
+
+ /* allocate space for the input list */
+ VOC_STK_ARRAY(ctx, vocoldef, inlist, objcnt + 1);
+
+ /* we don't have any unknown words yet */
+ unknown_count = 0;
+
+ /* parse the list, filling in the input array */
+ for (lstp = objp, lstsiz = objsiz, i = 0 ; lstsiz != 0 ;
+ lstadv(&lstp, &lstsiz))
+ {
+ /* if this is a sublist, parse it */
+ if (*lstp == DAT_LIST)
+ {
+ uchar *subp;
+ uint subsiz;
+ int firstwrd, lastwrd;
+
+ /* get the sublist pointer, and read and skip the size prefix */
+ subp = lstp + 1;
+ subsiz = osrp2(subp) - 2;
+ subp += 2;
+
+ /* in case we don't find token indices, clear them */
+ firstwrd = 0;
+ lastwrd = 0;
+
+ /*
+ * the first two elements of the list are the token indices
+ * of the first and last words of this object's noun phrase
+ */
+ if (subsiz > 0 && *subp == DAT_NUMBER)
+ {
+ /* read the first index, adjusting to zero-based indexing */
+ firstwrd = (int)osrp4(subp+1) - 1;
+
+ /* make sure it's in range */
+ if (firstwrd < 0)
+ firstwrd = 0;
+ else if (firstwrd > tokcnt)
+ firstwrd = tokcnt;
+
+ /* skip it */
+ lstadv(&subp, &subsiz);
+ }
+ if (subsiz > 0 && *subp == DAT_NUMBER)
+ {
+ /* read the last index, adjusting to zero-based indexing */
+ lastwrd = (int)osrp4(subp+1) - 1;
+
+ /* make sure it's in range */
+ if (lastwrd < firstwrd)
+ lastwrd = firstwrd;
+ else if (lastwrd > tokcnt)
+ lastwrd = tokcnt;
+
+ /* skip it */
+ lstadv(&subp, &subsiz);
+ }
+
+ /* scan the sublist */
+ while (subsiz > 0)
+ {
+ /* if this is an object, store it */
+ if (*subp == DAT_OBJECT || *subp == DAT_NIL)
+ {
+ /* store the object */
+ if (*subp == DAT_OBJECT)
+ inlist[i].vocolobj = osrp2(subp+1);
+ else
+ inlist[i].vocolobj = MCMONINV;
+
+ /* set the first and last word pointers */
+ inlist[i].vocolfst = cmd[firstwrd];
+ inlist[i].vocollst = cmd[lastwrd];
+
+ /* skip the object */
+ lstadv(&subp, &subsiz);
+
+ /* check for flags */
+ if (subsiz > 0 && *subp == DAT_NUMBER)
+ {
+ /* get the flags value */
+ inlist[i].vocolflg = (int)osrp4(subp+1);
+
+ /* skip the number in the list */
+ lstadv(&subp, &subsiz);
+ }
+ else
+ {
+ /* clear the flags */
+ inlist[i].vocolflg = 0;
+ }
+
+ /* if an unknown word was involved, note it */
+ if ((inlist[i].vocolflg & VOCS_UNKNOWN) != 0)
+ ++unknown_count;
+
+ /* consume the element */
+ ++i;
+ }
+ else
+ {
+ /* skip this element */
+ lstadv(&subp, &subsiz);
+ }
+ }
+ }
+ }
+
+ /* terminate the list */
+ inlist[i].vocolobj = MCMONINV;
+ inlist[i].vocolflg = 0;
+
+ /*
+ * make a copy of our context, so the disambiguation can't make any
+ * global changes
+ */
+ memcpy(&ctx_copy, ctx, sizeof(ctx_copy));
+
+ /*
+ * Count the unknown words and set the count in the context. This
+ * will allow us to determine after we call the resolver whether the
+ * resolution process cleared up the unknown words (via
+ * parseUnknownDobj/Iobj).
+ */
+ ctx_copy.voccxunknown = ctx_copy.voccxlastunk = unknown_count;
+
+ /* disambiguate the noun list */
+ err = vocdisambig(&ctx_copy, outlist, inlist,
+ defprop, accprop, verprop, cmd,
+ otherobj, actor, verb, prep, cmdbuf, silent);
+
+ /*
+ * If the error was VOCERR(2) - unknown word - check the input list
+ * to see if it contained any unknown words. If it does, and we're
+ * not in "silent" mode, flag the error and then give the user a
+ * chance to use "oops" to correct the problem. If we're in silent
+ * mode, don't display an error and don't allow interactive
+ * correction via "oops."
+ *
+ * It is possible that the unknown word is not in the input list,
+ * but in the user's response to an interactive disambiguation
+ * query. This is why we must check to see if the unknown word is
+ * in the original input list or not.
+ */
+ if (err == VOCERR(2) && ctx_copy.voccxunknown != 0 && !silent)
+ {
+ char *unk;
+ int unk_idx = 0;
+ char *rpl_text;
+
+ /*
+ * forget we have unknown words, since we're going to handle
+ * them now
+ */
+ ctx_copy.voccxunknown = 0;
+
+ /*
+ * find the unknown word - look up each word until we find one
+ * that's not in the dictionary
+ */
+ for (i = 0, unk = 0 ; cmd[i] != 0 ; ++i)
+ {
+ int t;
+
+ /*
+ * get this word's type - if the word has no type, it's an
+ * unknown word
+ */
+ t = voc_lookup_type(ctx, cmd[i], strlen(cmd[i]), TRUE);
+ if (t == 0)
+ {
+ /* this is it - note it and stop searching */
+ unk_idx = i;
+ unk = cmd[i];
+ break;
+ }
+ }
+
+ /*
+ * if we didn't find any unknown words, assume the first word
+ * was unknown
+ */
+ if (unk == 0)
+ {
+ unk_idx = 0;
+ unk = cmd[0];
+ }
+
+ /* display an error, and read a new command */
+ rpl_text = voc_read_oops(&ctx_copy, oopsbuf, VOCBUFSIZ, unk);
+
+ /*
+ * if they didn't respond with "oops," treat the response as a
+ * brand new command to replace the current command
+ */
+ if (rpl_text == 0)
+ {
+ /*
+ * it's a replacement command - set the redo flag to
+ * indicate that we should process the replacement command
+ */
+ ctx_copy.voccxredo = TRUE;
+
+ /* copy the response into the command buffer */
+ strcpy(cmdbuf, oopsbuf);
+ }
+ else
+ {
+ /* indicate the correction via the result code */
+ err = VOCERR(45);
+
+ /*
+ * Build the new command string. The new command string
+ * consists of all of the tokens up to the unknown token,
+ * then the replacement text, then all of the remaining
+ * tokens.
+ */
+ for (p = cmdbuf, i = 0 ; cmd[i] != 0 ; ++i)
+ {
+ size_t needed;
+
+ /* figure the size needs for this token */
+ if (i == unk_idx)
+ {
+ /* we need to insert the replacement text */
+ needed = strlen(rpl_text);
+ }
+ else
+ {
+ /* we need to insert this token string */
+ needed = strlen(cmd[i]);
+ }
+
+ /*
+ * if more tokens follow, we need a space after the
+ * replacement text to separate it from what follows
+ */
+ if (cmd[i+1] != 0 && needed != 0)
+ needed += 1;
+
+ /* leave room for null termination */
+ needed += 1;
+
+ /* if we don't have room for this token, stop now */
+ if (needed > (size_t)(VOCBUFSIZ - (p - cmdbuf)))
+ break;
+
+ /*
+ * if we've reached the unknown token, insert the
+ * replacement text; otherwise, insert this token
+ */
+ if (i == unk_idx)
+ {
+ /* insert the replacement text */
+ strcpy(p, rpl_text);
+ }
+ else if (*cmd[i] == '"')
+ {
+ char *p1;
+ char qu;
+
+ /*
+ * Scan the quoted string for embedded double quotes
+ * - if it has any, use single quotes as the
+ * delimiter; otherwise, use double quotes as the
+ * delimiter. Note that we ignore the first and
+ * last characters in the string, since these are
+ * always the delimiting double quotes in the
+ * original token text.
+ */
+ for (qu = '"', p1 = cmd[i] + 1 ;
+ *p1 != '\0' && *(p1 + 1) != '\0' ; ++p1)
+ {
+ /* check for an embedded double quote */
+ if (*p1 == '"')
+ {
+ /* switch to single quotes as delimiters */
+ qu = '\'';
+
+ /* no need to look any further */
+ break;
+ }
+ }
+
+ /* add the open quote */
+ *p++ = qu;
+
+ /*
+ * add the text, leaving out the first and last
+ * characters (which are the original quotes)
+ */
+ if (strlen(cmd[i]) > 2)
+ {
+ memcpy(p, cmd[i] + 1, strlen(cmd[i]) - 2);
+ p += strlen(cmd[i]) - 2;
+ }
+
+ /* add the closing quote */
+ *p++ = qu;
+
+ /* null-terminate here so we don't skip any further */
+ *p = '\0';
+ }
+ else
+ {
+ /* copy this word */
+ strcpy(p, cmd[i]);
+ }
+
+ /* move past this token */
+ p += strlen(p);
+
+ /* add a space if another token follows */
+ if (cmd[i+1] != 0)
+ *p++ = ' ';
+ }
+
+ /* null-terminate the replacement text */
+ *p = '\0';
+ }
+ }
+
+ /*
+ * Count the objects. An object list is returned only on success or
+ * VOCERR(44), which indicates that the list is still ambiguous.
+ */
+ if (err == 0 || err == VOCERR(44))
+ {
+ /* count the objects in the list */
+ for (i = 0 ; outlist[i].vocolobj != MCMONINV ; ++i) ;
+ objcnt = i;
+ }
+ else
+ {
+ /* there's nothing in the list */
+ objcnt = 0;
+ }
+
+ /* figure out how much space we need for the objects */
+ lstsiz = (1+2) * objcnt;
+
+ /* add space for the first element, which contains the status code */
+ lstsiz += (1 + 4);
+
+ /* if there's a new command string, we'll store it, so make room */
+ if (ctx_copy.voccxredo || err == VOCERR(45))
+ {
+ /*
+ * add space for the type prefix (1), length prefix (2), and the
+ * string bytes (with no null terminator, of course)
+ */
+ lstsiz += (1 + 2 + strlen(cmdbuf));
+
+ /*
+ * if we're retrying due to the redo flag, always return the
+ * RETRY error code, regardless of what caused us to retry the
+ * command
+ */
+ if (ctx_copy.voccxredo)
+ err = VOCERR(43);
+ }
+
+ /* push a list with space for the objects */
+ lstp = voc_push_list_siz(ctx, lstsiz);
+
+ /* store the status code in the first element */
+ *lstp++ = DAT_NUMBER;
+ oswp4s(lstp, err);
+ lstp += 4;
+
+ /* store the remainder of the list */
+ if (err == 0 || err == VOCERR(44))
+ {
+ /* fill in the list with the objects */
+ for (i = 0 ; i < objcnt ; ++i)
+ {
+ /* set this element */
+ *lstp++ = DAT_OBJECT;
+ oswp2(lstp, outlist[i].vocolobj);
+ lstp += 2;
+ }
+ }
+ else if (ctx_copy.voccxredo || err == VOCERR(45))
+ {
+ uint len;
+
+ /* there's a new command - return it as the second element */
+ *lstp++ = DAT_SSTRING;
+
+ /* store the length */
+ len = strlen(cmdbuf);
+ oswp2(lstp, len + 2);
+ lstp += 2;
+
+ /* store the string */
+ memcpy(lstp, cmdbuf, len);
+ }
+
+ /* leave the stack frame */
+ voc_leave(ctx, save_sp);
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * TADS program code interface - replace the current command line with a
+ * new string, aborting the current command.
+ */
+void voc_parse_replace_cmd(voccxdef *ctx)
+{
+ runcxdef *rcx = ctx->voccxrun;
+ uchar *p;
+ uint len;
+
+ /* get the string */
+ p = runpopstr(rcx);
+
+ /* read and skip the length prefix */
+ len = osrp2(p) - 2;
+ p += 2;
+
+ /* make sure it fits in the redo buffer - truncate it if necessary */
+ if (len + 1 > VOCBUFSIZ)
+ len = VOCBUFSIZ - 1;
+
+ /* copy the string and null-terminate it */
+ memcpy(ctx->voccxredobuf, p, len);
+ ctx->voccxredobuf[len] = '\0';
+
+ /* set the "redo" flag so that we execute what's in the buffer */
+ ctx->voccxredo = TRUE;
+
+ /* abort the current command so that we start anew with the replacement */
+ errsig(ctx->voccxerr, ERR_RUNABRT);
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * This routine gets an actor, which is just a single object reference at
+ * the beginning of a sentence. We return 0 if we fail to find an actor;
+ * since this can be either a harmless or troublesome condition, we must
+ * return additional information. The method used to return back ERROR/OK
+ * is to set *next != cur if there is an error, *next == cur if not. So,
+ * getting back (objdef*)0 means that you should check *next. If the return
+ * value is nonzero, then that object is the actor.
+ */
+static objnum vocgetactor(voccxdef *ctx, char *cmd[], int typelist[],
+ int cur, int *next, char *cmdbuf)
+{
+ int l;
+ vocoldef *nounlist;
+ vocoldef *actlist;
+ int cnt;
+ uchar *save_sp;
+ prpnum valprop, verprop;
+
+ voc_enter(ctx, &save_sp);
+ VOC_MAX_ARRAY(ctx, vocoldef, nounlist);
+ VOC_MAX_ARRAY(ctx, vocoldef, actlist);
+
+ *next = cur; /* assume no error will occur */
+ cnt = vocchknoun(ctx, cmd, typelist, cur, next, nounlist, TRUE);
+ if (cnt > 0 && *next != -1 && cmd[*next] && vocspec(cmd[*next], VOCW_AND))
+ {
+ int have_unknown;
+
+ /* make a note as to whether the list contains an unknown word */
+ have_unknown = ((nounlist[0].vocolflg & VOCS_UNKNOWN) != 0);
+
+ /*
+ * If validActor is defined for any of the actors, use it;
+ * otherwise, for compatibility with past versions, use the
+ * takeVerb disambiguation mechanism. If we have a pronoun, we
+ * can't decide yet how to do this, so presume that we'll use
+ * the new mechanism and switch later if necessary.
+ *
+ * If we have don't have a valid object (which will be the case
+ * for a pronoun), we can't decide until we get into the
+ * disambiguation process, so presume we'll use validActor for
+ * now.
+ */
+ verprop = PRP_VERACTOR;
+ if (nounlist[0].vocolobj == MCMONINV
+ || objgetap(ctx->voccxmem, nounlist[0].vocolobj, PRP_VALIDACTOR,
+ (objnum *)0, FALSE))
+ valprop = PRP_VALIDACTOR;
+ else
+ valprop = PRP_VALIDDO;
+
+ /* disambiguate it using the selected properties */
+ if (vocdisambig(ctx, actlist, nounlist, PRP_DODEFAULT, valprop,
+ verprop, cmd, MCMONINV, ctx->voccxme,
+ ctx->voccxvtk, MCMONINV, cmdbuf, FALSE))
+ {
+ /*
+ * if we have an unknown word in the list, assume for the
+ * moment that this isn't an actor phrase after all, but a
+ * verb
+ */
+ if (have_unknown)
+ {
+ /* indicate that we didn't find a noun phrase syntactically */
+ *next = cur;
+ }
+
+ /* return no actor */
+ VOC_RETVAL(ctx, save_sp, MCMONINV);
+ }
+
+ if ((l = voclistlen(actlist)) > 1)
+ {
+ vocerr(ctx, VOCERR(12),
+ "You can only speak to one person at a time.");
+ *next = cur + 1; /* error flag - return invalid but next!=cur */
+ VOC_RETVAL(ctx, save_sp, MCMONINV);
+ }
+ else if (l == 0)
+ return(MCMONINV);
+
+ if (cmd[*next] && vocspec(cmd[*next], VOCW_AND))
+ {
+ ++(*next);
+ VOC_RETVAL(ctx, save_sp, actlist[0].vocolobj);
+ }
+ }
+ if (cnt < 0)
+ {
+ /* error - make *next != cur */
+ *next = cur + 1;
+ }
+ else
+ *next = cur; /* no error condition, but nothing found */
+
+ VOC_RETVAL(ctx, save_sp, MCMONINV); /* so return invalid and *next == cur */
+}
+
+/* figure out how many objects are in an object list */
+int voclistlen(vocoldef *lst)
+{
+ int i;
+
+ for (i = 0 ; lst->vocolobj != MCMONINV || lst->vocolflg != 0 ;
+ ++lst, ++i) ;
+ return(i);
+}
+
+/*
+ * check access - evaluates cmdVerb.verprop(actor, obj, seqno), and
+ * returns whatever it returns. The seqno parameter is used for special
+ * cases, such as "ask", when the validation routine wishes to return
+ * "true" on the first object and "nil" on all subsequent objects which
+ * correspond to a particular noun phrase. We expect to be called with
+ * seqno==0 on the first object, non-zero on others; we will pass
+ * seqno==1 on the first object to the validation property, higher on
+ * subsequent objects, to maintain consistency with the TADS language
+ * convention of indexing from 1 up (as seen by the user in indexing
+ * functions). Note that if we're checking an actor, we'll just call
+ * obj.validActor() for the object itself (not the verb).
+ */
+int vocchkaccess(voccxdef *ctx, objnum obj, prpnum verprop,
+ int seqno, objnum cmdActor, objnum cmdVerb)
+{
+ /*
+ * special case: the special "string" and "number" objects are
+ * always accessible
+ */
+ if (obj == ctx->voccxstr || obj == ctx->voccxnum)
+ return TRUE;
+
+ /*
+ * If the access method is validActor, make sure the object in fact
+ * has a validActor method defined; if it doesn't, we must be
+ * running a game from before validActor's invention, so use the old
+ * ValidXo mechanism instead.
+ */
+ if (verprop == PRP_VALIDACTOR)
+ {
+ /* checking an actor - check to see if ValidActor is defined */
+ if (objgetap(ctx->voccxmem, obj, PRP_VALIDACTOR, (objnum *)0, FALSE))
+ {
+ /* ValidActor is present - call ValidActor in the object itself */
+ runppr(ctx->voccxrun, obj, verprop, 0);
+
+ /* return the result */
+ return runpoplog(ctx->voccxrun);
+ }
+ else
+ {
+ /* there's no ValidActor - call ValidXo in the "take" verb */
+ cmdVerb = ctx->voccxvtk;
+ verprop = PRP_VALIDDO;
+ }
+ }
+
+ /* call ValidXo in the verb */
+ runpnum(ctx->voccxrun, (long)(seqno + 1));
+ runpobj(ctx->voccxrun, obj);
+ runpobj(ctx->voccxrun,
+ (objnum)(cmdActor == MCMONINV ? ctx->voccxme : cmdActor));
+ runppr(ctx->voccxrun, cmdVerb, verprop, 3);
+
+ /* return the result */
+ return runpoplog(ctx->voccxrun);
+}
+
+/* ask game if object is visible to the actor */
+int vocchkvis(voccxdef *ctx, objnum obj, objnum cmdActor)
+{
+ runpobj(ctx->voccxrun,
+ (objnum)(cmdActor == MCMONINV ? ctx->voccxme : cmdActor));
+ runppr(ctx->voccxrun, obj, (prpnum)PRP_ISVIS, 1);
+ return(runpoplog(ctx->voccxrun));
+}
+
+/* set {numObj | strObj}.value, as appropriate */
+void vocsetobj(voccxdef *ctx, objnum obj, dattyp typ, void *val,
+ vocoldef *inobj, vocoldef *outobj)
+{
+ *outobj = *inobj;
+ outobj->vocolobj = obj;
+ objsetp(ctx->voccxmem, obj, PRP_VALUE, typ, val, ctx->voccxundo);
+}
+
+/* set up a vocoldef */
+static void vocout(vocoldef *outobj, objnum obj, int flg,
+ char *fst, char *lst)
+{
+ outobj->vocolobj = obj;
+ outobj->vocolflg = flg;
+ outobj->vocolfst = fst;
+ outobj->vocollst = lst;
+}
+
+/*
+ * Generate an appropriate error message saying that the objects in the
+ * command are visible, but can't be used with the command for some
+ * reason. Use the cantReach method of the verb (the new way), or if
+ * there is no cantReach in the verb, of each object in the list.
+ */
+void vocnoreach(voccxdef *ctx, objnum *list1, int cnt,
+ objnum actor, objnum verb, objnum prep, prpnum defprop,
+ int show_multi_prefix,
+ int multi_flags, int multi_base_index, int multi_total_count)
+{
+ /* see if the verb has a cantReach method - use it if so */
+ if (objgetap(ctx->voccxmem, verb, PRP_NOREACH, (objnum *)0, FALSE))
+ {
+ /* push arguments: (actor, dolist, iolist, prep) */
+ runpobj(ctx->voccxrun, prep);
+ if (defprop == PRP_DODEFAULT)
+ {
+ runpnil(ctx->voccxrun);
+ voc_push_objlist(ctx, list1, cnt);
+ }
+ else
+ {
+ voc_push_objlist(ctx, list1, cnt);
+ runpnil(ctx->voccxrun);
+ }
+ runpobj(ctx->voccxrun, actor);
+
+ /* invoke the method in the verb */
+ runppr(ctx->voccxrun, verb, PRP_NOREACH, 4);
+ }
+ else
+ {
+ int i;
+
+ /* use the old way - call obj.cantReach() for each object */
+ for (i = 0 ; i < cnt ; ++i)
+ {
+ /*
+ * display this object's name if there's more than one, so
+ * that the player can tell to which object this message
+ * applies
+ */
+ voc_multi_prefix(ctx, list1[i], show_multi_prefix, multi_flags,
+ multi_base_index + i, multi_total_count);
+
+ /* call cantReach on the object */
+ runpobj(ctx->voccxrun,
+ (objnum)(actor == MCMONINV ? ctx->voccxme : actor));
+ runppr(ctx->voccxrun, list1[i], (prpnum)PRP_NOREACH, 1);
+ tioflush(ctx->voccxtio);
+ }
+ }
+}
+
+/*
+ * Get the specialWords string for a given special word entry. Returns
+ * the first string if multiple strings are defined for the entry.
+ */
+static void voc_get_spec_str(voccxdef *ctx, char vocw_id,
+ char *buf, size_t buflen,
+ const char *default_name)
+{
+ int found;
+
+ /* presume we won't find it */
+ found = FALSE;
+
+ /* if there's a special word list, search it for this entry */
+ if (ctx->voccxspp != 0)
+ {
+ char *p;
+ char *endp;
+ size_t len;
+
+ /* find appropriate user-defined word in specialWords list */
+ for (p = ctx->voccxspp, endp = p + ctx->voccxspl ; p < endp ; )
+ {
+ /* if this one matches, get its first word */
+ if (*p++ == vocw_id)
+ {
+ /* note that we found it */
+ found = TRUE;
+
+ /*
+ * get the length, and limit it to the buffer size,
+ * leaving room for null termination
+ */
+ len = *p++;
+ if (len + 1 > buflen)
+ len = buflen - 1;
+
+ /* copy it and null-terminate the string */
+ memcpy(buf, p, len);
+ buf[len] = '\0';
+
+ /* we found it - no need to look any further */
+ break;
+ }
+
+ /*
+ * move on to the next one - skip the length prefix plus the
+ * length
+ */
+ p += *p + 1;
+ }
+ }
+
+ /* if we didn't find it, use the default */
+ if (!found)
+ {
+ strncpy(buf, default_name, (size_t)buflen);
+ buf[buflen - 1] = '\0';
+ }
+}
+
+/* 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,
+ prpnum defprop, int silent)
+{
+ if (obj == MCMONINV || !vocchkaccess(ctx, obj, (prpnum)accprop,
+ 0, actor, verb))
+ {
+ char nambuf[40];
+
+ /* get the display name for this specialWords entry */
+ voc_get_spec_str(ctx, vocw_id, nambuf, sizeof(nambuf), default_name);
+
+ /* show the error if appropriate */
+ if (!silent)
+ {
+ /* use 'noreach' if possible, otherwise use a default message */
+ if (obj == MCMONINV)
+ vocerr(ctx, VOCERR(13),
+ "I don't know what you're referring to with '%s'.",
+ nambuf);
+ else
+ vocnoreach(ctx, &obj, 1, actor, verb, prep,
+ defprop, FALSE, 0, 0, 1);
+ }
+
+ /* indicate that the antecedent is inaccessible */
+ return VOCERR(13);
+ }
+
+ /* set the object */
+ vocout(outobj, obj, 0, default_name, default_name);
+ return 0;
+}
+
+/*
+ * Get a new numbered object, given a number. This is used for objects
+ * that define '#' as one of their adjectives; we call the object,
+ * asking it to create an object with a particular number. The object
+ * can return nil, in which case we'll reject the command.
+ */
+static objnum voc_new_num_obj(voccxdef *ctx, objnum objn,
+ objnum actor, objnum verb,
+ long num, int plural)
+{
+ /* push the number - if we need a plural object, use nil instead */
+ if (plural)
+ runpnil(ctx->voccxrun);
+ else
+ runpnum(ctx->voccxrun, num);
+
+ /* push the other arguments and call the method */
+ runpobj(ctx->voccxrun, verb);
+ runpobj(ctx->voccxrun, actor);
+ runppr(ctx->voccxrun, objn, PRP_NEWNUMOBJ, 3);
+
+ /* if it was rejected, return an invalid object, else return the object */
+ if (runtostyp(ctx->voccxrun) == DAT_NIL)
+ {
+ rundisc(ctx->voccxrun);
+ return MCMONINV;
+ }
+ else
+ return runpopobj(ctx->voccxrun);
+}
+
+/* check if an object defines the special adjective '#' */
+static int has_gen_num_adj(voccxdef *ctx, objnum objn)
+{
+ vocwdef *v;
+ vocseadef search_ctx;
+
+ /* scan the list of objects defined '#' as an adjective */
+ for (v = vocffw(ctx, "#", 1, (char *)0, 0, PRP_ADJ, &search_ctx) ;
+ v ; v = vocfnw(ctx, &search_ctx))
+ {
+ /* if this is the object, return positive indication */
+ if (v->vocwobj == objn)
+ return TRUE;
+ }
+
+ /* didn't find it */
+ return FALSE;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Call the deepverb's disambigDobj or disambigIobj method to perform
+ * game-controlled disambiguation.
+ */
+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,
+ int num_wanted, int is_ambig, char *resp,
+ int silent)
+{
+ runcxdef *rcx = ctx->voccxrun;
+ prpnum call_prop;
+ runsdef val;
+ uchar *lstp;
+ uint lstsiz;
+ int ret;
+ int i;
+
+ /* check for actor disambiguation */
+ if (verprop == PRP_VERACTOR)
+ {
+ /* do nothing on actor disambiguation */
+ return VOC_DISAMBIG_CONT;
+ }
+
+ /* figure out whether this is a dobj method or an iobj method */
+ call_prop = (accprop == PRP_VALIDDO ? PRP_DISAMBIGDO : PRP_DISAMBIGIO);
+
+ /* if the method isn't defined, we can skip this entirely */
+ if (objgetap(ctx->voccxmem, verb, call_prop, (objnum *)0, FALSE) == 0)
+ return VOC_DISAMBIG_CONT;
+
+ /* push the "silent" flag */
+ val.runstyp = (silent ? DAT_TRUE : DAT_NIL);
+ runpush(rcx, val.runstyp, &val);
+
+ /* push the "is_ambiguous" flag */
+ val.runstyp = (is_ambig ? DAT_TRUE : DAT_NIL);
+ runpush(rcx, val.runstyp, &val);
+
+ /* push the "numWanted" count */
+ runpnum(rcx, num_wanted);
+
+ /* push the flag list */
+ voc_push_numlist(ctx, flags, *objcount);
+
+ /* push the object list */
+ voc_push_objlist(ctx, objlist, *objcount);
+
+ /* push the word list */
+ voc_push_strlist(ctx, firstwrd, lastwrd);
+
+ /* push the verification property */
+ val.runstyp = DAT_PROPNUM;
+ val.runsv.runsvprp = verprop;
+ runpush(rcx, DAT_PROPNUM, &val);
+
+ /* push the other object */
+ runpobj(rcx, otherobj);
+
+ /* push the preposition and the actor objects */
+ runpobj(rcx, prep);
+ runpobj(rcx, actor);
+
+ /* call the method */
+ runppr(rcx, verb, call_prop, 10);
+
+ /* check the return value */
+ switch(runtostyp(rcx))
+ {
+ case DAT_LIST:
+ /* get the list */
+ lstp = runpoplst(rcx);
+
+ /* read the list size prefix */
+ lstsiz = osrp2(lstp) - 2;
+ lstp += 2;
+
+ /* check for the status code */
+ if (lstsiz > 0 && *lstp == DAT_NUMBER)
+ {
+ /* get the status code */
+ ret = osrp4s(lstp+1);
+
+ /* skip the element */
+ lstadv(&lstp, &lstsiz);
+ }
+ else
+ {
+ /* there's no status code - assume CONTINUE */
+ ret = VOC_DISAMBIG_CONT;
+ }
+
+ /* check for a PARSE_RESP return */
+ if (ret == VOC_DISAMBIG_PARSE_RESP)
+ {
+ /* the second element is the string */
+ if (*lstp == DAT_SSTRING)
+ {
+ uint len;
+
+ /* get the length, and limit it to our buffer size */
+ len = osrp2(lstp+1) - 2;
+ if (len > VOCBUFSIZ - 1)
+ len = VOCBUFSIZ - 1;
+
+ /* copy the string into the caller's buffer */
+ memcpy(resp, lstp+3, len);
+ resp[len] = '\0';
+ }
+ else
+ {
+ /* there's no string - ignore it */
+ ret = VOC_DISAMBIG_CONT;
+ }
+ }
+ else
+ {
+ /* store the object list in the caller's list */
+ for (i = 0 ; lstsiz > 0 && i < VOCMAXAMBIG-1 ; ++i)
+ {
+ /* get this object */
+ if (*lstp == DAT_OBJECT)
+ objlist[i] = osrp2(lstp+1);
+ else
+ objlist[i] = MCMONINV;
+
+ /* skip the list entry */
+ lstadv(&lstp, &lstsiz);
+
+ /* check for flags */
+ if (lstsiz > 0 && *lstp == DAT_NUMBER)
+ {
+ /* store the flags */
+ flags[i] = (int)osrp4(lstp+1);
+
+ /* skip the flags elements */
+ lstadv(&lstp, &lstsiz);
+ }
+ else
+ {
+ /* no flags - use zero by default */
+ flags[i] = 0;
+ }
+ }
+
+ /* store a terminator at the end of the list */
+ objlist[i] = MCMONINV;
+ flags[i] = 0;
+
+ /* store the output count for the caller */
+ *objcount = i;
+ }
+
+ /* return the result */
+ return ret;
+
+ case DAT_NUMBER:
+ /* get the status code */
+ ret = runpopnum(rcx);
+
+ /* ignore raw PARSE_RESP codes, since they need to return a string */
+ if (ret == VOC_DISAMBIG_PARSE_RESP)
+ ret = VOC_DISAMBIG_CONT;
+
+ /* return the status */
+ return ret;
+
+ default:
+ /* treat anything else as CONTINUE */
+ rundisc(rcx);
+ return VOC_DISAMBIG_CONT;
+ }
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Prune a list of matches by keeping only the matches without the given
+ * flag value, if we have a mix of entries with and without the flag.
+ * This is a service routine for voc_prune_matches.
+ *
+ * The flag indicates a lower quality of matching, so this routine can
+ * be used to reduce ambiguity by keeping only the best-quality matches
+ * when matches of mixed quality are present.
+ */
+static int voc_remove_objs_with_flag(voccxdef *ctx,
+ objnum *list, uint *flags, int cnt,
+ int flag_to_remove)
+{
+ int i;
+ int flag_cnt;
+ int special_cnt;
+
+ /* first, count the number of objects with the flag */
+ for (i = 0, flag_cnt = special_cnt = 0 ; i < cnt ; ++i)
+ {
+ /* if this object exhibits the flag, count it */
+ if ((flags[i] & flag_to_remove) != 0)
+ ++flag_cnt;
+
+ /* if it's numObj or strObj, count it separately */
+ if (list[i] == ctx->voccxnum || list[i] == ctx->voccxstr)
+ ++special_cnt;
+ }
+
+ /*
+ * If all of the objects didn't have the flag, omit the ones that
+ * did, so that we reduce the ambiguity to those without the flag.
+ * Don't include the special objects (numObj and strObj) in the
+ * count, since they will never have any of these flags set.
+ */
+ if (flag_cnt != 0 && flag_cnt < cnt - special_cnt)
+ {
+ int dst;
+
+ /*
+ * Remove the flagged objects. Note that we can make this
+ * adjustment to the arrays in place, because they can only
+ * shrink - there's no need to make an extra temporary copy.
+ */
+ for (i = 0, dst = 0 ; i < cnt ; ++i)
+ {
+ /*
+ * If this one doesn't have the flag, keep it. Always keep
+ * the special objects (numObj and strObj).
+ */
+ if ((flags[i] & flag_to_remove) == 0
+ || list[i] == ctx->voccxnum
+ || list[i] == ctx->voccxstr)
+ {
+ /* copy this one to the output location */
+ list[dst] = list[i];
+ flags[dst] = flags[i];
+
+ /* count the new element of the output */
+ ++dst;
+ }
+ }
+
+ /* set the updated count */
+ cnt = dst;
+ list[cnt] = MCMONINV;
+ }
+
+ /* return the new count */
+ return cnt;
+}
+
+/*
+ * Prune a list of matches by keeping only the best matches when matches
+ * of different qualities are present.
+ *
+ * If we have a mix of objects matching noun phrases that end in
+ * adjectives and phrases ending in nouns with the same words, remove
+ * those elements that end in adjectives, keeping only the better
+ * matches that end in nouns.
+ *
+ * If we have a mix of objects where the words match exactly, and others
+ * where the words are only leading substrings of longer dictionary
+ * words, keep only the exact matches.
+ *
+ * Returns the number of elements in the result list.
+ */
+static int voc_prune_matches(voccxdef *ctx,
+ objnum *list, uint *flags, int cnt)
+{
+ /* remove matches that end with an adjective */
+ cnt = voc_remove_objs_with_flag(ctx, list, flags, cnt, VOCS_ENDADJ);
+
+ /* remove matches that use truncated words */
+ cnt = voc_remove_objs_with_flag(ctx, list, flags, cnt, VOCS_TRUNC);
+
+ /* return the new list size */
+ return cnt;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Count indistinguishable items.
+ *
+ * If 'keep_all' is true, we'll keep all of the items, whether or not
+ * some are indistinguishable from one another. If 'keep_all' is false,
+ * we'll keep only one item from each set of indistinguishable items.
+ */
+static int voc_count_diff(voccxdef *ctx, objnum *list, uint *flags, int *cnt,
+ int keep_all)
+{
+ int i;
+ int diff_cnt;
+
+ /*
+ * Presume all items will be distinguishable from one another. As
+ * we scan the list for indistinguishable items, we'll decrement
+ * this count each time we find an item that can't be distinguished
+ * from another item.
+ */
+ diff_cnt = *cnt;
+
+ /*
+ * Look for indistinguishable items.
+ *
+ * An object is distinguishable if it doesn't have the special
+ * property marking it as one of a group of equivalent objects
+ * (PRP_ISEQUIV), or if it has the property but there is no object
+ * following it in the list which has the same immediate superclass.
+ *
+ * Note that we want to keep the duplicates if we're looking for
+ * plurals, because the player is explicitly referring to all
+ * matching objects.
+ */
+ for (i = 0 ; i < *cnt ; ++i)
+ {
+ /*
+ * check to see if this object might have indistinguishable
+ * duplicates - it must be marked with isEquiv for this to be
+ * possible
+ */
+ runppr(ctx->voccxrun, list[i], PRP_ISEQUIV, 0);
+ if (runpoplog(ctx->voccxrun))
+ {
+ int j;
+ int dst;
+ objnum sc;
+
+ /* get the superclass, if possible */
+ sc = objget1sc(ctx->voccxmem, list[i]);
+ if (sc == MCMONINV)
+ continue;
+
+ /*
+ * run through the remainder of the list, and remove any
+ * duplicates of this item
+ */
+ for (j = i + 1, dst = i + 1 ; j < *cnt ; ++j)
+ {
+ /*
+ * see if it matches our object - if not, keep it in the
+ * list by copying it to our destination position
+ */
+ if (objget1sc(ctx->voccxmem, list[j]) != sc)
+ {
+ /* it's distinguishable - keep it */
+ list[dst] = list[j];
+ flags[dst++] = flags[j];
+ }
+ else
+ {
+ /*
+ * This item is indistinguishable from the list[i].
+ * First, reduce the count of different items.
+ */
+ --diff_cnt;
+
+ /*
+ * Keep this object only if we're keeping all
+ * redundant indistinguishable items.
+ */
+ if (keep_all)
+ {
+ /* keep all items -> keep this item */
+ list[dst] = list[j];
+ flags[dst++] = flags[j];
+ }
+ }
+ }
+
+ /* adjust the count to reflect the updated list */
+ *cnt = dst;
+
+ /* add a terminator */
+ list[dst] = MCMONINV;
+ flags[dst] = 0;
+ }
+ }
+
+ /* return the number of distinguishable items */
+ return diff_cnt;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * vocdisambig - determines which nouns in a noun list apply. When this
+ * is called, we must know the verb that we are processing, so we delay
+ * disambiguation until quite late in the parsing of a sentence, opting
+ * to keep all relevant information around until such time as we can
+ * meaningfully disambiguate.
+ *
+ * This routine resolves any "all [except...]", "it", and "them"
+ * references. We determine if all of the objects listed are accessible
+ * (via verb.validDo, verb.validIo). We finally try to determine which
+ * nouns apply when there are ambiguous nouns by using do.verDo<Verb>
+ * and io.verIo<Verb>.
+ */
+int vocdisambig(voccxdef *ctx, vocoldef *outlist, vocoldef *inlist,
+ prpnum defprop, prpnum accprop, prpnum verprop,
+ char *cmd[], objnum otherobj, objnum cmdActor,
+ objnum cmdVerb, objnum cmdPrep, char *cmdbuf,
+ int silent)
+{
+ int inpos;
+ int outpos;
+ int listlen = voclistlen(inlist);
+ int noreach = FALSE;
+ prpnum listprop;
+ uchar *save_sp;
+ int old_unknown, old_lastunk;
+ int err;
+ int still_ambig;
+ static char one_name[] = "ones";
+
+ voc_enter(ctx, &save_sp);
+
+ ERRBEGIN(ctx->voccxerr)
+
+ /* presume we will not leave any ambiguity in the result */
+ still_ambig = FALSE;
+
+ /* loop through all of the objects in the input list */
+ for (inpos = outpos = 0 ; inpos < listlen ; ++inpos)
+ {
+ /*
+ * reset the stack to our entrypoint value, since our stack
+ * variables are all temporary for a single iteration
+ */
+ voc_leave(ctx, save_sp);
+ voc_enter(ctx, &save_sp);
+
+ if (inlist[inpos].vocolflg == VOCS_STR)
+ {
+ vocsetobj(ctx, ctx->voccxstr, DAT_SSTRING,
+ inlist[inpos].vocolfst + 1,
+ &inlist[inpos], &outlist[outpos]);
+ ++outpos;
+ }
+ else if (inlist[inpos].vocolflg == VOCS_NUM)
+ {
+ long v1;
+ char vbuf[4];
+
+ v1 = atol(inlist[inpos].vocolfst);
+ oswp4s(vbuf, v1);
+ vocsetobj(ctx, ctx->voccxnum, DAT_NUMBER, vbuf,
+ &inlist[inpos], &outlist[outpos]);
+ ++outpos;
+ }
+ 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,
+ (char)(inlist[inpos].vocolflg == VOCS_IT
+ ? VOCW_IT : VOCW_THEM), defprop, silent);
+ if (err != 0)
+ goto done;
+ ++outpos;
+ }
+ else if (inlist[inpos].vocolflg == VOCS_HIM)
+ {
+ err = vocsetit(ctx, ctx->voccxhim, accprop, cmdActor, cmdVerb,
+ cmdPrep, &outlist[outpos], "him", VOCW_HIM,
+ defprop, silent);
+ if (err != 0)
+ goto done;
+ ++outpos;
+ }
+ else if (inlist[inpos].vocolflg == VOCS_HER)
+ {
+ err = vocsetit(ctx, ctx->voccxher, accprop, cmdActor, cmdVerb,
+ cmdPrep, &outlist[outpos], "her", VOCW_HER,
+ defprop, silent);
+ if (err != 0)
+ goto done;
+ ++outpos;
+ }
+ else if (inlist[inpos].vocolflg == VOCS_THEM)
+ {
+ int i;
+ int thempos = outpos;
+ static char them_name[] = "them";
+
+ for (i = 0 ; i < ctx->voccxthc ; ++i)
+ {
+ if (outpos >= VOCMAXAMBIG)
+ {
+ if (!silent)
+ vocerr(ctx, VOCERR(11),
+ "You're referring to too many objects.");
+ err = VOCERR(11);
+ goto done;
+ }
+
+ /* add object only if it's still accessible */
+ if (vocchkaccess(ctx, ctx->voccxthm[i], accprop, 0,
+ cmdActor, cmdVerb))
+ {
+ /* it's still accessible - add it to the list */
+ vocout(&outlist[outpos++], ctx->voccxthm[i], VOCS_THEM,
+ them_name, them_name);
+ }
+ else
+ {
+ /* it's not accessible - complain about it */
+ vocnoreach(ctx, &ctx->voccxthm[i], 1,
+ cmdActor, cmdVerb, cmdPrep,
+ defprop, TRUE, VOCS_THEM, i, ctx->voccxthc);
+ tioflush(ctx->voccxtio);
+ }
+ }
+
+ /* make sure we found at least one acceptable object */
+ if (outpos == thempos)
+ {
+ if (!silent)
+ vocerr(ctx, VOCERR(14),
+ "I don't know what you're referring to.");
+ err = VOCERR(14);
+ goto done;
+ }
+ }
+ else if (inlist[inpos].vocolflg == VOCS_ALL)
+ {
+ uchar *l;
+ int exccnt = 0;
+ int allpos = outpos;
+ int k;
+ uint len;
+ static char all_name[] = "all";
+ vocoldef *exclist;
+ vocoldef *exclist2;
+
+ VOC_MAX_ARRAY(ctx, vocoldef, exclist);
+ VOC_MAX_ARRAY(ctx, vocoldef, exclist2);
+
+ if (defprop != PRP_IODEFAULT)
+ runpobj(ctx->voccxrun, otherobj);
+ runpobj(ctx->voccxrun, cmdPrep);
+ runpobj(ctx->voccxrun, cmdActor);
+ runppr(ctx->voccxrun, cmdVerb, defprop,
+ defprop == PRP_DODEFAULT ? 3 : 2);
+
+ if (runtostyp(ctx->voccxrun) == DAT_LIST)
+ {
+ l = runpoplst(ctx->voccxrun);
+ len = osrp2(l) - 2;
+ l += 2;
+
+ while (len)
+ {
+ /* add list element to output if it's an object */
+ if (*l == DAT_OBJECT)
+ vocout(&outlist[outpos++], (objnum)osrp2(l+1), 0,
+ all_name, all_name);
+
+ /* move on to next list element */
+ lstadv(&l, &len);
+ }
+
+ vocout(&outlist[outpos], MCMONINV, 0, (char *)0, (char *)0);
+ }
+ else
+ rundisc(ctx->voccxrun); /* discard non-list value */
+
+ /* if we didn't get anything, complain about it and quit */
+ if (outpos <= allpos)
+ {
+ if (!silent)
+ vocerr(ctx, VOCERR(15),
+ "I don't see what you're referring to.");
+ err = VOCERR(15);
+ goto done;
+ }
+
+ /* remove any items in "except" list */
+ while (inlist[inpos + 1].vocolflg & VOCS_EXCEPT)
+ {
+ OSCPYSTRUCT(exclist[exccnt], inlist[++inpos]);
+ exclist[exccnt++].vocolflg &= ~VOCS_EXCEPT;
+ }
+ exclist[exccnt].vocolobj = MCMONINV;
+ exclist[exccnt].vocolflg = 0;
+
+ /* disambiguate "except" list */
+ if (exccnt)
+ {
+ err = vocdisambig(ctx, exclist2, exclist, defprop, accprop,
+ verprop, cmd, otherobj, cmdActor,
+ cmdVerb, cmdPrep, cmdbuf, silent);
+ if (err != 0)
+ goto done;
+
+ exccnt = voclistlen(exclist2);
+ for (k = 0 ; k < exccnt ; ++k)
+ {
+ int i;
+ for (i = allpos ; i < outpos ; ++i)
+ {
+ if (outlist[i].vocolobj == exclist2[k].vocolobj)
+ {
+ int j;
+ for (j = i ; j < outpos ; ++j)
+ outlist[j].vocolobj = outlist[j+1].vocolobj;
+ --i;
+ --outpos;
+ if (outpos <= allpos)
+ {
+ if (!silent)
+ vocerr(ctx, VOCERR(15),
+ "I don't see what you're referring to.");
+ err = VOCERR(15);
+ goto done;
+ }
+ }
+ }
+ }
+ }
+ }
+ else /* we have a (possibly ambiguous) noun */
+ {
+ int lpos = inpos;
+ int i = 0;
+ int cnt;
+ char *p;
+ int cnt2, cnt3;
+ int trying_again;
+ int user_count = 0;
+ objnum *cantreach_list;
+ int unknown_count;
+ int use_all_objs;
+ objnum *list1;
+ uint *flags1;
+ objnum *list2;
+ uint *flags2;
+ objnum *list3;
+ uint *flags3;
+ char *usrobj;
+ uchar *lstbuf;
+ char *newobj;
+ char *disnewbuf;
+ char *disbuffer;
+ char **diswordlist;
+ int *distypelist;
+ vocoldef *disnounlist;
+ int dst;
+
+ VOC_MAX_ARRAY(ctx, objnum, list1);
+ VOC_MAX_ARRAY(ctx, objnum, list2);
+ VOC_MAX_ARRAY(ctx, objnum, list3);
+ VOC_MAX_ARRAY(ctx, uint, flags1);
+ VOC_MAX_ARRAY(ctx, uint, flags2);
+ VOC_MAX_ARRAY(ctx, uint, flags3);
+ VOC_MAX_ARRAY(ctx, vocoldef, disnounlist);
+ VOC_STK_ARRAY(ctx, char, disnewbuf, VOCBUFSIZ);
+ VOC_STK_ARRAY(ctx, char, disbuffer, 2*VOCBUFSIZ);
+ VOC_STK_ARRAY(ctx, char *, diswordlist, VOCBUFSIZ);
+ VOC_STK_ARRAY(ctx, int, distypelist, VOCBUFSIZ);
+ VOC_STK_ARRAY(ctx, char, usrobj, VOCBUFSIZ);
+ VOC_STK_ARRAY(ctx, char, newobj, VOCBUFSIZ);
+ VOC_STK_ARRAY(ctx, uchar, lstbuf, 2 + VOCMAXAMBIG*3);
+
+ /* presume we won't resolve any unknown words */
+ unknown_count = 0;
+
+ /*
+ * Presume that we won't use all the objects that match
+ * these words, since we normally want to try to find a
+ * single, unambiguous match for a given singular noun
+ * phrase. Under certain circumstances, we'll want to keep
+ * all of the words that match the noun phrase, in which
+ * case we'll set this flag accordingly.
+ */
+ use_all_objs = FALSE;
+
+ /*
+ * go through the objects matching the current noun phrase
+ * and add them into our list
+ */
+ while (inlist[lpos].vocolfst == inlist[inpos].vocolfst
+ && lpos < listlen)
+ {
+ /* add this object to the list of nouns */
+ list1[i] = inlist[lpos].vocolobj;
+
+ /*
+ * note whether this object matched a plural, whether it
+ * matched adjective-at-end usage, and whether it
+ * matched a truncated dictionary word
+ */
+ flags1[i] = inlist[lpos].vocolflg
+ & (VOCS_PLURAL | VOCS_ANY | VOCS_COUNT
+ | VOCS_ENDADJ | VOCS_TRUNC);
+
+ /* if this is a valid object, count it */
+ if (list1[i] != MCMONINV)
+ ++i;
+
+ /* if there's a user count, note it */
+ if ((inlist[lpos].vocolflg & VOCS_COUNT) != 0)
+ user_count = atoi(inlist[lpos].vocolfst);
+
+ /* if an unknown word was involved, note it */
+ if ((inlist[lpos].vocolflg & VOCS_UNKNOWN) != 0)
+ ++unknown_count;
+
+ /* move on to the next entry */
+ ++lpos;
+ }
+
+ /* terminate the list */
+ list1[i] = MCMONINV;
+ cnt = i;
+
+ /*
+ * If this noun phrase contained an unknown word, check to
+ * see if the verb defines the parseUnknownXobj() method.
+ * If so, call the method and check the result.
+ */
+ if (unknown_count > 0)
+ {
+ prpnum prp;
+
+ /*
+ * figure out which method to call - use
+ * parseUnknownDobj if we're disambiguating the direct
+ * object, parseUnknownIobj for the indirect object
+ */
+ prp = (defprop == PRP_DODEFAULT
+ ? PRP_PARSEUNKNOWNDOBJ : PRP_PARSEUNKNOWNIOBJ);
+
+ /* check if the verb defines this method */
+ if (objgetap(ctx->voccxmem, cmdVerb, prp, (objnum *)0, FALSE))
+ {
+ uchar *lstp;
+ uint lstlen;
+
+ /* trace the event for debugging */
+ if (ctx->voccxflg & VOCCXFDBG)
+ tioputs(ctx->voccxtio,
+ "... unknown word: calling "
+ "parseUnknownXobj\\n");
+
+ /* push the list of words in the noun phrase */
+ voc_push_strlist(ctx, inlist[inpos].vocolfst,
+ inlist[inpos].vocollst);
+
+ /* push the other arguments */
+ runpobj(ctx->voccxrun, otherobj);
+ runpobj(ctx->voccxrun, cmdPrep);
+ runpobj(ctx->voccxrun, cmdActor);
+
+ /* call the method */
+ runppr(ctx->voccxrun, cmdVerb, prp, 4);
+
+ /* see what they returned */
+ switch(runtostyp(ctx->voccxrun))
+ {
+ case DAT_OBJECT:
+ /*
+ * use the object they returned as the match for
+ * the noun phrase
+ */
+ list1[cnt++] = runpopobj(ctx->voccxrun);
+
+ /* terminate the new list */
+ list1[cnt] = MCMONINV;
+ break;
+
+ case DAT_LIST:
+ /*
+ * use the list of objects they returned as the
+ * match for the noun phrase
+ */
+ lstp = runpoplst(ctx->voccxrun);
+
+ /* get the length of the list */
+ lstlen = osrp2(lstp) - 2;
+ lstp += 2;
+
+ /* run through the list's elements */
+ while (lstlen != 0)
+ {
+ /* if this is an object, add it */
+ if (*lstp == DAT_OBJECT
+ && i < VOCMAXAMBIG)
+ list1[cnt++] = osrp2(lstp+1);
+
+ /* move on to the next element */
+ lstadv(&lstp, &lstlen);
+ }
+
+ /*
+ * Note that we want to use all of these objects
+ * without disambiguation, since the game code
+ * has explicitly said that this is the list
+ * that matches the given noun phrase.
+ */
+ use_all_objs = TRUE;
+
+ /* terminate the new list */
+ list1[cnt] = MCMONINV;
+ break;
+
+ case DAT_TRUE:
+ /*
+ * A 'true' return value indicates that the
+ * parseUnknownXobj routine has fully handled
+ * the command. They don't want anything more
+ * to be done with these words. Simply remove
+ * the unknown words and continue with any other
+ * words in the list.
+ */
+ rundisc(ctx->voccxrun);
+
+ /* we're done with this input phrase */
+ continue;
+
+ default:
+ /*
+ * For anything else, use the default mechanism.
+ * Simply return an error; since the "unknown
+ * word" flag is set, we'll reparse the
+ * sentence, this time rejecting unknown words
+ * from the outset.
+ *
+ * Return error 2, since that's the generic "I
+ * don't know the word..." error code.
+ */
+ rundisc(ctx->voccxrun);
+ err = VOCERR(2);
+ goto done;
+ }
+
+ /*
+ * If we made it this far, it means that they've
+ * resolved the object for us, so we can consider
+ * the previously unknown words to be known now.
+ */
+ ctx->voccxunknown -= unknown_count;
+ }
+ else
+ {
+ /* trace the event for debugging */
+ if (ctx->voccxflg & VOCCXFDBG)
+ tioputs(ctx->voccxtio,
+ "... unknown word: no parseUnknownXobj - "
+ "restarting parsing\\n");
+
+ /*
+ * The verb doesn't define this method, so we should
+ * use the traditional method; simply return
+ * failure, and we'll reparse the sentence to reject
+ * the unknown word in the usual fashion. Return
+ * error 2, since that's the generic "I don't know
+ * the word..." error code.
+ */
+ err = VOCERR(2);
+ goto done;
+ }
+ }
+
+ /*
+ * Use a new method to cut down on the time it will take to
+ * iterate through the verprop's on all of those words.
+ * We'll call the verb's validXoList method - it should
+ * return a list containing all of the valid objects for the
+ * verb (it's sort of a Fourier transform of validDo).
+ * We'll intersect that list with the list we're about to
+ * disambiguate, which should provide a list of objects that
+ * are already qualified, in that validDo should return true
+ * for every one of them.
+ *
+ * The calling sequence is:
+ * verb.validXoList(actor, prep, otherobj)
+ *
+ * For reverse compatibility, if the return value is nil,
+ * we use the old algorithm and consider all objects
+ * that match the vocabulary. The return value must be
+ * a list to be considered.
+ *
+ * If disambiguating the actor, skip this phase, since
+ * we don't have a verb yet.
+ */
+ if (accprop != PRP_VALIDACTOR && cnt != 0)
+ {
+ if (defprop == PRP_DODEFAULT)
+ listprop = PRP_VALDOLIST;
+ else
+ listprop = PRP_VALIOLIST;
+
+ /* push the arguments: the actor, prep, and other object */
+ runpobj(ctx->voccxrun, otherobj);
+ runpobj(ctx->voccxrun, cmdPrep);
+ runpobj(ctx->voccxrun, cmdActor);
+ runppr(ctx->voccxrun, cmdVerb, listprop, 3);
+ if (runtostyp(ctx->voccxrun) == DAT_LIST)
+ {
+ uchar *l;
+ uint len;
+ int kept_numobj;
+
+ /* presume we won't keep numObj */
+ kept_numobj = FALSE;
+
+ /* read the list length prefix, and skip it */
+ l = runpoplst(ctx->voccxrun);
+ len = osrp2(l) - 2;
+ l += 2;
+
+ /*
+ * For each element of the return value, see if
+ * it's in list1. If so, copy the object into
+ * list2, unless it's already in list2.
+ */
+ for (cnt2 = 0 ; len != 0 ; )
+ {
+ if (*l == DAT_OBJECT)
+ {
+ objnum o = osrp2(l+1);
+
+ for (i = 0 ; i < cnt ; ++i)
+ {
+ if (list1[i] == o)
+ {
+ int j;
+
+ /* check to see if o is already in list2 */
+ for (j = 0 ; j < cnt2 ; ++j)
+ if (list2[j] == o) break;
+
+ /* if o is not in list2 yet, add it */
+ if (j == cnt2)
+ {
+ /* add it */
+ list2[cnt2] = o;
+ flags2[cnt2] = flags1[i];
+ ++cnt2;
+
+ /*
+ * if it's numObj, note that
+ * we've already included it in
+ * the output list, so that we
+ * don't add it again later
+ */
+ if (o == ctx->voccxnum)
+ kept_numobj = TRUE;
+ }
+ break;
+ }
+ }
+ }
+
+ /* move on to next element */
+ lstadv(&l, &len);
+ }
+
+ /*
+ * If the original list included numObj, keep it in
+ * the accessible list for now - we consider numObj
+ * to be always accessible. The noun phrase matcher
+ * will include numObj whenever the player enters a
+ * single number as a noun phrase, even when the
+ * number matches an object. Note that we can skip
+ * this special step if we already kept numObj in
+ * the valid list.
+ */
+ if (!kept_numobj)
+ {
+ /* search the original list for numObj */
+ for (i = 0 ; i < cnt ; ++i)
+ {
+ /* if this original entry is numObj, keep it */
+ if (list1[i] == ctx->voccxnum)
+ {
+ /* keep it in the accessible list */
+ list2[cnt2++] = ctx->voccxnum;
+
+ /* no need to look any further */
+ break;
+ }
+ }
+ }
+
+ /* copy list2 into list1 */
+ memcpy(list1, list2, (size_t)(cnt2 * sizeof(list1[0])));
+ memcpy(flags1, flags2, (size_t)cnt2 * sizeof(flags1[0]));
+ cnt = cnt2;
+ list1[cnt] = MCMONINV;
+ }
+ else
+ rundisc(ctx->voccxrun);
+ }
+
+ /*
+ * Determine accessibility and visibility. First, limit
+ * list1 to those objects that are visible OR accessible,
+ * and limit list3 to those objects that are visible.
+ */
+ for (cnt = cnt3 = i = 0 ; list1[i] != MCMONINV ; ++i)
+ {
+ int is_vis;
+ int is_acc;
+
+ /* determine if the object is visible */
+ is_vis = vocchkvis(ctx, list1[i], cmdActor);
+
+ /* determine if it's accessible */
+ is_acc = vocchkaccess(ctx, list1[i], accprop, i,
+ cmdActor, cmdVerb);
+
+ /* keep items that are visible OR accessible in list1 */
+ if (is_acc || is_vis)
+ {
+ list1[cnt] = list1[i];
+ flags1[cnt] = flags1[i];
+ ++cnt;
+ }
+
+ /*
+ * put items that are visible (regardless of whether or
+ * not they're accessible) in list3
+ */
+ if (is_vis)
+ {
+ list3[cnt3] = list1[i];
+ flags3[cnt3] = flags1[i];
+ ++cnt3;
+ }
+ }
+
+ /*
+ * If some of our accessible objects matched with an
+ * adjective at the end of the noun phrase, and others
+ * didn't (i.e., the others matched with a noun or plural at
+ * the end of the noun phrase), eliminate the ones that
+ * matched with an adjective at the end. Ending a noun
+ * phrase with an adjective is really a kind of short-hand;
+ * if we have matches for both the full name version (with a
+ * noun at the end) and a short-hand version, we want to
+ * discard the short-hand version so that we don't treat it
+ * as ambiguous with the long-name version. Likewise, if we
+ * have some exact matches and some truncations, keep only
+ * the exact matches.
+ */
+ cnt = voc_prune_matches(ctx, list1, flags1, cnt);
+ cnt3 = voc_prune_matches(ctx, list3, flags3, cnt3);
+
+ /*
+ * Now, reduce list1 to objects that are accessible. The
+ * reason for this multi-step process is to ensure that we
+ * prune the list with respect to every object in scope
+ * (visible or accessible for the verb), so that we get the
+ * most sensible pruning behavior. This is more sensible
+ * than pruning by accessibility only, because sometimes we
+ * may have objects that are visible but are not accessible;
+ * as far as the player is concerned, the visible objects
+ * are part of the current location, so the player should be
+ * able to refer to them regardless of whether they're
+ * accessible.
+ */
+ for (dst = 0, i = 0 ; i < cnt ; ++i)
+ {
+ /* check this object for accessibility */
+ if (vocchkaccess(ctx, list1[i], accprop, i,
+ cmdActor, cmdVerb))
+ {
+ /* keep it in the final list */
+ list1[dst] = list1[i];
+ flags1[dst] = flags1[i];
+
+ /* count the new list entry */
+ ++dst;
+ }
+ }
+
+ /* terminate list1 */
+ cnt = dst;
+ list1[dst] = MCMONINV;
+
+ /*
+ * Go through the list of accessible objects, and perform
+ * the sensible-object (verXoVerb) check on each. Copy each
+ * sensible object to list2.
+ */
+ for (i = 0, cnt2 = 0 ; i < cnt ; ++i)
+ {
+ /* run it by the appropriate sensible-object check */
+ if (accprop == PRP_VALIDACTOR)
+ {
+ /* run it through preferredActor */
+ runppr(ctx->voccxrun, list1[i], PRP_PREFACTOR, 0);
+ if (runpoplog(ctx->voccxrun))
+ {
+ list2[cnt2] = list1[i];
+ flags2[cnt2] = flags1[i];
+ ++cnt2;
+ }
+ }
+ else
+ {
+ /* run it through verXoVerb */
+ tiohide(ctx->voccxtio);
+ if (otherobj != MCMONINV)
+ runpobj(ctx->voccxrun, otherobj);
+ runpobj(ctx->voccxrun, cmdActor);
+ runppr(ctx->voccxrun, list1[i], verprop,
+ (otherobj != MCMONINV ? 2 : 1));
+
+ /*
+ * If that didn't result in a message, this object
+ * passed the tougher test of ver?oX, so include it
+ * in list2.
+ */
+ if (!tioshow(ctx->voccxtio))
+ {
+ list2[cnt2] = list1[i];
+ flags2[cnt2] = flags1[i];
+ ++cnt2;
+ }
+ }
+ }
+
+ /*
+ * Construct a string consisting of the words the user typed
+ * to reference this object, in case we need to complain.
+ */
+ usrobj[0] = '\0';
+ if (inlist[inpos].vocolfst != 0 && inlist[inpos].vocollst != 0)
+ {
+ for (p = inlist[inpos].vocolfst ; p <= inlist[inpos].vocollst
+ ; p += strlen(p) + 1)
+ {
+ /* add a space if we have a prior word */
+ if (usrobj[0] != '\0')
+ {
+ /* quote the space if the last word ended with '.' */
+ if (p[strlen(p)-1] == '.')
+ strcat(usrobj, "\\");
+
+ /* add the space */
+ strcat(usrobj, " ");
+ }
+
+ /* add the current word, or "of" if it's "of" */
+ if (voc_check_special(ctx, p, VOCW_OF))
+ vocaddof(ctx, usrobj);
+ else
+ strcat(usrobj, p);
+ }
+ }
+
+ /*
+ * If there's nothing in the YES list, and we have just a
+ * single number as our word, act as though they are talking
+ * about the number itself, rather than one of the objects
+ * that happened to use the number -- none of those objects
+ * make any sense, it seems, so fall back on the number.
+ *
+ * Note that we may also have only numObj in the YES list,
+ * because the noun phrase parser normally adds numObj when
+ * the player types a noun phrase consisting only of a
+ * number. Do the same thing in this case -- just return
+ * the number object.
+ */
+ if ((cnt2 == 0
+ || (cnt2 == 1 && list2[0] == ctx->voccxnum))
+ && inlist[inpos].vocolfst != 0
+ && inlist[inpos].vocolfst == inlist[inpos].vocollst
+ && vocisdigit(*inlist[inpos].vocolfst))
+ {
+ long v1;
+ char vbuf[4];
+
+ v1 = atol(inlist[inpos].vocolfst);
+ oswp4s(vbuf, v1);
+ vocsetobj(ctx, ctx->voccxnum, DAT_NUMBER, vbuf,
+ &inlist[inpos], &outlist[outpos]);
+ outlist[outpos].vocolflg = VOCS_NUM;
+ ++outpos;
+
+ /* skip all objects that matched the number */
+ for ( ; inlist[inpos+1].vocolobj != MCMONINV
+ && inlist[inpos+1].vocolfst == inlist[inpos].vocolfst
+ ; ++inpos) ;
+ continue;
+ }
+
+ /*
+ * Check if we found anything in either the YES (list2) or
+ * MAYBE (list1) lists. If there's nothing in either list,
+ * complain and return.
+ */
+ if (cnt2 == 0 && cnt == 0)
+ {
+ /*
+ * We have nothing sensible, and nothing even
+ * accessible. If there's anything merely visible,
+ * complain about those items.
+ */
+ if (cnt3 != 0)
+ {
+ /* there are visible items - complain about them */
+ cnt = cnt3;
+ cantreach_list = list3;
+ noreach = TRUE;
+
+ /* give the cantReach message, even for multiple objects */
+ goto noreach1;
+ }
+ else
+ {
+ /*
+ * explain that there's nothing visible or
+ * accessible matching the noun phrase, and abort
+ * the command with an error
+ */
+ if (!silent)
+ vocerr(ctx, VOCERR(9),
+ "I don't see any %s here.", usrobj);
+ err = VOCERR(9);
+ goto done;
+ }
+ }
+
+ /*
+ * If anything passed the stronger test (objects passing are
+ * in list2), use this as our proposed resolution for the
+ * noun phrase. If nothing passed the stronger test (i.e.,
+ * list2 is empty), simply keep the list of accessible
+ * objects in list1.
+ */
+ if (cnt2 != 0)
+ {
+ /*
+ * we have items passing the stronger test -- copy the
+ * stronger list (list2) to list1
+ */
+ cnt = cnt2;
+ memcpy(list1, list2, (size_t)(cnt2 * sizeof(list1[0])));
+ memcpy(flags1, flags2, (size_t)(cnt2 * sizeof(flags1[0])));
+ }
+
+ /*
+ * Check for redundant objects in the list. If the same
+ * object appears multiple times in the list, remove the
+ * extra occurrences. Sometimes, a game can inadvertantly
+ * define the same vocabulary word several times for the
+ * same object, because of the parser's leniency with
+ * matching leading substrings of 6 characters or longer.
+ * To avoid unnecessary "which x do you mean..." errors,
+ * simply discard any duplicates in the list.
+ */
+ for (dst = 0, i = 0 ; i < cnt ; ++i)
+ {
+ int dup;
+ int j;
+
+ /* presume we won't find a duplicate of this object */
+ dup = FALSE;
+
+ /*
+ * look for duplicates of this object in the remainder
+ * of the list
+ */
+ for (j = i + 1 ; j < cnt ; ++j)
+ {
+ /* check for a duplicate */
+ if (list1[i] == list1[j])
+ {
+ /* note that this object has a duplicate */
+ dup = TRUE;
+
+ /* we don't need to look any further */
+ break;
+ }
+ }
+
+ /*
+ * if this object has no duplicate, retain it in the
+ * output list
+ */
+ if (!dup)
+ {
+ /* copy the element to the output */
+ list1[dst] = list1[i];
+ flags1[dst] = flags1[i];
+
+ /* count the output */
+ ++dst;
+ }
+ }
+
+ /* update the count to the new list's size */
+ cnt = dst;
+ list1[cnt] = MCMONINV;
+
+ /*
+ * If we have more than one object in the list, and numObj
+ * is still in the list, remove numObj - we don't want to
+ * consider numObj to be considered ambiguous with another
+ * object when the other object passes access and validation
+ * tests.
+ */
+ if (cnt > 1)
+ {
+ /* scan the list for numObj */
+ for (i = 0, dst = 0 ; i < cnt ; ++i)
+ {
+ /* if this isn't numObj, keep this element */
+ if (list1[i] != ctx->voccxnum)
+ list1[dst++] = list1[i];
+ }
+
+ /* update the final count */
+ cnt = dst;
+ list1[cnt] = MCMONINV;
+ }
+
+ /*
+ * Check for a generic numeric adjective ('#' in the
+ * adjective list for the object) in each object. If we
+ * find it, we need to make sure there's a number in the
+ * name of the object.
+ */
+ for (i = 0 ; i < cnt ; ++i)
+ {
+ if (has_gen_num_adj(ctx, list1[i]))
+ {
+ /*
+ * If they specified a count, create the specified
+ * number of objects. Otherwise, if the object is
+ * plural, they mean to use all of the objects, so a
+ * numeric adjective isn't required -- set the
+ * numeric adjective property in the object to nil
+ * to so indicate. Otherwise, look for the number,
+ * and set the numeric adjective property
+ * accordingly.
+ */
+ if ((flags1[i] & (VOCS_ANY | VOCS_COUNT)) != 0)
+ {
+ int n = (user_count ? user_count : 1);
+ int j;
+ objnum objn = list1[i];
+
+ /*
+ * They specified a count, so we want to create
+ * n-1 copies of the numbered object. Make room
+ * for the n-1 new copies of this object by
+ * shifting any elements that follow up n-1
+ * slots.
+ */
+ if (i + 1 != cnt && n > 1)
+ {
+ memmove(&list1[i + n - 1], &list1[i],
+ (cnt - i) * sizeof(list1[i]));
+ memmove(&flags1[i + n - 1], &flags1[i],
+ (cnt - i) * sizeof(flags1[i]));
+ }
+
+ /* create n copies of this object */
+ for (j = 0 ; j < n ; ++j)
+ {
+ long l;
+
+ /*
+ * Generate a number for the new object,
+ * asking the object to tell us what value
+ * to use for an "any".
+ */
+ runpnum(ctx->voccxrun, (long)(j + 1));
+ runppr(ctx->voccxrun, objn, PRP_ANYVALUE, 1);
+ l = runpopnum(ctx->voccxrun);
+
+ /* try creating the new object */
+ list1[i+j] =
+ voc_new_num_obj(ctx, objn,
+ cmdActor, cmdVerb,
+ l, FALSE);
+ if (list1[i+j] == MCMONINV)
+ {
+ err = VOCERR(40);
+ goto done;
+ }
+ }
+ }
+ else if ((flags1[i] & VOCS_PLURAL) != 0)
+ {
+ /*
+ * get the plural object by asking for the
+ * numbered object with a nil number parameter
+ */
+ list1[i] =
+ voc_new_num_obj(ctx, list1[i], cmdActor, cmdVerb,
+ (long)0, TRUE);
+ if (list1[i] == MCMONINV)
+ {
+ err = VOCERR(40);
+ goto done;
+ }
+ }
+ else
+ {
+ //char *p;
+ int found;
+
+ /*
+ * No plural, no "any" - we just want to create
+ * one numbered object, using the number that
+ * the player must have specified. Make sure
+ * the player did, in fact, specify a number.
+ */
+ for (found = FALSE, p = inlist[inpos].vocolfst ;
+ p != 0 && p <= inlist[inpos].vocollst ;
+ p += strlen(p) + 1)
+ {
+ /* did we find it? */
+ if (vocisdigit(*p))
+ {
+ long l;
+
+ /* get the number */
+ l = atol(p);
+
+ /* create the object with this number */
+ list1[i] = voc_new_num_obj(ctx, list1[i],
+ cmdActor, cmdVerb,
+ l, FALSE);
+ if (list1[i] == MCMONINV)
+ {
+ err = VOCERR(40);
+ goto done;
+ }
+
+ /* the command looks to be valid */
+ found = TRUE;
+ break;
+ }
+ }
+
+ /* if we didn't find it, stop now */
+ if (!found)
+ {
+ if (!silent)
+ vocerr(ctx, VOCERR(160),
+ "You'll have to be more specific about which %s you mean.",
+ usrobj);
+ err = VOCERR(160);
+ goto done;
+ }
+ }
+ }
+ }
+
+ /*
+ * We still have an ambiguous word - ask the user which of
+ * the possible objects they meant to use
+ */
+ trying_again = FALSE;
+ for (;;)
+ {
+ int wrdcnt;
+ int next;
+ uchar *pu;
+ int cleared_noun;
+ int diff_cnt;
+ int stat;
+ int num_wanted;
+ int is_ambig;
+ int all_plural;
+
+ /*
+ * check for usage - determine if we have singular
+ * definite, singular indefinite, counted, or plural
+ * usage
+ */
+ if ((flags1[0] & (VOCS_PLURAL | VOCS_ANY | VOCS_COUNT)) != 0)
+ {
+ //int i;
+
+ /*
+ * loop through the objects to AND together the
+ * flags from all of the objects; we only care about
+ * the plural flags (PLURAL, ANY, and COUNT), so
+ * start out with only those, then AND off any that
+ * aren't in all of the objects
+ */
+ for (all_plural = VOCS_PLURAL | VOCS_ANY | VOCS_COUNT,
+ i = 0 ; i < cnt ; ++i)
+ {
+ /* AND out this object's flags */
+ all_plural &= flags1[i];
+
+ /*
+ * if we've ANDed down to zero, there's no need
+ * to look any further
+ */
+ if (!all_plural)
+ break;
+ }
+ }
+ else
+ {
+ /*
+ * it looks like we want just a single object -
+ * clear the various plural flags
+ */
+ all_plural = 0;
+ }
+
+ /*
+ * Count the distinguishable items.
+ *
+ * If we're looking for a single object, don't keep
+ * duplicate indistinguishable items (i.e., keep only
+ * one item from each set of mutually indistinguishable
+ * items), since we could equally well use any single
+ * one of those items. If we're looking for multiple
+ * objects, keep all of the items, since the user is
+ * referring to all of them.
+ */
+ diff_cnt = voc_count_diff(ctx, list1, flags1, &cnt,
+ all_plural != 0 || use_all_objs);
+
+ /*
+ * Determine how many objects we'd like to find. If we
+ * have a count specified, we'd like to find the given
+ * number of objects. If we have "ANY" specified, we
+ * just want to pick one object arbitrarily. If we have
+ * all plurals, we can keep all of the objects. If the
+ * 'use_all_objs' flag is true, it means that we can use
+ * everything in the list.
+ */
+ if (use_all_objs)
+ {
+ /* we want to use all of the objects */
+ num_wanted = cnt;
+ is_ambig = FALSE;
+ }
+ else if ((all_plural & VOCS_COUNT) != 0)
+ {
+ /*
+ * we have a count - we want exactly the given
+ * number of objects, but we can pick an arbitrary
+ * subset, so it's not ambiguous even if we have too
+ * many at the moment
+ */
+ num_wanted = user_count;
+ is_ambig = FALSE;
+ }
+ else if ((all_plural & VOCS_ANY) != 0)
+ {
+ /*
+ * they specified "any", so we want exactly one, but
+ * we can pick one arbitrarily, so there's no
+ * ambiguity
+ */
+ num_wanted = 1;
+ is_ambig = FALSE;
+ }
+ else if (all_plural != 0)
+ {
+ /*
+ * we have a simple plural, so we can use all of the
+ * provided objects without ambiguity
+ */
+ num_wanted = cnt;
+ is_ambig = FALSE;
+ }
+ else
+ {
+ /*
+ * it's a singular, definite usage, so we want
+ * exactly one item; if we have more than one in our
+ * list, it's ambiguous
+ */
+ num_wanted = 1;
+ is_ambig = (cnt != 1);
+ }
+
+ /* call the disambiguation hook */
+ stat = voc_disambig_hook(ctx, cmdVerb, cmdActor, cmdPrep,
+ otherobj, accprop, verprop,
+ list1, flags1, &cnt,
+ inlist[inpos].vocolfst,
+ inlist[inpos].vocollst,
+ num_wanted, is_ambig, disnewbuf,
+ silent);
+
+ /* check the status */
+ if (stat == VOC_DISAMBIG_DONE)
+ {
+ /* that's it - copy the result */
+ for (i = 0 ; i < cnt ; ++i)
+ vocout(&outlist[outpos++], list1[i], flags1[i],
+ inlist[inpos].vocolfst,
+ inlist[inpos].vocollst);
+
+ /* we're done */
+ break;
+ }
+ else if (stat == VOC_DISAMBIG_CONT)
+ {
+ /*
+ * Continue with the new list (which is the same as
+ * the old list, if it wasn't actually updated by
+ * the hook routine) - proceed with remaining
+ * processing, but using the new list.
+ *
+ * Because the list has been updated, we must once
+ * again count the number of distinguishable items,
+ * since that may have changed.
+ */
+ diff_cnt = voc_count_diff(ctx, list1, flags1, &cnt, TRUE);
+ }
+ else if (stat == VOC_DISAMBIG_PARSE_RESP
+ || stat == VOC_DISAMBIG_PROMPTED)
+ {
+ /*
+ * The status indicates one of the following:
+ *
+ * - the hook prompted for more information and read
+ * a response from the player, but decided not to
+ * parse it; we will continue with the current list,
+ * and parse the player's response as provided by
+ * the hook.
+ *
+ * - the hook prompted for more information, but
+ * left the reading to us. We'll proceed with the
+ * current list and read a response as normal, but
+ * without displaying another prompt.
+ *
+ * In any case, just continue processing; we'll take
+ * appropriate action on the prompting and reading
+ * when we reach those steps.
+ */
+ }
+ else
+ {
+ /* anything else is an error */
+ err = VOCERR(41);
+ goto done;
+ }
+
+ /*
+ * If we found only one word, or a plural/ANY, we are
+ * finished. If we found a count, use that count if
+ * possible.
+ */
+ if (cnt == 1 || all_plural || use_all_objs)
+ {
+ int flags;
+
+ /* keep only one of the objects if ANY was used */
+ if ((all_plural & VOCS_COUNT) != 0)
+ {
+ if (user_count > cnt)
+ {
+ if (!silent)
+ vocerr(ctx, VOCERR(30),
+ "I only see %d of those.", cnt);
+ err = VOCERR(30);
+ goto done;
+ }
+ cnt = user_count;
+ flags = VOCS_ALL;
+ }
+ else if ((all_plural & VOCS_ANY) != 0)
+ {
+ cnt = 1;
+ flags = VOCS_ALL;
+ }
+ else
+ flags = 0;
+
+ /* put the list */
+ for (i = 0 ; i < cnt ; ++i)
+ vocout(&outlist[outpos++], list1[i], flags,
+ inlist[inpos].vocolfst,
+ inlist[inpos].vocollst);
+
+ /* we're done */
+ break;
+ }
+
+ /* make sure output capturing is off */
+ tiocapture(ctx->voccxtio, (mcmcxdef *)0, FALSE);
+ tioclrcapture(ctx->voccxtio);
+
+ /*
+ * if we're in "silent" mode, we can't ask the player
+ * for help, so return an error
+ */
+ if (silent)
+ {
+ /*
+ * We can't disambiguate the list. Fill in the
+ * return list with what's left, which is still
+ * ambiguous, and note that we need to return an
+ * error code indicating that the list remains
+ * ambiguous.
+ */
+ for (i = 0 ; i < cnt && outpos < VOCMAXAMBIG ; ++i)
+ vocout(&outlist[outpos++], list1[i], 0,
+ inlist[inpos].vocolfst,
+ inlist[inpos].vocollst);
+
+ /* note that we have ambiguity remaining */
+ still_ambig = TRUE;
+
+ /* we're done with this sublist */
+ break;
+ }
+
+ /*
+ * We need to prompt for more information interactively.
+ * Figure out how we're going to display the prompt.
+ *
+ * - If the disambigXobj hook status (stat) indicates
+ * that the hook already displayed a prompt of its own,
+ * we don't need to add anything here.
+ *
+ * - Otherwise, if there's a parseDisambig function
+ * defined in the game, call it to display the prompt.
+ *
+ * - Otherwise, display our default prompt.
+ */
+ if (stat == VOC_DISAMBIG_PARSE_RESP
+ || stat == VOC_DISAMBIG_PROMPTED)
+ {
+ /*
+ * the disambigXobj hook already asked for a
+ * response, so don't display any prompt of our own
+ */
+ }
+ else if (ctx->voccxpdis != MCMONINV)
+ {
+ uint l;
+
+ /*
+ * There's a parseDisambig function defined in the
+ * game - call it to display the prompt, passing the
+ * list of possible objects and the player's
+ * original noun phrase text as parameters.
+ */
+ for (i = 0, pu = lstbuf+2 ; i < cnt ; ++i, pu += 2)
+ {
+ *pu++ = DAT_OBJECT;
+ oswp2(pu, list1[i]);
+ }
+ l = pu - lstbuf;
+ oswp2(lstbuf, l);
+ runpbuf(ctx->voccxrun, DAT_LIST, lstbuf);
+ runpstr(ctx->voccxrun, usrobj, (int)strlen(usrobj), 1);
+ runfn(ctx->voccxrun, ctx->voccxpdis, 2);
+ }
+ else
+ {
+ /* display "again" message, if necessary */
+ if (trying_again)
+ vocerr_info(ctx, VOCERR(100), "Let's try it again: ");
+
+ /* ask the user about it */
+ vocerr_info(ctx, VOCERR(101),
+ "Which %s do you mean, ", usrobj);
+ for (i = 0 ; i < cnt ; )
+ {
+ int eqcnt;
+ int j;
+ objnum sc;
+
+ /*
+ * See if we have multiple instances of an
+ * identical object. All such instances should
+ * be grouped together (this was done above), so
+ * we can just count the number of consecutive
+ * equivalent objects.
+ */
+ eqcnt = 1;
+ runppr(ctx->voccxrun, list1[i], PRP_ISEQUIV, 0);
+ if (runpoplog(ctx->voccxrun))
+ {
+ /* get the superclass, if possible */
+ sc = objget1sc(ctx->voccxmem, list1[i]);
+ if (sc != MCMONINV)
+ {
+ /* count equivalent objects that follow */
+ for (j = i + 1 ; j < cnt ; ++j)
+ {
+ if (objget1sc(ctx->voccxmem, list1[j])
+ == sc)
+ ++eqcnt;
+ else
+ break;
+ }
+ }
+ }
+
+ /*
+ * Display this object's name. If we have only
+ * one such object, display its thedesc,
+ * otherwise display its adesc.
+ */
+ runppr(ctx->voccxrun, list1[i],
+ (prpnum)(eqcnt == 1 ?
+ PRP_THEDESC : PRP_ADESC), 0);
+
+ /* display the separator as appropriate */
+ if (i + 1 < diff_cnt)
+ vocerr_info(ctx, VOCERR(102), ", ");
+ if (i + 2 == diff_cnt)
+ vocerr_info(ctx, VOCERR(103), "or ");
+
+ /* skip all equivalent items */
+ i += eqcnt;
+ }
+ vocerr_info(ctx, VOCERR(104), "?");
+ }
+
+ /*
+ * Read the response. If the disambigXobj hook already
+ * read the response, we don't need to read anything
+ * more.
+ */
+ if (stat != VOC_DISAMBIG_PARSE_RESP
+ && vocread(ctx, cmdActor, cmdVerb, disnewbuf,
+ (int)VOCBUFSIZ, 2) == VOCREAD_REDO)
+ {
+ /* they want to treat the input as a new command */
+ strcpy(cmdbuf, disnewbuf);
+ ctx->voccxunknown = 0;
+ ctx->voccxredo = TRUE;
+ err = VOCERR(43);
+ goto done;
+ }
+
+ /*
+ * parse the response
+ */
+
+ /* tokenize the list */
+ wrdcnt = voctok(ctx, disnewbuf, disbuffer, diswordlist,
+ TRUE, TRUE, TRUE);
+ if (wrdcnt == 0)
+ {
+ /* empty response - run pardon() function and abort */
+ runfn(ctx->voccxrun, ctx->voccxprd, 0);
+ err = VOCERR(42);
+ goto done;
+ }
+ if (wrdcnt < 0)
+ {
+ /* return the generic punctuation error */
+ err = VOCERR(1);
+ goto done;
+ }
+
+ /*
+ * Before we tokenize the sentence, remember the current
+ * unknown word count, then momentarily set the count to
+ * zero. This will cause the tokenizer to absorb any
+ * unknown words; if there are any unknown words, the
+ * tokenizer will parse them and set the unknown count.
+ * If we find any unknown words in the input, we'll
+ * simply treat the input as an entirely new command.
+ */
+ old_unknown = ctx->voccxunknown;
+ old_lastunk = ctx->voccxlastunk;
+ ctx->voccxunknown = 0;
+
+ /* clear our internal type list */
+ memset(distypelist, 0, VOCBUFSIZ * sizeof(distypelist[0]));
+
+ /* tokenize the sentence */
+ diswordlist[wrdcnt] = 0;
+ if (vocgtyp(ctx, diswordlist, distypelist, cmdbuf)
+ || ctx->voccxunknown != 0)
+ {
+ /*
+ * there's an unknown word or other problem - retry
+ * the input as an entirely new command
+ */
+ strcpy(cmdbuf, disnewbuf);
+ ctx->voccxunknown = 0;
+ ctx->voccxredo = TRUE;
+ err = VOCERR(2);
+ goto done;
+ }
+
+ /* restore the original unknown word count */
+ ctx->voccxunknown = old_unknown;
+ ctx->voccxlastunk = old_lastunk;
+
+ /*
+ * Find the last word that can be an adj and/or a noun.
+ * If it can be either (i.e., both bits are set), clear
+ * the noun bit and make it just an adjective. This is
+ * because we're asking for an adjective for clarification,
+ * and we most likely want it to be an adjective in this
+ * context; if the noun bit is set, too, the object lister
+ * will think it must be a noun, being the last word.
+ */
+ for (i = 0 ; i < wrdcnt ; ++i)
+ {
+ if (!(distypelist[i] &
+ (VOCT_ADJ | VOCT_NOUN | VOCT_ARTICLE)))
+ break;
+ }
+
+ if (i && (distypelist[i-1] & VOCT_ADJ)
+ && (distypelist[i-1] & VOCT_NOUN))
+ {
+ /*
+ * Note that we're clearing the noun flag. If
+ * we're unsuccessful in finding the object with the
+ * noun flag cleared, we'll put the noun flag back
+ * in and give it another try (by adding VOCT_NOUN
+ * back into distypelist[cleared_noun], and coming
+ * back to the label below).
+ */
+ cleared_noun = i-1;
+ distypelist[i-1] &= ~VOCT_NOUN;
+ }
+ else
+ cleared_noun = -1;
+
+ try_current_flags:
+ /* start with the first word */
+ if (vocspec(diswordlist[0], VOCW_ALL)
+ || vocspec(diswordlist[0], VOCW_BOTH))
+ {
+ char *nam;
+ static char all_name[] = "all";
+ static char both_name[] = "both";
+
+ if (vocspec(diswordlist[0], VOCW_ALL))
+ nam = all_name;
+ else
+ nam = both_name;
+
+ for (i = 0 ; i < cnt ; ++i)
+ vocout(&outlist[outpos++], list1[i], 0, nam, nam);
+ if (noreach)
+ {
+ cantreach_list = list1;
+ goto noreach1;
+ }
+ break;
+ }
+ 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);
+ break;
+ }
+ else
+ {
+ /* check for a word matching the phrase */
+ cnt2 = vocchknoun(ctx, diswordlist, distypelist,
+ 0, &next, disnounlist, FALSE);
+ if (cnt2 > 0)
+ {
+ /*
+ * if that didn't consume the entire phrase, or
+ * at least up to "one" or "ones" or a period,
+ * disallow it, since they must be entering
+ * something more complicated
+ */
+ if (diswordlist[next] != 0
+ && !vocspec(diswordlist[next], VOCW_ONE)
+ && !vocspec(diswordlist[next], VOCW_ONES)
+ && !vocspec(diswordlist[next], VOCW_THEN))
+ {
+ cnt2 = 0;
+ }
+ }
+ else if (cnt2 < 0)
+ {
+ /*
+ * There was a syntax error in the phrase.
+ * vocchknoun() will have displayed a message in
+ * this case, so we're done parsing this command.
+ */
+ err = VOCERR(45);
+ goto done;
+ }
+
+ /* proceed only if we got a valid phrase */
+ if (cnt2 > 0)
+ {
+ //int cnt3;
+ int newcnt;
+
+ /* build the list of matches for the new phrase */
+ for (i = 0, newcnt = 0 ; i < cnt2 ; ++i)
+ {
+ int j;
+ int found;
+
+ /*
+ * make sure this object isn't already in
+ * our list - we want each object only once
+ */
+ for (j = 0, found = FALSE ; j < newcnt ; ++j)
+ {
+ /* if this is in the list, note it */
+ if (list2[j] == disnounlist[i].vocolobj)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ /*
+ * add it to our list only if it wasn't
+ * already there
+ */
+ if (!found)
+ {
+ /* copy the object ID */
+ list2[newcnt] = disnounlist[i].vocolobj;
+
+ /* copy the flags that we care about */
+ flags2[newcnt] = disnounlist[i].vocolflg
+ & (VOCS_PLURAL | VOCS_ANY
+ | VOCS_COUNT);
+
+ /* count the entry */
+ ++newcnt;
+ }
+ }
+
+ /* terminate the list */
+ list2[newcnt] = MCMONINV;
+
+ /* intersect the new list with the old list */
+ newcnt = vocisect(list2, list1);
+
+ /* count the noun phrases in the new list */
+ for (i = cnt3 = 0 ; i < cnt2 ; ++i)
+ {
+ /* we have one more noun phrase */
+ ++cnt3;
+
+ /* if we have a noun phrase, skip matching objs */
+ if (disnounlist[i].vocolfst != 0)
+ {
+ int j;
+
+ /* skip objects matching this noun phrase */
+ for (j = i + 1 ; disnounlist[i].vocolfst ==
+ disnounlist[j].vocolfst ; ++j) ;
+ i = j - 1;
+ }
+ }
+
+ /*
+ * If the count of items in the intersection of
+ * the original list and the typed-in list is no
+ * bigger than the number of items specified in
+ * the typed-in list, we've successfully
+ * disambiguated the object, because the user's
+ * new list matches only one object for each set
+ * of words the user typed.
+ */
+ if (newcnt
+ && (newcnt <= cnt3
+ || (diswordlist[next]
+ && vocspec(diswordlist[next],
+ VOCW_ONES))))
+ {
+ for (i = 0 ; i < cnt ; ++i)
+ vocout(&outlist[outpos++], list2[i], 0,
+ one_name, one_name);
+
+ if (noreach)
+ {
+ cnt = newcnt;
+ cantreach_list = list2;
+ noreach1:
+ if (accprop == PRP_VALIDACTOR)
+ {
+ /* for actors, show a special message */
+ vocerr(ctx, VOCERR(31),
+ "You can't talk to that.");
+ }
+ else
+ {
+ /* use the normal no-reach message */
+ vocnoreach(ctx, cantreach_list, cnt,
+ cmdActor, cmdVerb, cmdPrep,
+ defprop, cnt > 1, 0, 0, cnt);
+ }
+ err = VOCERR(31);
+ goto done;
+ }
+ break;
+ }
+ else if (newcnt == 0)
+ {
+ /*
+ * If we cleared the noun, maybe we actually
+ * need to treat the word as a noun, so add
+ * the noun flag back in and give it another
+ * go. If we didn't clear the noun, there's
+ * nothing left to try, so explain that we
+ * don't see any such object and give up.
+ */
+ if (cleared_noun != -1)
+ {
+ distypelist[cleared_noun] |= VOCT_NOUN;
+ cleared_noun = -1;
+ goto try_current_flags;
+ }
+
+ /* find the first object with a noun phrase */
+ for (i = 0 ; i < cnt2 ; ++i)
+ {
+ /* if we have a noun phrase, stop scanning */
+ if (disnounlist[i].vocolfst != 0)
+ break;
+ }
+
+ /*
+ * if we found a noun phrase, build a string
+ * out of the words used; otherwise, just
+ * use "such"
+ */
+ if (i != cnt2) {
+ char *last;
+
+ /* clear the word buffer */
+ newobj[0] = '\0';
+
+ /* build a string out of the words */
+ p = disnounlist[i].vocolfst;
+ last = disnounlist[i].vocollst;
+ for ( ; p <= last ; p += strlen(p) + 1)
+ {
+ /*
+ * If this is a special word, we
+ * probably can't construct a
+ * sensible sentence - special words
+ * are special parts of speech that
+ * will look weird if inserted into
+ * our constructed noun phrase. In
+ * these cases, turn the entire
+ * thing into "I don't see any
+ * *such* object" rather than trying
+ * to make do with pronouns or other
+ * special words.
+ */
+ if (vocisspec(p))
+ {
+ /*
+ * replace the entire adjective
+ * phrase with "such"
+ */
+ strcpy(newobj, "such");
+
+ /*
+ * stop here - don't add any
+ * more, since "such" is the
+ * whole thing
+ */
+ break;
+ }
+
+ /* add a space if we have a prior word */
+ if (newobj[0] != '\0')
+ strcat(newobj, " ");
+
+ /* add this word */
+ strcat(newobj, p);
+ }
+ }
+ else
+ {
+ /* no noun phrase found */
+ strcpy(newobj, "such");
+ }
+
+ /* didn't find anything - complain and give up */
+ vocerr(ctx, VOCERR(16),
+ "You don't see any %s %s here.",
+ newobj, usrobj);
+ err = VOCERR(16);
+ goto done;
+ }
+
+ /*
+ * If we get here, it means that we have still
+ * more than one object per noun phrase typed in
+ * the latest sentence. Limit the list to the
+ * intersection (by copying list2 to list1), and
+ * try again.
+ */
+ memcpy(list1, list2,
+ (size_t)((newcnt + 1) * sizeof(list1[0])));
+ cnt = newcnt;
+ trying_again = TRUE;
+ }
+ else
+ {
+ /*
+ * We didn't find a noun phrase, so it's probably a
+ * new command. However, check first to see if we
+ * were making a trial run with the noun flag
+ * cleared: if so, go back and make another pass
+ * with the noun flag added back in to see if that
+ * works any better.
+ */
+ if (cleared_noun != -1)
+ {
+ distypelist[cleared_noun] |= VOCT_NOUN;
+ cleared_noun = -1;
+ goto try_current_flags;
+ }
+
+ /* retry as an entire new command */
+ strcpy(cmdbuf, disnewbuf);
+ ctx->voccxunknown = 0;
+ ctx->voccxredo = TRUE;
+ err = VOCERR(43);
+ goto done;
+ }
+ }
+ }
+ inpos = lpos - 1;
+ }
+ }
+
+ /* terminate the output list */
+ vocout(&outlist[outpos], MCMONINV, 0, (char *)0, (char *)0);
+
+ /*
+ * If we still have ambiguous objects, so indicate. This can only
+ * happen when we operate in "silent" mode, because only then can we
+ * give up without fully resolving a list.
+ */
+ if (still_ambig)
+ err = VOCERR(44);
+
+ /* no error */
+ err = 0;
+
+done:
+ ERRCLEAN(ctx->voccxerr)
+ {
+ /*
+ * reset the stack before we return, in case the caller handles
+ * the error without aborting the command
+ */
+ voc_leave(ctx, save_sp);
+ }
+ ERRENDCLN(ctx->voccxerr);
+
+ /* return success */
+ VOC_RETVAL(ctx, save_sp, err);
+}
+
+/* vocready - see if at end of command, execute & return TRUE if so */
+static int vocready(voccxdef *ctx, char *cmd[], int *typelist, int cur,
+ objnum cmdActor, objnum cmdPrep, char *vverb, char *vprep,
+ vocoldef *dolist, vocoldef *iolist, int *errp,
+ char *cmdbuf, int first_word, uchar **preparse_list,
+ int *next_start)
+{
+ if (cur != -1
+ && (cmd[cur] == (char *)0
+ || vocspec(cmd[cur], VOCW_AND) || vocspec(cmd[cur], VOCW_THEN)))
+ {
+ if (ctx->voccxflg & VOCCXFDBG)
+ {
+ char buf[128];
+
+ sprintf(buf, ". executing verb: %s %s\\n",
+ vverb, vprep ? vprep : "");
+ tioputs(ctx->vocxtio, buf);
+ }
+
+ *errp = execmd(ctx, cmdActor, cmdPrep, vverb, vprep, dolist, iolist,
+ &cmd[first_word], &typelist[first_word],cmdbuf,
+ cur - first_word, preparse_list, next_start);
+ return(TRUE);
+ }
+ return(FALSE);
+}
+
+/* execute a single command */
+static int voc1cmd(voccxdef *ctx, char *cmd[], char *cmdbuf,
+ objnum *cmdActorp, int first)
+{
+ int cur;
+ int next;
+ objnum o;
+ vocwdef *v;
+ char *vverb;
+ int vvlen;
+ char *vprep;
+ int cnt;
+ int err;
+ vocoldef *dolist;
+ vocoldef *iolist;
+ int *typelist;
+ objnum cmdActor = *cmdActorp;
+ objnum cmdPrep;
+ int swapObj; /* TRUE -> swap dobj and iobj */
+ int again;
+ int first_word;
+ uchar *preparse_list = nullptr;
+ int next_start;
+ struct
+ {
+ int active;
+ int cur;
+ char **cmd;
+ char *cmdbuf;
+ } preparseCmd_stat;
+ char **newcmd;
+ char *origcmdbuf;
+ char *newcmdbuf;
+ uchar *save_sp;
+ int no_match;
+ int retval;
+
+ voc_enter(ctx, &save_sp);
+ VOC_MAX_ARRAY(ctx, vocoldef, dolist);
+ VOC_MAX_ARRAY(ctx, vocoldef, iolist);
+ VOC_STK_ARRAY(ctx, int, typelist, VOCBUFSIZ);
+ VOC_STK_ARRAY(ctx, char *, newcmd, VOCBUFSIZ);
+ VOC_STK_ARRAY(ctx, char, newcmdbuf, VOCBUFSIZ);
+
+ preparseCmd_stat.active = 0;
+ preparseCmd_stat.cur = 0;
+ preparseCmd_stat.cmd = nullptr;
+ preparseCmd_stat.cmdbuf = nullptr;
+
+ /* save the original command buf in case we need to redo the command */
+ origcmdbuf = cmdbuf;
+
+ /* clear out the type list */
+ memset(typelist, 0, VOCBUFSIZ*sizeof(typelist[0]));
+
+ /* get the types of the words in the command */
+ if (vocgtyp(ctx, cmd, typelist, cmdbuf))
+ {
+ retval = 1;
+ goto done;
+ }
+
+ /* start off at the first word */
+ cur = next = first_word = 0;
+
+ /*
+ * Presume we will be in control of the next word - when execmd() or
+ * another routine we call decides where the command ends, it will
+ * fill in a new value here. When this value is non-zero, it will
+ * tell us where the next sentence start is relative to the previous
+ * sentence start.
+ */
+ next_start = 0;
+
+ /* we don't have a preparseCmd result yet */
+ preparseCmd_stat.active = FALSE;
+
+ /* keep going until we run out of work to do */
+ for (again = FALSE, err = 0 ; ; again = TRUE)
+ {
+ /*
+ * if preparseCmd sent us back a list, parse that list as a new
+ * command
+ */
+ if (err == ERR_PREPRSCMDREDO)
+ {
+ uchar *src;
+ size_t len;
+ size_t curlen;
+ char *dst;
+ //int cnt;
+
+ /* don't allow a preparseCmd to loop */
+ if (preparseCmd_stat.active)
+ {
+ vocerr(ctx, VOCERR(34),
+ "Internal game error: preparseCmd loop");
+ retval = 1;
+ goto done;
+ }
+
+ /* save our status prior to processing the preparseCmd list */
+ preparseCmd_stat.active = TRUE;
+ preparseCmd_stat.cur = cur;
+ preparseCmd_stat.cmd = cmd;
+ preparseCmd_stat.cmdbuf = cmdbuf;
+
+ /* set up with the new command */
+ cmd = newcmd;
+ cmdbuf = newcmdbuf;
+ cur = 0;
+
+ /* break up the list into the new command buffer */
+ src = preparse_list;
+ len = osrp2(src) - 2;
+ for (src += 2, dst = cmdbuf, cnt = 0 ; len ; )
+ {
+ /* make sure the next element is a string */
+ if (*src != DAT_SSTRING)
+ {
+ vocerr(ctx, VOCERR(32),
+ "Internal game error: preparseCmd returned an invalid list");
+ retval = 1;
+ goto done;
+ }
+
+ /* get the string */
+ ++src;
+ curlen = osrp2(src) - 2;
+ src += 2;
+
+ /* make sure it will fit in the buffer */
+ if (dst + curlen + 1 >= cmdbuf + VOCBUFSIZ)
+ {
+ vocerr(ctx, VOCERR(33),
+ "Internal game error: preparseCmd command too long");
+ retval = 1;
+ goto done;
+ }
+
+ /* store the word */
+ cmd[cnt++] = dst;
+ memcpy(dst, src, curlen);
+ dst[curlen] = '\0';
+
+ /* move on to the next word */
+ len -= 3 + curlen;
+ src += curlen;
+ dst += curlen + 1;
+ }
+
+ /* enter a null last word */
+ cmd[cnt] = 0;
+
+ /* generate the type list for the new list */
+ if (vocgtyp(ctx, cmd, typelist, cmdbuf))
+ {
+ /* return an error */
+ retval = 1;
+ goto done;
+ }
+
+ /*
+ * this is not a new command - it's just further processing
+ * of the current command
+ */
+ again = FALSE;
+
+ /* clear the error */
+ err = 0;
+ }
+
+ /* initialize locals */
+ cmdPrep = MCMONINV; /* assume no preposition */
+ swapObj = FALSE; /* assume no object swapping */
+ dolist[0].vocolobj = iolist[0].vocolobj = MCMONINV;
+ dolist[0].vocolflg = iolist[0].vocolflg = 0;
+
+ /* check error return from vocready (which returns from execmd) */
+ if (err)
+ {
+ /* return the error */
+ retval = err;
+ goto done;
+ }
+
+ skip_leading_stuff:
+ /*
+ * If someone updated the sentence start point, jump there. The
+ * sentence start is relative to the previous sentence start.
+ */
+ if (next_start != 0)
+ cur = first_word + next_start;
+
+ /* clear next_start, so we can tell if someone updates it */
+ next_start = 0;
+
+ /* skip any leading THEN's and AND's */
+ while (cmd[cur] && (vocspec(cmd[cur], VOCW_THEN)
+ || vocspec(cmd[cur], VOCW_AND)))
+ ++cur;
+
+ /* see if there's anything left to parse */
+ if (cmd[cur] == 0)
+ {
+ /*
+ * if we've been off doing preparseCmd work, return to the
+ * original command list
+ */
+ if (preparseCmd_stat.active)
+ {
+ /* restore the original status */
+ cur = preparseCmd_stat.cur;
+ cmd = preparseCmd_stat.cmd;
+ cmdbuf = preparseCmd_stat.cmdbuf;
+ preparseCmd_stat.active = FALSE;
+
+ /* get the type list for the original list again */
+ if (vocgtyp(ctx, cmd, typelist, cmdbuf))
+ {
+ /* return the error */
+ retval = 1;
+ goto done;
+ }
+
+ /* try again */
+ goto skip_leading_stuff;
+ }
+ else
+ {
+ /* nothing to pop - we must be done */
+ retval = 0;
+ goto done;
+ }
+ }
+
+ /*
+ * display a blank line if this is not the first command on this
+ * command line, so that we visually separate the results of the
+ * new command from the results of the previous command
+ */
+ if (again)
+ outformat("\\b"); /* tioblank(ctx->voccxtio); */
+
+ {
+ /* look for an explicit actor in the command */
+ if ((o = vocgetactor(ctx, cmd, typelist, cur, &next, cmdbuf))
+ != MCMONINV)
+ {
+ /* skip the actor noun phrase in the input */
+ cur = next;
+
+ /* remember the actor internally */
+ cmdActor = *cmdActorp = o;
+
+ /* set the actor in the context */
+ ctx->voccxactor = o;
+ }
+
+ /* if the actor parsing failed, return an error */
+ if (cur != next)
+ {
+ /* error getting actor */
+ retval = 1;
+ goto done;
+ }
+ }
+
+ /* remember where the sentence starts */
+ first_word = cur;
+
+ /* make sure we have a verb */
+ if ((cmd[cur] == (char *)0) || !(typelist[cur] & VOCT_VERB))
+ {
+ /* unknown verb - handle it with parseUnknownVerb if possible */
+ if (!try_unknown_verb(ctx, cmdActor, &cmd[cur], &typelist[cur],
+ 0, &next_start, TRUE, VOCERR(17),
+ "There's no verb in that sentence!"))
+ {
+ /* error - abort the command */
+ retval = 1;
+ goto done;
+ }
+ else
+ {
+ /* go back for more */
+ continue;
+ }
+ }
+ vverb = cmd[cur++]; /* this is the verb */
+ vvlen = strlen(vverb); /* remember length of verb */
+ vprep = 0; /* assume no verb-preposition */
+
+ /* execute if the command is just a verb */
+ if (vocready(ctx, cmd, typelist, cur, cmdActor, cmdPrep,
+ vverb, vprep, dolist, iolist, &err, cmdbuf,
+ first_word, &preparse_list, &next_start))
+ continue;
+
+ /*
+ * If the next word is a preposition, and it makes sense to be
+ * aggregated with this verb, use it as such.
+ */
+ if (typelist[cur] & VOCT_PREP)
+ {
+ if (vocffw(ctx, vverb, vvlen, cmd[cur], (int)strlen(cmd[cur]),
+ PRP_VERB, (vocseadef *)0))
+ {
+ vprep = cmd[cur++];
+ if (vocready(ctx, cmd, typelist, cur, cmdActor, cmdPrep,
+ vverb, vprep, dolist, iolist, &err, cmdbuf,
+ first_word, &preparse_list, &next_start))
+ continue;
+ }
+ else
+ {
+ /*
+ * If we have a preposition which can NOT be aggregated
+ * with the verb, take command of this form: "verb prep
+ * iobj dobj". Note that we do *not* do this if the
+ * word is also a noun, or it's an adjective and a noun
+ * (possibly separated by one or more adjectives)
+ * follows.
+ */
+ if ((v = vocffw(ctx, cmd[cur], (int)strlen(cmd[cur]),
+ (char *)0, 0, PRP_PREP, (vocseadef *)0)) != 0)
+ {
+ int swap_ok;
+
+ /* if it can be an adjective, check further */
+ if (typelist[cur] & VOCT_NOUN)
+ {
+ /* don't allow the swap */
+ swap_ok = FALSE;
+ }
+ else if (typelist[cur] & VOCT_ADJ)
+ {
+ int i;
+
+ /* look for a noun, possibly preceded by adj's */
+ for (i = cur + 1 ;
+ cmd[i] && (typelist[i] & VOCT_ADJ)
+ && !(typelist[i] & VOCT_NOUN) ; ++i) ;
+ swap_ok = (!cmd[i] || !(typelist[i] & VOCT_NOUN));
+ }
+ else
+ {
+ /* we can definitely allow this swap */
+ swap_ok = TRUE;
+ }
+
+ if (swap_ok)
+ {
+ cmdPrep = v->vocwobj;
+ swapObj = TRUE;
+ ++cur;
+ }
+ }
+ }
+ }
+
+ retry_swap:
+ /* get the direct object if there is one */
+ if ((cnt = vocchknoun2(ctx, cmd, typelist, cur, &next, dolist,
+ FALSE, &no_match)) > 0)
+ {
+ /* we found a noun phrase matching one or more objects */
+ cur = next;
+ }
+ else if (no_match)
+ {
+ /*
+ * we found a valid noun phrase, but we didn't find any
+ * objects that matched the words -- get the noun again,
+ * this time showing the error
+ */
+ vocgetnoun(ctx, cmd, typelist, cur, &next, dolist);
+
+ /* return the error */
+ retval = 1;
+ goto done;
+ }
+ else if (cnt < 0)
+ {
+ /* invalid syntax - return failure */
+ retval = 1;
+ goto done;
+ }
+ else
+ {
+ /*
+ * If we thought we were going to get a two-object
+ * sentence, and we got a zero-object sentence, and it looks
+ * like the word we took as a preposition is also an
+ * adjective or noun, go back and treat it as such.
+ */
+ if (swapObj &&
+ ((typelist[cur-1] & VOCT_NOUN)
+ || (typelist[cur-1] & VOCT_ADJ)))
+ {
+ --cur;
+ swapObj = FALSE;
+ cmdPrep = MCMONINV;
+ goto retry_swap;
+ }
+
+ bad_sentence:
+ /* find the last word */
+ while (cmd[cur]) ++cur;
+
+ /* try running the sentence through preparseCmd */
+ err = try_preparse_cmd(ctx, &cmd[first_word], cur - first_word,
+ &preparse_list);
+ switch(err)
+ {
+ case 0:
+ /* preparseCmd didn't do anything - the sentence fails */
+ if (!try_unknown_verb(ctx, cmdActor, &cmd[first_word],
+ &typelist[first_word], 0, &next_start,
+ TRUE, VOCERR(18),
+ "I don't understand that sentence."))
+ {
+ /* error - abort the command */
+ retval = 1;
+ goto done;
+ }
+ else
+ {
+ /* success - go back for more */
+ continue;
+ }
+
+ case ERR_PREPRSCMDCAN:
+ /* they cancelled - we're done with the sentence */
+ retval = 0;
+ goto done;
+
+ case ERR_PREPRSCMDREDO:
+ /* reparse with the new sentence */
+ continue;
+ }
+ }
+
+ /* see if we want to execute the command now */
+ if (vocready(ctx, cmd, typelist, cur, cmdActor, cmdPrep,
+ vverb, vprep,
+ swapObj ? iolist : dolist,
+ swapObj ? dolist : iolist,
+ &err, cmdbuf, first_word, &preparse_list,
+ &next_start))
+ continue;
+
+ /*
+ * Check for an indirect object, which may or may not be preceded
+ * by a preposition. (Note that the lack of a preposition implies
+ * that the object we already found is the indirect object, and the
+ * next object is the direct object. It also implies a preposition
+ * of "to.")
+ */
+ if (cmdPrep == MCMONINV && (typelist[cur] & VOCT_PREP))
+ {
+ char *p1 = cmd[cur++];
+
+ /* if this is the end of the sentence, add the prep to the verb */
+ if (cmd[cur] == (char *)0
+ || vocspec(cmd[cur], VOCW_AND)
+ || vocspec(cmd[cur], VOCW_THEN))
+ {
+ if (vocffw(ctx, vverb, vvlen, p1, (int)strlen(p1), PRP_VERB,
+ (vocseadef *)0)
+ && !vprep)
+ vprep = p1;
+ else
+ {
+ /* call parseUnknownVerb */
+ if (!try_unknown_verb(ctx, cmdActor, &cmd[first_word],
+ &typelist[first_word], 0,
+ &next_start, TRUE, VOCERR(19),
+ "There are words after your command I couldn't use."))
+ {
+ /* error - abandon the command */
+ retval = 1;
+ goto done;
+ }
+ else
+ {
+ /* go back for more */
+ continue;
+ }
+ }
+
+ if ((err = execmd(ctx, cmdActor, cmdPrep, vverb, vprep,
+ dolist, iolist,
+ &cmd[first_word], &typelist[first_word],
+ cmdbuf, cur - first_word,
+ &preparse_list, &next_start)) != 0)
+ {
+ retval = 1;
+ goto done;
+ }
+ continue;
+ }
+
+ /*
+ * If we have no verb preposition already, and we have
+ * another prep-capable word following this prep-capable
+ * word, and this preposition aggregates with the verb, take
+ * it as a sentence of the form "pry box open with crowbar"
+ * (where the current word is "with"). We also must have at
+ * least one more word after that, since there will have to
+ * be an indirect object.
+ */
+ if (cmd[cur] && (typelist[cur] & VOCT_PREP) && cmd[cur+1]
+ && vprep == 0
+ && vocffw(ctx, vverb, vvlen, p1, (int)strlen(p1), PRP_VERB,
+ (vocseadef *)0))
+ {
+ /*
+ * check to make sure that the next word, which we're
+ * about to take for a prep (the "with" in "pry box open
+ * with crowbar") is actually not part of an object name
+ * - if it is, use it as the object name rather than as
+ * the prep
+ */
+ if (vocgobj(ctx, cmd, typelist, cur, &next,
+ FALSE, iolist, FALSE, FALSE, 0) <= 0)
+ {
+ /* aggregate the first preposition into the verb */
+ vprep = p1;
+
+ /* use the current word as the object-introducing prep */
+ p1 = cmd[cur++];
+ }
+ }
+
+ /* try for an indirect object */
+ cnt = vocgobj(ctx, cmd, typelist, cur, &next, TRUE, iolist,
+ TRUE, FALSE, &no_match);
+ if (cnt > 0)
+ {
+ cur = next;
+ v = vocffw(ctx, p1, (int)strlen(p1), (char *)0, 0, PRP_PREP,
+ (vocseadef *)0);
+ if (v == (vocwdef *)0)
+ {
+ /* let parseUnknownVerb handle it */
+ if (!try_unknown_verb(ctx, cmdActor, &cmd[first_word],
+ &typelist[first_word], 0,
+ &next_start, TRUE, VOCERR(20),
+ "I don't know how to use the word \"%s\" like that.", p1))
+ {
+ /* error - abort the command */
+ retval = 1;
+ goto done;
+ }
+ else
+ {
+ /* go on to the next command */
+ continue;
+ }
+ }
+ cmdPrep = v->vocwobj;
+
+ if (vocready(ctx, cmd, typelist, cur, cmdActor, cmdPrep,
+ vverb, vprep, dolist, iolist, &err, cmdbuf,
+ first_word, &preparse_list, &next_start))
+ continue;
+ else if ((typelist[cur] & VOCT_PREP) &&
+ vocffw(ctx, vverb, vvlen, cmd[cur],
+ (int)strlen(cmd[cur]), PRP_VERB,
+ (vocseadef *)0) && !vprep)
+ {
+ vprep = cmd[cur++];
+ if (vocready(ctx, cmd, typelist, cur, cmdActor,
+ cmdPrep, vverb, vprep, dolist, iolist,
+ &err, cmdbuf, first_word, &preparse_list,
+ &next_start))
+ continue;
+ else
+ {
+ /* let parseUnknownVerb handle it */
+ if (!try_unknown_verb(ctx, cmdActor, &cmd[first_word],
+ &typelist[first_word], 0,
+ &next_start, TRUE, VOCERR(19),
+ "There are words after your command I couldn't use."))
+ {
+ /* error - abandon the command */
+ retval = 1;
+ goto done;
+ }
+ else
+ {
+ /* go on to the next command */
+ continue;
+ }
+ }
+ }
+ else
+ {
+ /*
+ * If the latter object phrase is flagged with the
+ * "trimmed preposition" flag, meaning that we could
+ * have used the preposition in the noun phrase but
+ * assumed instead it was part of the verb, reverse
+ * this assumption now: add the preposition back to the
+ * noun phrase and explain that there's no such thing
+ * present.
+ *
+ * Otherwise, we simply have an unknown verb phrasing,
+ * so let parseUnknownVerb handle it.
+ */
+ vocoldef *np1 =
+ (dolist[0].vocolflg & VOCS_TRIMPREP) != 0
+ ? &dolist[0]
+ : (iolist[0].vocolflg & VOCS_TRIMPREP) != 0
+ ? &iolist[0]
+ : 0;
+ if (np1 != 0)
+ {
+ char namebuf[VOCBUFSIZ];
+
+ /* show the name, adding the prep back in */
+ voc_make_obj_name_from_list(
+ ctx, namebuf, cmd, np1->vocolfst, np1->vocolhlst);
+ vocerr(ctx, VOCERR(9), "I don't see any %s here.",
+ namebuf);
+
+ /* error - abort */
+ retval = 1;
+ goto done;
+ }
+ else if (!try_unknown_verb(
+ ctx, cmdActor,
+ &cmd[first_word], &typelist[first_word],
+ 0, &next_start, TRUE, VOCERR(19),
+ "There are words after your command that I couldn't use."))
+ {
+ /* error - abort */
+ retval = 1;
+ goto done;
+ }
+ else
+ {
+ /* continue with the next command */
+ continue;
+ }
+ }
+ }
+ else if (cnt < 0)
+ {
+ /*
+ * the noun phrase syntax was invalid - we've already
+ * displayed an error about it, so simply return failure
+ */
+ retval = 1;
+ goto done;
+ }
+ else if (no_match)
+ {
+ /*
+ * we found a valid noun phrase, but we didn't find any
+ * matching objects - we've already generated an error,
+ * so simply return failure
+ */
+ retval = 1;
+ goto done;
+ }
+ else
+ {
+ goto bad_sentence;
+ }
+ }
+ else if ((cnt = vocchknoun(ctx, cmd, typelist, cur,
+ &next, iolist, FALSE)) > 0)
+ {
+ /* look for prep at end of command */
+ cur = next;
+ if (cmd[cur])
+ {
+ if ((typelist[cur] & VOCT_PREP) &&
+ vocffw(ctx, vverb, vvlen, cmd[cur],
+ (int)strlen(cmd[cur]), PRP_VERB,
+ (vocseadef *)0) && !vprep)
+ {
+ vprep = cmd[cur++];
+ }
+ }
+
+ /* the command should definitely be done now */
+ if (cmd[cur] != 0)
+ {
+ /* let parseUnknownVerb handle it */
+ if (!try_unknown_verb(ctx, cmdActor, &cmd[first_word],
+ &typelist[first_word], 0,
+ &next_start, TRUE, VOCERR(21),
+ "There appear to be extra words after your command."))
+ {
+ /* error - stop the command */
+ retval = 1;
+ goto done;
+ }
+ else
+ {
+ /* go on to the next command */
+ continue;
+ }
+ }
+
+ /*
+ * If we don't have a preposition yet, we need to find the
+ * verb's default. If the verb object has a nilPrep
+ * property defined, use that prep object; otherwise, look
+ * up the word "to" and use that.
+ */
+ if (cmdPrep == MCMONINV &&
+ (v = vocffw(ctx, vverb, vvlen,
+ vprep, (vprep ? (int)strlen(vprep) : 0),
+ PRP_VERB, (vocseadef *)0)) != 0)
+ {
+ runppr(ctx->voccxrun, v->vocwobj, PRP_NILPREP, 0);
+ if (runtostyp(ctx->voccxrun) == DAT_OBJECT)
+ cmdPrep = runpopobj(ctx->voccxrun);
+ else
+ rundisc(ctx->voccxrun);
+ }
+
+ /* if we didn't find anything with nilPrep, find "to" */
+ if (cmdPrep == MCMONINV)
+ {
+ v = vocffw(ctx, "to", 2, (char *)0, 0, PRP_PREP,
+ (vocseadef *)0);
+ if (v) cmdPrep = v->vocwobj;
+ }
+
+ /* execute the command */
+ err = execmd(ctx, cmdActor, cmdPrep, vverb, vprep,
+ iolist, dolist,
+ &cmd[first_word], &typelist[first_word], cmdbuf,
+ cur - first_word, &preparse_list, &next_start);
+ continue;
+ }
+ else if (cnt < 0)
+ {
+ retval = 1;
+ goto done;
+ }
+ else
+ {
+ goto bad_sentence;
+ }
+ }
+
+done:
+ /* copy back the command if we need to redo */
+ if (ctx->voccxredo && cmdbuf != origcmdbuf)
+ strcpy(origcmdbuf, cmdbuf);
+
+ /* return the status */
+ VOC_RETVAL(ctx, save_sp, retval);
+}
+
+/* execute a player command */
+int voccmd(voccxdef *ctx, char *cmd, uint cmdlen)
+{
+ int wrdcnt;
+ int cur = 0;
+ int next;
+ char *buffer;
+ char **wordlist;
+ objnum cmdActor;
+ int first;
+
+ /*
+ * Make sure the stack is set up, resetting the stack on entry. Note
+ * that this makes this routine non-reentrant - recursively calling
+ * this routine will wipe out the enclosing caller's stack.
+ */
+ voc_stk_ini(ctx, (uint)VOC_STACK_SIZE);
+
+ /* allocate our stack arrays */
+ VOC_STK_ARRAY(ctx, char, buffer, 2*VOCBUFSIZ);
+ VOC_STK_ARRAY(ctx, char *, wordlist, VOCBUFSIZ);
+
+ /* until further notice, the actor for formatStrings is Me */
+ tiosetactor(ctx->voccxtio, ctx->voccxme);
+
+ /* clear the 'ignore' flag */
+ ctx->voccxflg &= ~VOCCXFCLEAR;
+
+ /* send to game function 'preparse' */
+ if (ctx->voccxpre != MCMONINV)
+ {
+ int typ;
+ uchar *s;
+ size_t len;
+ int err;
+
+ /* push the argument string */
+ runpstr(ctx->voccxrun, cmd, (int)strlen(cmd), 0);
+
+ /* presume no error will occur */
+ err = 0;
+
+ /* catch errors that occur during preparse() */
+ ERRBEGIN(ctx->voccxerr)
+ {
+ /* call preparse() */
+ runfn(ctx->voccxrun, ctx->voccxpre, 1);
+ }
+ ERRCATCH(ctx->voccxerr, err)
+ {
+ /* see what we have */
+ switch(err)
+ {
+ case ERR_RUNEXIT:
+ case ERR_RUNEXITOBJ:
+ case ERR_RUNABRT:
+ /*
+ * if we encountered abort, exit, or exitobj, treat it
+ * the same as a nil result code - simply cancel the
+ * entire current command
+ */
+ break;
+
+ default:
+ /* re-throw any other error */
+ errrse(ctx->voccxerr);
+ }
+ }
+ ERREND(ctx->voccxerr);
+
+ /* if an error occurred, abort the command */
+ if (err != 0)
+ return 0;
+
+ /* check the return value's type */
+ typ = runtostyp(ctx->voccxrun);
+ if (typ == DAT_SSTRING)
+ {
+ /*
+ * It's a string - replace the command with the new string.
+ * Pop the new string and scan its length prefix.
+ */
+ s = runpopstr(ctx->voccxrun);
+ len = osrp2(s) - 2;
+ s += 2;
+
+ /*
+ * limit the size of the command to our buffer length,
+ * leaving space for null termination
+ */
+ if (len > cmdlen - 1)
+ len = cmdlen - 1;
+
+ /* copy the new command string into our command buffer */
+ memcpy(cmd, s, len);
+
+ /* null-terminate the command buffer */
+ cmd[len] = '\0';
+ }
+ else
+ {
+ /* the value isn't important for any other type */
+ rundisc(ctx->voccxrun);
+
+ /* if they returned nil, we're done processing the command */
+ if (typ == DAT_NIL)
+ return 0;
+ }
+ }
+
+ /* break up into individual words */
+ if ((wrdcnt = voctok(ctx, cmd, buffer, wordlist, TRUE, FALSE, TRUE)) > 0)
+ {
+ /* skip any leading "and" and "then" separators */
+ for (cur = 0 ; cur < wrdcnt ; ++cur)
+ {
+ /* if this isn't "and" or "then", we're done scanning */
+ if (!vocspec(wordlist[cur], VOCW_THEN)
+ && !vocspec(wordlist[cur], VOCW_AND))
+ break;
+ }
+ }
+
+ /*
+ * if we found no words, or we found only useless leading "and" and
+ * "then" separators, run the "pardon" function to tell the player
+ * that we didn't find any command on the line
+ */
+ if (wrdcnt == 0 || (wrdcnt > 0 && cur >= wrdcnt))
+ {
+ runfn(ctx->voccxrun, ctx->voccxprd, 0);
+ return 0;
+ }
+
+ /*
+ * if we got an error tokenizing the word list, return - we've
+ * already displayed an error message, so there's nothing more for
+ * us to do here
+ */
+ if (wrdcnt < 0)
+ return 0;
+
+ /* process the commands on the line */
+ for (first = TRUE, cmdActor = ctx->voccxactor = MCMONINV ; cur < wrdcnt ;
+ ++cur, first = FALSE)
+ {
+ /* presume we won't find an unknown word */
+ ctx->voccxunknown = 0;
+
+ /* find the THEN that ends the command, if there is one */
+ for (next = cur ; cur < wrdcnt && !vocspec(wordlist[cur], VOCW_THEN)
+ ; ++cur) ;
+ wordlist[cur] = (char *)0;
+
+ /* process until we run out of work to do */
+ for (;;)
+ {
+ /* try processing the command */
+ if (voc1cmd(ctx, &wordlist[next], cmd, &cmdActor, first))
+ {
+ /*
+ * If the unknown word flag is set, try the command
+ * again from the beginning. This flag means that we
+ * tried using parseUnknownDobj/Iobj to resolve the
+ * unknown word, but that failed and we need to try
+ * again with the normal "oops" processing.
+ */
+ if (ctx->voccxunknown > 0)
+ continue;
+
+ /* return an error */
+ return 1;
+ }
+
+ /* success - we're done with the command */
+ break;
+ }
+
+ /* if the rest of the command is to be ignored, we're done */
+ if (ctx->voccxflg & VOCCXFCLEAR)
+ return 0;
+
+ /* scan past any separating AND's and THEN's */
+ while (cur + 1 < wrdcnt
+ && (vocspec(wordlist[cur+1], VOCW_THEN)
+ || vocspec(wordlist[cur+1], VOCW_AND)))
+ ++cur;
+
+ /*
+ * if another command follows, add a blank line to separate the
+ * results from the previous command and those from the next
+ * command
+ */
+ if (cur + 1 < wrdcnt)
+ outformat("\\b");
+ }
+
+ /* done */
+ return 0;
+}
+
+
+/*
+ * Off-stack stack management
+ */
+
+/* allocate/reset the stack */
+void voc_stk_ini(voccxdef *ctx, uint siz)
+{
+ /* allocate it if it's not already allocated */
+ if (ctx->voc_stk_ptr == 0)
+ {
+ ctx->voc_stk_ptr = mchalo(ctx->voccxerr, siz, "voc_stk_ini");
+ ctx->voc_stk_end = ctx->voc_stk_ptr + siz;
+ }
+
+ /* reset the stack */
+ ctx->voc_stk_cur = ctx->voc_stk_ptr;
+}
+
+/* allocate space from the off-stack stack */
+void *voc_stk_alo(voccxdef *ctx, uint siz)
+{
+ void *ret;
+
+ /* round size up to allocation units */
+ siz = osrndsz(siz);
+
+ /* if there's not space, signal an error */
+ if (ctx->voc_stk_cur + siz > ctx->voc_stk_end)
+ errsig(ctx->voccxerr, ERR_VOCSTK);
+
+ /* save the return pointer */
+ ret = ctx->voc_stk_cur;
+
+ /* consume the space */
+ ctx->voc_stk_cur += siz;
+
+/*#define SHOW_HI*/
+#ifdef SHOW_HI
+{
+ static uint maxsiz;
+ if (ctx->voc_stk_cur - ctx->voc_stk_ptr > maxsiz)
+ {
+ char buf[20];
+
+ maxsiz = ctx->voc_stk_cur - ctx->voc_stk_ptr;
+ sprintf(buf, "%u\n", maxsiz);
+ os_printz(buf);
+ }
+}
+#endif
+
+
+ /* return the space */
+ return ret;
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk