diff options
author | Paul Gilbert | 2019-05-18 13:26:57 -1000 |
---|---|---|
committer | Paul Gilbert | 2019-05-24 18:21:06 -0700 |
commit | 3d9e03af554814bee10f112c2efa0b7f0b722489 (patch) | |
tree | f174c8004d0c554dae124df63d874f78d2e88e5d /engines/glk/tads | |
parent | fcb2592ec24f983c49f1e2e1b4f41cc9659a9229 (diff) | |
download | scummvm-rg350-3d9e03af554814bee10f112c2efa0b7f0b722489.tar.gz scummvm-rg350-3d9e03af554814bee10f112c2efa0b7f0b722489.tar.bz2 scummvm-rg350-3d9e03af554814bee10f112c2efa0b7f0b722489.zip |
GLK: TADS2: Soooo much more implementation
Diffstat (limited to 'engines/glk/tads')
42 files changed, 20671 insertions, 6269 deletions
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 |