From 0279143a62c2cdab635894103da03b43eba7cf9c Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Fri, 17 May 2019 10:04:03 -1000 Subject: GLK: TADS2: Adding headers --- engines/glk/tads/os_filetype.h | 64 + engines/glk/tads/osfrobtads.cpp | 42 + engines/glk/tads/osfrobtads.h | 267 ++ engines/glk/tads/tads2/appctx.h | 248 ++ engines/glk/tads/tads2/built_in.h | 216 + engines/glk/tads/tads2/character_map.cpp | 339 ++ engines/glk/tads/tads2/character_map.h | 131 + engines/glk/tads/tads2/data.cpp | 3 +- engines/glk/tads/tads2/data.h | 1 + engines/glk/tads/tads2/debug.h | 589 +++ engines/glk/tads/tads2/error.cpp | 32 + engines/glk/tads/tads2/error.h | 390 ++ engines/glk/tads/tads2/error_handling.cpp | 32 + engines/glk/tads/tads2/error_handling.h | 338 ++ engines/glk/tads/tads2/file_io.cpp | 32 + engines/glk/tads/tads2/file_io.h | 137 + engines/glk/tads/tads2/ler.cpp | 152 - engines/glk/tads/tads2/ler.h | 617 --- engines/glk/tads/tads2/lib.h | 164 + engines/glk/tads/tads2/line_source.h | 129 + engines/glk/tads/tads2/memory_cache.cpp | 31 + engines/glk/tads/tads2/memory_cache.h | 403 ++ engines/glk/tads/tads2/memory_cache_heap.cpp | 51 + engines/glk/tads/tads2/memory_cache_heap.h | 60 + engines/glk/tads/tads2/memory_cache_loader.cpp | 31 + engines/glk/tads/tads2/memory_cache_loader.h | 57 + engines/glk/tads/tads2/memory_cache_swap.cpp | 31 + engines/glk/tads/tads2/memory_cache_swap.h | 122 + engines/glk/tads/tads2/object.cpp | 35 + engines/glk/tads/tads2/object.h | 396 ++ engines/glk/tads/tads2/opcode_defs.h | 218 + engines/glk/tads/tads2/os.cpp | 2 + engines/glk/tads/tads2/os.h | 5324 +++++++++++++++++++++++- engines/glk/tads/tads2/post_compilation.h | 96 + engines/glk/tads/tads2/property.h | 167 + engines/glk/tads/tads2/regex.cpp | 2481 ++++++----- engines/glk/tads/tads2/regex.h | 378 +- engines/glk/tads/tads2/run.cpp | 52 + engines/glk/tads/tads2/run.h | 393 ++ engines/glk/tads/tads2/tads2.cpp | 523 ++- engines/glk/tads/tads2/tads2.h | 123 +- engines/glk/tads/tads2/tads2_cmap.cpp | 268 -- engines/glk/tads/tads2/text_io.h | 233 ++ engines/glk/tads/tads2/tokenizer.cpp | 33 + engines/glk/tads/tads2/tokenizer.h | 473 +++ engines/glk/tads/tads2/types.h | 190 - engines/glk/tads/tads2/vocabulary.cpp | 1 + engines/glk/tads/tads2/vocabulary.h | 763 +++- 48 files changed, 13976 insertions(+), 2882 deletions(-) create mode 100644 engines/glk/tads/os_filetype.h create mode 100644 engines/glk/tads/osfrobtads.cpp create mode 100644 engines/glk/tads/osfrobtads.h create mode 100644 engines/glk/tads/tads2/appctx.h create mode 100644 engines/glk/tads/tads2/built_in.h create mode 100644 engines/glk/tads/tads2/character_map.cpp create mode 100644 engines/glk/tads/tads2/character_map.h create mode 100644 engines/glk/tads/tads2/debug.h create mode 100644 engines/glk/tads/tads2/error.cpp create mode 100644 engines/glk/tads/tads2/error.h create mode 100644 engines/glk/tads/tads2/error_handling.cpp create mode 100644 engines/glk/tads/tads2/error_handling.h create mode 100644 engines/glk/tads/tads2/file_io.cpp create mode 100644 engines/glk/tads/tads2/file_io.h delete mode 100644 engines/glk/tads/tads2/ler.cpp delete mode 100644 engines/glk/tads/tads2/ler.h create mode 100644 engines/glk/tads/tads2/lib.h create mode 100644 engines/glk/tads/tads2/line_source.h create mode 100644 engines/glk/tads/tads2/memory_cache.cpp create mode 100644 engines/glk/tads/tads2/memory_cache.h create mode 100644 engines/glk/tads/tads2/memory_cache_heap.cpp create mode 100644 engines/glk/tads/tads2/memory_cache_heap.h create mode 100644 engines/glk/tads/tads2/memory_cache_loader.cpp create mode 100644 engines/glk/tads/tads2/memory_cache_loader.h create mode 100644 engines/glk/tads/tads2/memory_cache_swap.cpp create mode 100644 engines/glk/tads/tads2/memory_cache_swap.h create mode 100644 engines/glk/tads/tads2/object.cpp create mode 100644 engines/glk/tads/tads2/object.h create mode 100644 engines/glk/tads/tads2/opcode_defs.h create mode 100644 engines/glk/tads/tads2/post_compilation.h create mode 100644 engines/glk/tads/tads2/property.h create mode 100644 engines/glk/tads/tads2/run.cpp create mode 100644 engines/glk/tads/tads2/run.h delete mode 100644 engines/glk/tads/tads2/tads2_cmap.cpp create mode 100644 engines/glk/tads/tads2/text_io.h create mode 100644 engines/glk/tads/tads2/tokenizer.cpp create mode 100644 engines/glk/tads/tads2/tokenizer.h delete mode 100644 engines/glk/tads/tads2/types.h (limited to 'engines/glk/tads') diff --git a/engines/glk/tads/os_filetype.h b/engines/glk/tads/os_filetype.h new file mode 100644 index 0000000000..93b101698f --- /dev/null +++ b/engines/glk/tads/os_filetype.h @@ -0,0 +1,64 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* TADS OS interface file type definitions + * + * Defines certain datatypes used in the TADS operating system interface + */ + +#ifndef GLK_TADS_OS_DATATYPE +#define GLK_TADS_OS_DATATYPE + +namespace Glk { +namespace TADS { + +/** + * File types. These type codes are used when opening or creating a + * file, so that the OS routine can set appropriate file system metadata + * to describe or find the file type. + * + * The type os_filetype_t is defined for documentary purposes; it's + * always just an int. + */ +enum os_filetype_t { + OSFTGAME = 0, /* a game data file (.gam) */ + OSFTSAVE = 1, /* a saved game (.sav) */ + OSFTLOG = 2, /* a transcript (log) file */ + OSFTSWAP = 3, /* swap file */ + OSFTDATA = 4, /* user data file (used with the TADS fopen() call) */ + OSFTCMD = 5, /* QA command/log file */ + OSFTERRS = 6, /* error message file */ + OSFTTEXT = 7, /* text file - used for source files */ + OSFTBIN = 8, /* binary file of unknown type - resources, etc */ + OSFTCMAP = 9, /* character mapping file */ + OSFTPREF = 10, /* preferences file */ + OSFTUNK = 11, /* unknown - as a filter, matches any file type */ + OSFTT3IMG = 12, /* T3 image file (.t3 - formerly .t3x) */ + OSFTT3OBJ = 13, /* T3 object file (.t3o) */ + OSFTT3SYM = 14, /* T3 symbol export file (.t3s) */ + OSFTT3SAV = 15, /* T3 saved state file (.t3v) */ +}; + +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/osfrobtads.cpp b/engines/glk/tads/osfrobtads.cpp new file mode 100644 index 0000000000..fbcf1c1050 --- /dev/null +++ b/engines/glk/tads/osfrobtads.cpp @@ -0,0 +1,42 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/osfrobtads.h" +#include "common/file.h" + +namespace Glk { +namespace TADS { + +osfildef *osfoprb(const char *fname, os_filetype_t typ) { + Common::File f; + if (f.open(fname)) + return f.readStream(f.size()); + else + return nullptr; +} + +int osfrb(osfildef *fp, void *buf, size_t count) { + return fp->read(buf, count); +} + +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/osfrobtads.h b/engines/glk/tads/osfrobtads.h new file mode 100644 index 0000000000..2dce84c5ec --- /dev/null +++ b/engines/glk/tads/osfrobtads.h @@ -0,0 +1,267 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* OS-layer functions and macros. + * + * This file does not introduce any curses (or other screen-API) + * dependencies; it can be used for both the interpreter as well as the + * compiler. + */ + +#ifndef GLK_TADS_OSFROBTADS +#define GLK_TADS_OSFROBTADS + +#include "common/fs.h" +#include "common/stream.h" +#include "glk/glk_api.h" +#include "glk/tads/os_filetype.h" + +namespace Glk { +namespace TADS { + + +/* Defined for Gargoyle. */ +#define HAVE_STDINT_ + +#if 0 +#include "common.h" +#endif + +/* Used by the base code to inhibit "unused parameter" compiler warnings. */ +#ifndef VARUSED +#define VARUSED(var) (void)var +#endif + +/* We assume that the C-compiler is mostly ANSI compatible. */ +#define OSANSI + +/* Special function qualifier needed for certain types of callback + * functions. This is for old 16-bit systems; we don't need it and + * define it to nothing. */ +#define OS_LOADDS + +/* Unices don't suffer the near/far pointers brain damage (thank God) so + * we make this a do-nothing macro. */ +#define osfar_t + +/* This is used to explicitly discard computed values (some compilers + * would otherwise give a warning like "computed value not used" in some + * cases). Casting to void should work on every ANSI-Compiler. */ +#define DISCARD (void) + +/* Copies a struct into another. ANSI C allows the assignment operator + * to be used with structs. */ +#define OSCPYSTRUCT(x,y) ((x)=(y)) + +/* Link error messages into the application. */ +#define ERR_LINK_MESSAGES + +/* Program Exit Codes. */ +#define OSEXSUCC 0 /* Successful completion. */ +#define OSEXFAIL 1 /* Failure. */ + +/* Here we configure the osgen layer; refer to tads2/osgen3.c for more + * information about the meaning of these macros. */ +#define USE_DOSEXT +#define USE_NULLSTYPE + +/* Theoretical maximum osmalloc() size. + * Unix systems have at least a 32-bit memory space. Even on 64-bit + * systems, 2^32 is a good value, so we don't bother trying to find out + * an exact value. */ +#define OSMALMAX 0xffffffffL + +#define OSFNMAX 255 + +/* File handle structure for osfxxx functions. */ +typedef Common::SeekableReadStream osfildef; + +/* Directory handle for searches via os_open_dir() et al. */ +typedef Common::FSNode *osdirhdl_t; + +/* file type/mode bits */ +#define OSFMODE_FILE S_IFREG +#define OSFMODE_DIR S_IFDIR +#define OSFMODE_CHAR S_IFCHR +#define OSFMODE_BLK S_IFBLK +#define OSFMODE_PIPE S_IFIFO +#ifdef S_IFLNK +#define OSFMODE_LINK S_IFLNK +#else +#define OSFMODE_LINK 0 +#endif +#ifdef S_IFSOCK +#define OSFMODE_SOCKET S_IFSOCK +#else +#define OSFMODE_SOCKET 0 +#endif + +/* File attribute bits. */ +#define OSFATTR_HIDDEN 0x0001 +#define OSFATTR_SYSTEM 0x0002 +#define OSFATTR_READ 0x0004 +#define OSFATTR_WRITE 0x0008 + +/* Get a file's stat() type. */ +int osfmode( const char* fname, int follow_links, unsigned long* mode, + unsigned long* attr ); + +#if 0 +/* The maximum width of a line of text. + * + * We ignore this, but the base code needs it defined. If the + * interpreter is run inside a console or terminal with more columns + * than the value defined here, weird things will happen, so we go safe + * and use a large value. */ +#define OS_MAXWIDTH 255 +#endif + +/* Disable the Tads swap file; computers have plenty of RAM these days. + */ +#define OS_DEFAULT_SWAP_ENABLED 0 + +/* TADS 2 macro/function configuration. Modern configurations always + * use the no-macro versions, so these definitions should always be set + * as shown below. */ +#define OS_MCM_NO_MACRO +#define ERR_NO_MACRO + +/* These values are used for the "mode" parameter of osfseek() to + * indicate where to seek in the file. */ +#define OSFSK_SET SEEK_SET /* Set position relative to the start of the file. */ +#define OSFSK_CUR SEEK_CUR /* Set position relative to the current file position. */ +#define OSFSK_END SEEK_END /* Set position relative to the end of the file. */ + + +/* ============= Functions follow ================ */ + +/* Allocate a block of memory of the given size in bytes. */ +#define osmalloc malloc + +/* Free memory previously allocated with osmalloc(). */ +#define osfree free + +/* Reallocate memory previously allocated with osmalloc() or osrealloc(), + * changing the block's size to the given number of bytes. */ +#define osrealloc realloc + +/* Open text file for reading. */ +#define osfoprt(fname,typ) (fopen((fname),"r")) + +/* Open text file for writing. */ +#define osfopwt(fname,typ) (fopen((fname),"w")) + +/* Open text file for reading and writing, keeping the file's existing + * contents if the file already exists or creating a new file if no + * such file exists. */ +osfildef *osfoprwt(const char *fname, os_filetype_t typ); + +/* Open text file for reading/writing. If the file already exists, + * truncate the existing contents. Create a new file if it doesn't + * already exist. */ +#define osfoprwtt(fname,typ) (fopen((fname),"w+")) + +/* Open binary file for writing. */ +#define osfopwb(fname,typ) (fopen((fname),"wb")) + +/* Open source file for reading - use the appropriate text or binary + * mode. */ +#define osfoprs osfoprt + +/* Open binary file for reading. */ +osfildef *osfoprb(const char *fname, os_filetype_t typ); + +/* Open binary file for reading/writing. If the file already exists, + * keep the existing contents. Create a new file if it doesn't already + * exist. */ +osfildef* +osfoprwb( const char* fname, os_filetype_t typ ); + +/* Open binary file for reading/writing. If the file already exists, + * truncate the existing contents. Create a new file if it doesn't + * already exist. */ +#define osfoprwtb(fname,typ) (fopen((fname),"w+b")) + +/* Get a line of text from a text file. */ +#define osfgets fgets + +/* Write a line of text to a text file. */ +#define osfputs fputs + +/* Write bytes to file. */ +#define osfwb(fp,buf,bufl) (fwrite((buf),(bufl),1,(fp))!=1) + +/* Flush buffered writes to a file. */ +#define osfflush fflush + +/* Read bytes from file. */ +int osfrb(osfildef *fp, void *buf, size_t count); + +/* Read bytes from file and return the number of bytes read. */ +#define osfrbc(fp,buf,bufl) (fread((buf),1,(bufl),(fp))) + +/* Get the current seek location in the file. */ +#define osfpos ftell + +/* Seek to a location in the file. */ +#define osfseek fseek + +/* Close a file. */ +#define osfcls delete + +/* Delete a file. */ +#define osfdel remove + +/* Access a file - determine if the file exists. + * + * We map this to the access() function. It should be available in + * virtually every system out there, as it appears in many standards + * (SVID, AT&T, POSIX, X/OPEN, BSD 4.3, DOS, MS Windows, maybe more). */ +#define osfacc(fname) (access((fname), F_OK)) + +/* Rename a file. */ +#define os_rename_file(from, to) (rename(from, to) == 0) + +/* Get a file's stat() type. */ +struct os_file_stat_t; +int os_file_stat( const char* fname, int follow_links, + struct os_file_stat_t* s ); + +/* Get a character from a file. */ +#define osfgetc fgetc + +/* Set busy cursor. + * + * We don't have a mouse cursor so there's no need to implement this. */ +#define os_csr_busy(a) + +/* Update progress display. + * + * We don't provide any kind of "compilation progress display", so we + * just define this as an empty macro. + */ +#define os_progress(fname,linenum) + +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/appctx.h b/engines/glk/tads/tads2/appctx.h new file mode 100644 index 0000000000..3b3f499777 --- /dev/null +++ b/engines/glk/tads/tads2/appctx.h @@ -0,0 +1,248 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_TADS_TADS2_APPCTX +#define GLK_TADS_TADS2_APPCTX + +#include "common/scummsys.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/** + * Application container context. The TADS run-time is a subsystem that + * can be invoked from different types of applications; in fact, even + * when only the standard stand-alone run-time is considered, multiple + * application containers must be supported because of differences + * between operating systems. The application container context is an + * optional mechanism that the main application can use to provide + * structured interaction between itself and the TADS run-time subsystem. + * + * The function pointers contained herein are intended to allow the + * run-time subsystem to call the host system to notify it of certain + * events, or obtain optional services from the host system. Any of + * these function pointers can be null, in which case the run-time + * subsystem will skip calling them. + * + * Note that each function has an associated callback context. This + * allows the host system to recover any necessary context information + * when the callback is invoked. + */ +struct appctxdef { + /** + * Get the .GAM file name. The run-time will call this only if it + * can't find a game file to load through some other means first. + * The run-time determines the game file first by looking at the + * command line, then by checking to see if a .GAM file is attached + * to the executable. If none of these checks yields a game, the + * run-time will call this routine to see if the host system wants + * to provide a game. This routine can be implemented on a GUI + * system, for example, to display a dialog prompting the user to + * select a game file to open. A trivial implementation of this + * routine (that merely returns false) is okay. + * + * This routine should return true (any non-zero value) if it + * provides the name of a file to open, false (zero) if not. + */ + int (*get_game_name)(void *appctxdat, char *buf, size_t buflen); + void *get_game_name_ctx; + + /** + * Set the .GAM file name. When the run-time determines the name of + * the file it will use to read the game, it calls this routine. + * The host system should note the game filename if it will need to + * access the game file itself (for example, to load resources). + */ + void (*set_game_name)(void *appctxdat, const char *fname); + void *set_game_name_ctx; + + /** + * Set the root path for individual resources. By default, we use the + * directory containing the game file, but this can be used to override + * that. + */ + void (*set_res_dir)(void *appctxdat, const char *fname); + void *set_res_dir_ctx; + + /** + * Set the resource map address in the game file. If the .GAM + * reader encounters a resource map in the file, it calls this + * routine with the seek offset of the first resource. Each + * resource's address is given as an offset from this point. + * + * fileno is the file number assigned by the host system in + * add_resfile. File number zero is always the .GAM file. + */ + void (*set_resmap_seek)(void *appctxdat, unsigned long seekpos, int fileno); + void *set_resmap_seek_ctx; + + /** + * Add a resource entry. The 'ofs' entry is the byte offset of the + * start of the resource, relative to the seek position previously + * set with set_resmap_seek. 'siz' is the size of the resource in + * bytes; the resource is stored as contiguous bytes starting at the + * given offset for the given size. Note that resources may be + * added before the resource map seek position is set, so the host + * system must simply store the resource information for later use. + * The 'fileno' is zero for the .GAM file, or the number assigned by + * the host system in add_resfile for other resource files. + */ + void (*add_resource)(void *appctxdat, unsigned long ofs, + unsigned long siz, const char *nm, size_t nmlen, + int fileno); + void *add_resource_ctx; + + /** + * Add a resource link entry. 'fname' and 'fnamelen' give the name of + * a local file containing the resource data; 'resname' and + * 'resnamelen' give the name of the resource as it appears within the + * compiled game file. This creates a link from a .GAM resource name + * to a local filename, where the actual binary resource data reside, + * so that we can retrieve a resource by .GAM resource name without + * actually copying the data into the .GAM file. This is used mostly + * for debugging purposes: it allows the compiler to skip the step of + * copying the resource data into the .GAM file, but still allows the + * game to load resources by .GAM resource name, to create a testing + * environment that's consistent with the full build version (where the + * resources would actually be copied). + */ + void (*add_resource_link)(void *appctxdat, + const char *fname, size_t fnamelen, + const char *resname, size_t resnamelen); + void *add_resource_link_ctx; + + /** + * Add a resource path. 'path' is a string giving a directory prefix + * in local system notation. + * + * This adds a directory to the list of directories that we'll search + * when we're looking for an individual resource as an external file + * (such as a ".jpg" image file or ".ogg" sound file). This can be + * called zero or more times; each call adds another directory to + * search after any previous directories. We'll always search the + * default directory first (this is the directory containing the game + * file); then we'll search directories added with this call in the + * order in which the directories were added. + */ + void (*add_res_path)(void *appctxdat, const char *path, size_t len); + void *add_res_path_ctx; + + /** + * Find a resource entry. If the resource can be found, this must + * return an osfildef* handle to the resource, with its seek position + * set to the first byte of the resource data, and set *res_size to + * the size in bytes of the resource data in the file. If the + * resource cannot be found, returns null. + */ + osfildef *(*find_resource)(void *appctxdat, + const char *resname, size_t resnamelen, + unsigned long *res_size); + void *find_resource_ctx; + + /** + * Add a resource file. The return value is a non-zero file number + * assigned by the host system; we'll use this number in subsequent + * calls to add_resource to add the resources from this file. + * + * After calling this routine to add the file, we'll parse the file + * and add any resources using add_resource. + */ + int (*add_resfile)(void *appctxdat, const char *fname); + void *add_resfile_ctx; + + /** + * Determine if a resource exists. Returns true if the resource can + * be loaded, false if not. The resource name is in the standard + * URL-style format. + */ + int (*resfile_exists)(void *appctxdat, const char *res_name, + size_t res_name_len); + void *resfile_exists_ctx; + + /** + * Resource file path. If we should look for resource files in a + * different location than the .GAM file, the host system can set + * this to a path that we should use to look for resource files. If + * it's null, we'll look in the directory that contains the .GAM + * file. Note that if the path is provided, it must be set up with + * a trailing path separator character, so that we can directly + * append a name to this path to form a valid fully-qualified + * filename. + */ + const char *ext_res_path; + + /** + * File safety level get/set. During initialization, we'll call the + * host system to tell it the file safety level selected by the user on + * the command line; if the host system is saving preference + * information, it should temporarily override its saved preferences + * and use the command line setting (and it may, if appropriate, want + * to save the command line setting as the saved preference setting, + * depending on how it handles preferences). During execution, any + * time the game tries to open a file (using the fopen built-in + * function), we'll call the host system to ask it for the current + * setting, and use this new setting rather than the original command + * line setting. + * + * Refer to bif.c for information on the meanings of the file safety + * levels. + */ + void (*set_io_safety_level)(void *ctx, int read, int write); + void (*get_io_safety_level)(void *ctx, int *read, int *write); + void *io_safety_level_ctx; + + /** + * Network safety level get/set. This is analogous to the file safety + * level scheme, but controls access to network resources. There are + * two components to the network safety setting: client and server. + * The client component controls the game's ability to open network + * connections to access information on remote machines, such as + * opening http connections to access web sites. The server component + * controls the game's ability to create servers of its own and accept + * incoming connections. Each component can be set to one of the + * following: + * + *. 0 = no restrictions (least "safety"): all network access granted + *. 1 = 'localhost' access only + *. 2 = no network access + * + * This only applies to the TADS 3 VM. TADS 2 doesn't support any + * network features, so this doesn't apply. + */ + void (*set_net_safety_level)(void *ctx, int client_level, int srv_level); + void (*get_net_safety_level)(void *ctx, int *client_level, int *srv_level); + void *net_safety_level_ctx; + + /** + * Name of run-time application for usage messages. If this is + * null, the default run-time application name will be used for + * usage messages. + */ + const char *usage_app_name; +}; + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/built_in.h b/engines/glk/tads/tads2/built_in.h new file mode 100644 index 0000000000..f5807e61a8 --- /dev/null +++ b/engines/glk/tads/tads2/built_in.h @@ -0,0 +1,216 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* Built-in functions interface + * + * Interface to run-time intrinsic function implementation + */ + +#ifndef GLK_TADS_TADS2_BUILT_IN +#define GLK_TADS_TADS2_BUILT_IN + +#include "glk/tads/tads2/error.h" +#include "glk/tads/tads2/run.h" +#include "glk/tads/tads2/text_io.h" +#include "glk/tads/tads2/regex.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* forward definitions */ +struct vocidef; +struct voccxdef; + +/* maximum number of file handles available */ +#define BIFFILMAX 10 + + +/* file contexts for the built-in file handling functions */ +struct biffildef { + osfildef *fp; /* underyling system file handle */ + uint flags; /* flags */ +#define BIFFIL_F_BINARY 0x01 /* file is binary */ +}; + +/* built-in execution context */ +struct bifcxdef { + errcxdef *bifcxerr; /* error-handling context */ + runcxdef *bifcxrun; /* code execution context */ + tiocxdef *bifcxtio; /* text I/O context */ + long bifcxrnd; /* random number seed */ + int bifcxseed1; /* first seed for new generator */ + int bifcxseed2; /* second seed for new generator */ + int bifcxseed3; /* third seed for new generator */ + int bifcxrndset; /* randomize() has been called */ + biffildef bifcxfile[BIFFILMAX]; /* file handles for fopen, etc */ + int bifcxsafetyr; /* file I/O safety level - read */ + int bifcxsafetyw; /* file I/O safety level - write */ + char *bifcxsavext; /* saved game extension (null by default) */ + appctxdef *bifcxappctx; /* host application context */ + re_context bifcxregex; /* regular expression searching context */ + + bifcxdef() : bifcxerr(nullptr), bifcxrun(nullptr), bifcxtio(nullptr), + bifcxrnd(0), bifcxseed1(0), bifcxseed2(0), bifcxseed3(0), bifcxrndset(0), + bifcxsafetyr(0), bifcxsafetyw(0), bifcxsavext(nullptr), bifcxappctx(nullptr) { + } +}; + +/* + * argument list checking routines - can be disabled for faster + * run-time + */ + +/* check for proper number of arguments */ +/* void bifcntargs(bifcxdef *ctx, int argcnt) */ + +/* check that next argument has proper type */ +/* void bifchkarg(bifcxdef *ctx, dattyp typ); */ + +#ifdef RUNFAST +# define bifcntargs(ctx, parmcnt, argcnt) +# define bifchkarg(ctx, typ) +#else /* RUNFAST */ +# define bifcntargs(ctx, parmcnt, argcnt) \ + (parmcnt == argcnt ? DISCARD 0 : \ + (runsig(ctx->bifcxrun, ERR_BIFARGC), DISCARD 0)) +# define bifchkarg(ctx, typ) \ + (runtostyp(ctx->bifcxrun) == typ ? DISCARD 0 : \ + (runsig(ctx->bifcxrun, ERR_INVTBIF), DISCARD 0)) +#endif /* RUNFAST */ + +/* determine if one object is a subclass of another */ +int bifinh(struct voccxdef *voc, struct vocidef *v, objnum cls); + + +/* enumerate the built-in functions */ +void bifyon(bifcxdef *ctx, int argc); /* yorn - yes or no */ +void bifsfs(bifcxdef *ctx, int argc); /* setfuse */ +void bifrfs(bifcxdef *ctx, int argc); /* remfuse */ +void bifsdm(bifcxdef *ctx, int argc); /* setdaemon */ +void bifrdm(bifcxdef *ctx, int argc); /* remdaemon */ +void bifinc(bifcxdef *ctx, int argc); /* incturn */ +void bifqui(bifcxdef *ctx, int argc); /* quit */ +void bifsav(bifcxdef *ctx, int argc); /* save */ +void bifrso(bifcxdef *ctx, int argc); /* restore */ +void biflog(bifcxdef *ctx, int argc); /* logging */ +void bifres(bifcxdef *ctx, int argc); /* restart */ +void bifinp(bifcxdef *ctx, int argc); /* input - get line from kb */ +void bifnfy(bifcxdef *ctx, int argc); /* notify */ +void bifunn(bifcxdef *ctx, int argc); /* unnotify */ +void biftrc(bifcxdef *ctx, int argc); /* trace on/off */ +void bifsay(bifcxdef *ctx, int argc); /* say */ +void bifcar(bifcxdef *ctx, int argc); /* car */ +void bifcdr(bifcxdef *ctx, int argc); /* cdr */ +void bifcap(bifcxdef *ctx, int argc); /* caps */ +void biflen(bifcxdef *ctx, int argc); /* length */ +void biffnd(bifcxdef *ctx, int argc); /* find */ +void bifsit(bifcxdef *ctx, int argc); /* setit - set current 'it' */ +void bifsrn(bifcxdef *ctx, int argc); /* randomize: seed rand */ +void bifrnd(bifcxdef *ctx, int argc); /* rand - get a random number */ +void bifask(bifcxdef *ctx, int argc); /* askfile */ +void bifssc(bifcxdef *ctx, int argc); /* setscore */ +void bifsub(bifcxdef *ctx, int argc); /* substr */ +void bifcvs(bifcxdef *ctx, int argc); /* cvtstr: convert to string */ +void bifcvn(bifcxdef *ctx, int argc); /* cvtnum: convert to number */ +void bifupr(bifcxdef *ctx, int argc); /* upper */ +void biflwr(bifcxdef *ctx, int argc); /* lower */ +void biffob(bifcxdef *ctx, int argc); /* firstobj */ +void bifnob(bifcxdef *ctx, int argc); /* nextobj */ +void bifsvn(bifcxdef *ctx, int argc); /* setversion */ +void bifarg(bifcxdef *ctx, int argc); /* getarg */ +void biftyp(bifcxdef *ctx, int argc); /* datatype */ +void bifisc(bifcxdef *ctx, int argc); /* isclass */ +void bifund(bifcxdef *ctx, int argc); /* undo */ +void bifdef(bifcxdef *ctx, int argc); /* defined */ +void bifpty(bifcxdef *ctx, int argc); /* proptype */ +void bifoph(bifcxdef *ctx, int argc); /* outhide */ +void bifgfu(bifcxdef *ctx, int argc); /* getfuse */ +void bifruf(bifcxdef *ctx, int argc); /* runfuses */ +void bifrud(bifcxdef *ctx, int argc); /* rundaemons */ +void biftim(bifcxdef *ctx, int argc); /* gettime */ +void bifsct(bifcxdef *ctx, int argc); /* intersect */ +void bifink(bifcxdef *ctx, int argc); /* inputkey */ +void bifwrd(bifcxdef *ctx, int argc); /* objwords */ +void bifadw(bifcxdef *ctx, int argc); /* addword */ +void bifdlw(bifcxdef *ctx, int argc); /* delword */ +void bifgtw(bifcxdef *ctx, int argc); /* getwords */ +void bifnoc(bifcxdef *ctx, int argc); /* nocaps */ +void bifskt(bifcxdef *ctx, int argc); /* skipturn */ +void bifcls(bifcxdef *ctx, int argc); /* clearscreen */ +void bif1sc(bifcxdef *ctx, int argc); /* firstsc */ +void bifvin(bifcxdef *ctx, int argc); /* verbinfo */ +void bifcapture(bifcxdef *ctx, int argc); /* outcapture */ + +void biffopen(bifcxdef *ctx, int argc); /* fopen */ +void biffclose(bifcxdef *ctx, int argc); /* fclose */ +void biffwrite(bifcxdef *ctx, int argc); /* fwrite */ +void biffread(bifcxdef *ctx, int argc); /* fread */ +void biffseek(bifcxdef *ctx, int argc); /* fseek */ +void biffseekeof(bifcxdef *ctx, int argc); /* fseekeof */ +void bifftell(bifcxdef *ctx, int argc); /* ftell */ + +void bifsysinfo(bifcxdef *ctx, int argc); /* systemInfo */ +void bifmore(bifcxdef *ctx, int argc); /* morePrompt */ +void bifsetme(bifcxdef *ctx, int argc); /* parserSetMe */ +void bifgetme(bifcxdef *ctx, int argc); /* parserGetMe */ + +void bifresearch(bifcxdef *ctx, int argc); /* reSearch */ +void bifregroup(bifcxdef *ctx, int argc); /* reGetGroup */ + +void bifinpevt(bifcxdef *ctx, int argc); /* inputevent */ +void bifdelay(bifcxdef *ctx, int argc); /* timeDelay */ + +void bifsetoutfilter(bifcxdef *ctx, int argc); /* setOutputFilter */ +void bifexec(bifcxdef *ctx, int argc); /* execCommand */ +void bifgetobj(bifcxdef *ctx, int argc); /* parserGetObj */ +void bifparsenl(bifcxdef *ctx, int argc); /* parserParseNounList */ +void bifprstok(bifcxdef *ctx, int argc); /* parserTokenize */ +void bifprstoktyp(bifcxdef *ctx, int argc); /* parserGetTokTypes */ +void bifprsdict(bifcxdef *ctx, int argc); /* parserDictLookup */ +void bifprsrslv(bifcxdef *ctx, int argc); /* parserResolveObjects */ +void bifprsrplcmd(bifcxdef *ctx, int argc); /* parserReplaceCommand */ +void bifexitobj(bifcxdef *ctx, int argc); /* exitobj */ +void bifinpdlg(bifcxdef *ctx, int argc); /* inputdialog */ +void bifresexists(bifcxdef *ctx, int argc); /* resourceExists */ + +/* + * TADS/graphic functions - these are present in the text system, but + * don't do anything. + */ +void bifgrp(bifcxdef *ctx, int argc); /* g_readpic: read picture */ +void bifgsp(bifcxdef *ctx, int argc); /* g_showpic: show picture */ +void bifgsh(bifcxdef *ctx, int argc); /* g_sethot: set hot list */ +void bifgin(bifcxdef *ctx, int argc); /* g_inventory */ +void bifgco(bifcxdef *ctx, int argc); /* g_compass */ +void bifgov(bifcxdef *ctx, int argc); /* g_overlay */ +void bifgmd(bifcxdef *ctx, int argc); /* g_mode */ +void bifgmu(bifcxdef *ctx, int argc); /* g_music */ +void bifgpa(bifcxdef *ctx, int argc); /* g_pause */ +void bifgef(bifcxdef *ctx, int argc); /* g_effect */ +void bifgsn(bifcxdef *ctx, int argc); /* g_sound */ + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/character_map.cpp b/engines/glk/tads/tads2/character_map.cpp new file mode 100644 index 0000000000..91daa8668c --- /dev/null +++ b/engines/glk/tads/tads2/character_map.cpp @@ -0,0 +1,339 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/tads2/character_map.h" +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/error.h" +#include "glk/tads/tads2/os.h" +#include "glk/tads/tads2/text_io.h" +#include "glk/tads/osfrobtads.h" +#include "common/algorithm.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* ------------------------------------------------------------------------ */ +/* + * Global variables for character mapping tables + */ + +unsigned char G_cmap_input[256]; +unsigned char G_cmap_output[256]; +char G_cmap_id[5]; +char G_cmap_ldesc[CMAP_LDESC_MAX_LEN + 1]; + + +/* + * static variables + */ + +/* + * flag: true -> a character set has been explicitly loaded, so we + * should ignore any game character set setting + */ +static int S_cmap_loaded; + + +/* ------------------------------------------------------------------------ */ +/* + * Initialize the default character mappings + */ +void cmap_init_default(void) +{ + size_t i; + + /* initialize the input table */ + for (i = 0 ; i < sizeof(G_cmap_input)/sizeof(G_cmap_input[0]) ; ++i) + G_cmap_input[i] = (unsigned char)i; + + /* initialize the output table */ + for (i = 0 ; i < sizeof(G_cmap_output)/sizeof(G_cmap_output[0]) ; ++i) + G_cmap_output[i] = (unsigned char)i; + + /* we have a null ID */ + memset(G_cmap_id, 0, sizeof(G_cmap_id)); + + /* indicate that it's the default */ + strcpy(G_cmap_ldesc, "(native/no mapping)"); + + /* note that we have no character set loaded */ + S_cmap_loaded = FALSE; +} + +/* ------------------------------------------------------------------------ */ +/* + * Internal routine to load a character map from a file + */ +static int cmap_load_internal(char *filename) +{ + osfildef *fp; + static char sig1[] = CMAP_SIG_S100; + char buf[256]; + uchar lenbuf[2]; + size_t len; + int sysblk; + + /* if there's no mapping file, use the default mapping */ + if (filename == 0) + { + /* initialize with the default mapping */ + cmap_init_default(); + + /* return success */ + return 0; + } + + /* open the file */ + fp = osfoprb(filename, OSFTCMAP); + if (fp == 0) + return 1; + + /* check the signature */ + if (osfrb(fp, buf, sizeof(sig1)) + || memcmp(buf, sig1, sizeof(sig1)) != 0) + { + osfcls(fp); + return 2; + } + + /* load the ID */ + G_cmap_id[4] = '\0'; + if (osfrb(fp, G_cmap_id, 4)) + { + osfcls(fp); + return 3; + } + + /* load the long description */ + if (osfrb(fp, lenbuf, 2) + || (len = osrp2(lenbuf)) > sizeof(G_cmap_ldesc) + || osfrb(fp, G_cmap_ldesc, len)) + { + osfcls(fp); + return 4; + } + + /* load the two tables - input, then output */ + if (osfrb(fp, G_cmap_input, sizeof(G_cmap_input)) + || osfrb(fp, G_cmap_output, sizeof(G_cmap_output))) + { + osfcls(fp); + return 5; + } + + /* read the next section header */ + if (osfrb(fp, buf, 4)) + { + osfcls(fp); + return 6; + } + + /* if it's "SYSI", read the system information string */ + if (!memcmp(buf, "SYSI", 4)) + { + /* read the length prefix, then the string */ + if (osfrb(fp, lenbuf, 2) + || (len = osrp2(lenbuf)) > sizeof(buf) + || osfrb(fp, buf, len)) + { + osfcls(fp); + return 7; + } + + /* we have a system information block */ + sysblk = TRUE; + } + else + { + /* there's no system information block */ + sysblk = FALSE; + } + + /* + * call the OS code, so that it can do any system-dependent + * initialization for the new character mapping + */ + os_advise_load_charmap(G_cmap_id, G_cmap_ldesc, sysblk ? buf : ""); + + /* read the next section header */ + if (sysblk && osfrb(fp, buf, 4)) + { + osfcls(fp); + return 8; + } + + /* see if we have an entity list */ + if (!memcmp(buf, "ENTY", 4)) + { + /* read the entities */ + for (;;) + { + size_t blen; + unsigned int cval; + char expansion[CMAP_MAX_ENTITY_EXPANSION]; + + /* read the next item's length and character value */ + if (osfrb(fp, buf, 4)) + { + osfcls(fp); + return 9; + } + + /* decode the values */ + blen = osrp2(buf); + cval = osrp2(buf+2); + + /* if we've reached the zero marker, we're done */ + if (blen == 0 && cval == 0) + break; + + /* read the string */ + if (blen > CMAP_MAX_ENTITY_EXPANSION + || osfrb(fp, expansion, blen)) + { + osfcls(fp); + return 10; + } + + /* tell the output code about the expansion */ + tio_set_html_expansion(cval, expansion, blen); + } + } + + /* + * ignore anything else we find - if the file format is updated to + * include extra information in the future, and this old code tries + * to load an updated file, we'll just ignore the new information, + * which should always be placed after the "SYSI" block (if present) + * to ensure compatibility with past versions (such as this code) + */ + + /* no problems - close the file and return success */ + osfcls(fp); + return 0; +} + + +/* ------------------------------------------------------------------------ */ +/* + * Explicitly load a character set from a file. This character set + * mapping will override any implicit character set mapping that we read + * from a game file. This should be called when the player explicitly + * loads a character set (via a command line option or similar action). + */ +int cmap_load(char *filename) +{ + int err; + + /* try loading the file */ + if ((err = cmap_load_internal(filename)) != 0) + return err; + + /* + * note that we've explicitly loaded a character set, if they named + * a character set (if not, this simply establishes the default + * setting, so we haven't explicitly loaded anything) + */ + if (filename != 0) + S_cmap_loaded = TRUE; + + /* success */ + return 0; +} + + +/* ------------------------------------------------------------------------ */ +/* + * Explicitly override any game character set and use no character set + * instead. + */ +void cmap_override(void) +{ + /* apply the default mapping */ + cmap_init_default(); + + /* + * pretend we have a character map loaded, so that we don't try to + * load another one if the game specifies a character set + */ + S_cmap_loaded = TRUE; +} + + +/* ------------------------------------------------------------------------ */ +/* + * Set the game's internal character set. This is called when a game is + * loaded, and the game specifies a character set. + */ +void cmap_set_game_charset(errcxdef *ec, + char *internal_id, char *internal_ldesc, + char *argv0) +{ + char filename[OSFNMAX]; + + /* + * If a character set is already explicitly loaded, ignore the + * game's character set - the player asked us to use a particular + * mapping, so ignore what the game wants. (This will probably + * result in incorrect display of non-ASCII character values, but + * the player is most likely to use this to avoid errors when an + * appropriate mapping file for the game is not available. In this + * case, the player informs us by setting the option that he or she + * knows and accepts that the game will not look exactly right.) + */ + if (S_cmap_loaded) + return; + + /* + * ask the operating system to name the mapping file -- this routine + * will determine, if possible, the current native character set, + * and apply a system-specific naming convention to tell us what + * mapping file we should open + */ + os_gen_charmap_filename(filename, internal_id, argv0); + + /* try loading the mapping file */ + if (cmap_load_internal(filename)) + errsig2(ec, ERR_CHRNOFILE, + ERRTSTR, errstr(ec, filename, strlen(filename)), + ERRTSTR, errstr(ec, internal_ldesc, strlen(internal_ldesc))); + + /* + * We were successful - the game's internal character set is now + * mapped to the current native character set. Even though we + * loaded an ldesc from the mapping file, forget that and store the + * internal ldesc that the game specified. The reason we do this is + * that it's possible that the player will dynamically switch native + * character sets in the future, at which point we'll need to + * re-load the mapping table, which could raise an error if a + * mapping file for the new character set isn't available. So, we + * may need to provide the same explanation later that we needed to + * provide here. Save the game's character set ldesc for that + * eventuality, since it describes exactly what the *game* wanted. + */ + strcpy(G_cmap_ldesc, internal_ldesc); +} + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/tads2/character_map.h b/engines/glk/tads/tads2/character_map.h new file mode 100644 index 0000000000..c61406fd27 --- /dev/null +++ b/engines/glk/tads/tads2/character_map.h @@ -0,0 +1,131 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_TADS_CHARACTER_MAP +#define GLK_TADS_CHARACTER_MAP + +namespace Glk { +namespace TADS { +namespace TADS2 { + +struct errcxdef; + +/* ------------------------------------------------------------------------ */ +/* + * Initialize the default character mappings. If no mapping file is to + * be read, this function will establish identify mappings that leave + * characters untranslated. + */ +void cmap_init_default(void); + + +/* + * Load a character map file. Returns zero on success, non-zero on + * failure. If filename is null, we'll use the default mapping. + */ +int cmap_load(char *filename); + + +/* + * Turn off character translation. This overrides any game character + * set that we find and simply uses the default translation. + */ +void cmap_override(void); + + +/* + * Set the game's internal character set. This should be called when a + * game is loaded, and the game specifies an internal character set. If + * there is no character map file explicitly loaded, we will attempt to + * load a character mapping file that maps this character set to the + * current native character set. Signals an error on failure. This + * routine will succeed (without doing anything) if a character set has + * already been explicitly loaded, since an explicitly-loaded character + * set overrides the automatic character set selection that we attempt + * when loading a game. + * + * argv0 must be provided so that we know where to look for our mapping + * file on systems where mapping files are stored in the same directory + * as the TADS executables. + */ +void cmap_set_game_charset(struct errcxdef *errctx, + char *internal_id, char *internal_ldesc, + char *argv0); + + +/* ------------------------------------------------------------------------ */ +/* + * Mapping macros + */ + +/* map a native character (read externally) into an internal character */ +#define cmap_n2i(c) (G_cmap_input[(unsigned char)(c)]) + +/* map an internal character into a native character (for display) */ +#define cmap_i2n(c) (G_cmap_output[(unsigned char)(c)]) + + +/* ------------------------------------------------------------------------ */ +/* + * Global character mapping tables. The character map is established at + * start-up. + */ + +/* + * input-mapping table - for native character 'n', cmap_input[n] yields + * the internal character code + */ +extern unsigned char G_cmap_input[256]; + +/* + * output-mapping table - for internal character 'n', cmap_output[n] + * yields the output character code + */ +extern unsigned char G_cmap_output[256]; + +/* the ID of the loaded character set */ +extern char G_cmap_id[5]; + +/* the full name (for display purposes) of the loaded character set */ +#define CMAP_LDESC_MAX_LEN 40 +extern char G_cmap_ldesc[CMAP_LDESC_MAX_LEN + 1]; + +/* + * Maximum expansion for an HTML entity mapping + */ +#define CMAP_MAX_ENTITY_EXPANSION 50 + + +/* ------------------------------------------------------------------------ */ +/* + * Signatures for character map files. The signature is stored at the + * beginning of the file. + */ + +/* single-byte character map version 1.0.0 */ +#define CMAP_SIG_S100 "TADS2 charmap S100\n\r\01a" + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/data.cpp b/engines/glk/tads/tads2/data.cpp index c60fcb2c5f..b19f45f1fd 100644 --- a/engines/glk/tads/tads2/data.cpp +++ b/engines/glk/tads/tads2/data.cpp @@ -21,7 +21,8 @@ */ #include "glk/tads/tads2/data.h" -#include "glk/tads/tads2/types.h" +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/run.h" #include "glk/tads/tads2/vocabulary.h" #include "common/algorithm.h" diff --git a/engines/glk/tads/tads2/data.h b/engines/glk/tads/tads2/data.h index 8db2153e1f..8d122c444a 100644 --- a/engines/glk/tads/tads2/data.h +++ b/engines/glk/tads/tads2/data.h @@ -47,6 +47,7 @@ enum DataType { DAT_REDIR = 16, ///< redirection to different object DAT_TPL2 = 17 ///< new-style template }; +typedef DataType dattyp; class Data { private: diff --git a/engines/glk/tads/tads2/debug.h b/engines/glk/tads/tads2/debug.h new file mode 100644 index 0000000000..52695cfb86 --- /dev/null +++ b/engines/glk/tads/tads2/debug.h @@ -0,0 +1,589 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_TADS_TADS2_DEBUG +#define GLK_TADS_TADS2_DEBUG + +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/object.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + + +/* forward declarations */ +struct bifcxdef; +struct toksdef; +struct toktdef; +struct tokcxdef; + +/* stack frame record */ +struct dbgfdef +{ + struct runsdef *dbgfbp; /* base pointer of frame */ + objnum dbgfself; /* 'self' object (MCMONINV for functions) */ + objnum dbgftarg; /* actual target object */ + prpnum dbgfprop; /* property being evalutated */ + int dbgfargc; /* number of arguments */ + int dbgfbif; /* set to built-in function number if in built-in */ + uint dbgffr; /* offset in object of local frame symbol table */ + uint dbgflin; /* OPCLINE operand of latest line */ +}; +typedef struct dbgfdef dbgfdef; + +/* max number of frames to store in debug frame memory */ +#define DBGMAXFRAME 100 + +/* maximum number of breakpoints set concurrently */ +#define DBGBPMAX 50 + +/* breakpoint structure */ +struct dbgbpdef +{ + objnum dbgbpself; /* the "self" object for the breakpoint */ + objnum dbgbptarg; /* actual target object for the breakpoint */ + uint dbgbpofs; /* offset in object of the breakpoint */ + uint dbgbpflg; /* breakpoint flags */ +#define DBGBPFUSED 0x01 /* breakpoint has been set */ +#define DBGBPFNAME 0x02 /* name of address has been stored */ +#define DBGBPFCOND 0x04 /* breakpoint has a condition attached */ +#define DBGBPFDISA 0x08 /* breakpoint is disabled */ +#define DBGBPFCONDNAME 0x10 /* condition name string has been stored */ + uint dbgbpnam; /* offset of address name within dbgcxnam buffer */ + uint dbgbpcondnam; /* offset of condition string within buffer */ + objnum dbgbpcond; /* object containing compiled condition for bp */ +}; +typedef struct dbgbpdef dbgbpdef; + +/* maximum number of watch expressions set concurrently */ +#define DBGWXMAX 30 + +/* watch expression structure */ +struct dbgwxdef +{ + objnum dbgwxobj; /* object containing compiled expression */ + objnum dbgwxself; /* 'self' for the expression */ + uint dbgwxnam; /* offset of expression text within dbgcxnam buffer */ + uint dbgwxflg; /* flags for this watch expression slot */ +#define DBGWXFUSED 0x01 /* watch slot is in use */ +#define DBGWXFNAME 0x02 /* name of watch has been stored */ +}; +typedef struct dbgwxdef dbgwxdef; + +/* amount of space for bp names (original address strings from user) */ +#define DBGCXNAMSIZ 2048 + +/* debug context */ +struct dbgcxdef +{ + struct tiocxdef *dbgcxtio; /* text i/o context */ + struct tokthdef *dbgcxtab; /* symbol table */ + struct mcmcxdef *dbgcxmem; /* memory cache manager context */ + struct errcxdef *dbgcxerr; /* error handling context */ + struct lindef *dbgcxlin; /* chain of line sources */ + int dbgcxfcn; /* number of frames in use */ + int dbgcxdep; /* actual depth (if overflow frame buffer) */ + int dbgcxfid; /* source file serial number */ + dbgfdef dbgcxfrm[DBGMAXFRAME]; /* stack frames */ + int dbgcxflg; /* flags for debug session */ +#define DBGCXFSS 0x01 /* single-stepping source lines */ +#define DBGCXFSO 0x02 /* stepping over a function/method call */ +#define DBGCXFOK 0x04 /* debugger is linked in */ +#define DBGCXFIND 0x08 /* in debugger - suppress stack trace on err */ +#define DBGCXFGBP 0x10 /* global breakpoints in effect */ +#define DBGCXFTRC 0x20 /* call tracing activated */ +#define DBGCXFLIN2 0x40 /* new-style line records (line numbers) */ + int dbgcxsof; /* frame depth at step-over time */ + dbgbpdef dbgcxbp[DBGBPMAX]; /* breakpoints */ + dbgwxdef dbgcxwx[DBGWXMAX]; /* watch expressions */ + struct prscxdef *dbgcxprs; /* parsing context */ + struct runcxdef *dbgcxrun; /* execution context */ + uint dbgcxnamf; /* next free byte of dbgcxnam buffer */ + uint dbgcxnams; /* size of dbgcxnam buffer */ + char *dbgcxnam; /* space for bp address names */ + char *dbgcxhstp; /* call history buffer */ + uint dbgcxhstl; /* history buffer length */ + uint dbgcxhstf; /* offset of next free byte of history */ + + /* + * This member is for the use of the user interface code. If the + * user interface implementation needs to store additional context, + * it can allocate a structure of its own (it should probably do + * this in dbguini()) and store a pointer to that structure here. + * Since the user interface entrypoints always have the debugger + * context passed as a parameter, the user interface code can + * recover its extra context information by following this pointer + * and casting it to its private structure type. The TADS code + * won't do anything with this pointer except initialize it to null + * when initializing the debugger context. + */ + void *dbgcxui; +}; +typedef struct dbgcxdef dbgcxdef; + + +/* ======================================================================== */ +/* + * Compiler interface. These routines are called by the compiler to + * inform the debug record generator about important events as + * compilation proceeds. + */ + + +/* + * Tell the current line source that we're compiling an executable + * line, and tell it the object number and offset of the code within the + * object. + */ +void dbgclin(struct tokcxdef *tokctx, objnum objn, uint ofs); + +/* size of information given to line source via lincmpinf method */ +#define DBGLINFSIZ 4 + + + +/* ======================================================================== */ +/* + * Run-time interface. These routines are called by the run-time + * system to apprise the debugger of important events during execution. + */ + + +/* + * Determine if the debugger is present. Returns true if so, false if + * not. This should return false for any stand-alone version of the + * executable that isn't linked with the debugger. If this returns + * true, dbgucmd() must not have a trivial implementation -- dbgucmd() + * must at least let the user quit out of the game. + * + * This can be switched at either link time or compile time. If DBG_OFF + * is defined, we'll force this to return false; otherwise, we'll let + * the program define the appropriate implementation through the linker. + */ +#ifdef DBG_OFF +#define dbgpresent() (FALSE) +#else +int dbgpresent(); +#endif + + +/* add a debug tracing record */ +/* void dbgenter(dbgcxdef *ctx, runsdef *bp, objnum self, objnum target, + prpnum prop, int binum, int argc); */ + +/* tell debugger where the current line's local frame table is located */ +/* void dbgframe(dbgcxdef *ctx, uint ofsfr, ofslin); */ + +/* + * Single-step interrupt: the run-time has reached a new source line. + * ofs is the offset from the start of the object of the line record, + * and p is the current execution pointer. *p can be changed upon + * return, in which case the run-time will continue from the new + * position; however, the new address must be within the same function + * or method as it was originally. + */ +/* void dbgssi(dbgcxdef *ctx, uint ofs, int instr, + int err, uchar *noreg *p); */ + +/* pop debug trace level */ +/* void dbgleave(dbgcxdef *ctx, int exittype); */ +#define DBGEXRET 0 /* return with no value */ +#define DBGEXVAL 1 /* return with a value */ +#define DBGEXPASS 2 /* use 'pass' to exit a function */ + +/* dump the stack into text output */ +/* void dbgdump(dbgcxdef *ctx); */ + +/* reset debug stack (throw away entire contents) */ +/* void dbgrst(dbgcxdef *ctx); */ + +/* activate debugger if possible; returns TRUE if no debugger is present */ +int dbgstart(dbgcxdef *ctx); + +/* add a string to the history buffer */ +void dbgaddhist(dbgcxdef *ctx, char *buf, int bufl); + +/* + * Find a base pointer, given the object+offset of the frame. If the + * frame is not active, this routine signals ERR_INACTFR; otherwise, the + * bp value for the frame is returned. + */ +struct runsdef *dbgfrfind(dbgcxdef *ctx, objnum frobj, uint frofs); + + +/* ======================================================================== */ +/* + * User Interface Support routines. These routines are called by the + * user interface layer to get information from the debugger and perform + * debugging operations. + */ + + +/* get a symbol name; returns length of name */ +int dbgnam(dbgcxdef *ctx, char *outbuf, int typ, int val); + +/* + * Get information about current line. It is assumed that the caller + * knows the size of the line information . + */ +void dbglget(dbgcxdef *ctx, uchar *buf); + +/* + * Get information about a line in an enclosing stack frame. Level 0 is + * the current line, level 1 is the first enclosing frame, and so on. + * Returns 0 on success, non-zero if the frame level is invalid. + */ +int dbglgetlvl(dbgcxdef *ctx, uchar *buf, int level); + +/* + * Set a breakpoint by symbolic address: "function" or + * "object.property". The string may contain whitespace characters + * around each symbol; it must be null-terminated. If an error occurs, + * the error number is returned. bpnum returns with the breakpoint + * number if err == 0. If the condition string is given (and is not an + * empty string), the condition is compiled in the scope of the + * breakpoint and attached as the breakpoint condition. + */ +int dbgbpset(dbgcxdef *ctx, char *addr, int *bpnum); + +/* + * Set a breakpoint at an object + offset location. If 'toggle' is + * true, and there's already a breakpoint at the given location, we'll + * clear the breakpoint; in this case, *did_set will return false to + * indicate that an existing breakpoint was cleared rather than a new + * breakpoint created. *did_set will return true if a new breakpoint + * was set. + */ +int dbgbpat(dbgcxdef *ctx, objnum objn, objnum self, + uint ofs, int *bpnum, char *bpname, int toggle, + char *condition, int *did_set); + +/* + * Set a breakpoint at an object + offset location, optionally with a + * condition, using an existing breakpoint slot. If the slot is already + * in use, we'll return an error. + */ +int dbgbpatid(dbgcxdef *ctx, int bpnum, objnum target, objnum self, + uint ofs, char *bpname, int toggle, char *cond, + int *did_set); + +/* + * Determine if there's a breakpoint at a given code location. Fills in + * *bpnum with the breakpoint identifier and returns true if a + * breakpoint is found at the given location; returns false if there are + * no breakpoints matching the description. + */ +int dbgisbp(dbgcxdef *ctx, objnum target, objnum self, uint ofs, int *bpnum); + +/* + * Determine if the given breakpoint is enabled + */ +int dbgisbpena(dbgcxdef *ctx, int bpnum); + +/* + * Delete a breakpoint by breakpoint number (as returned from + * dbgbpset). Returns error number, or 0 for success. + */ +int dbgbpdel(dbgcxdef *ctx, int bpnum); + +/* disable or enable a breakpoint, by breakpoint number; returns error num */ +int dbgbpdis(dbgcxdef *ctx, int bpnum, int disable); + +/* + * Set a new condition for the given breakpoint. Replaces any existing + * condition. If an error occurs, we'll leave the old condition as it + * was and return a non-zero error code; on success, we'll update the + * condition and return zero. + */ +int dbgbpsetcond(dbgcxdef *ctx, int bpnum, char *cond); + +/* list breakpoints, using user callback to do display */ +void dbgbplist(dbgcxdef *ctx, + void (*dispfn)(void *ctx, const char *str, int len), + void *dispctx); + +/* enumerate breakpoints */ +void dbgbpenum(dbgcxdef *ctx, + void (*cbfunc)(void *cbctx, int bpnum, const char *desc, + const char *cond, int disabled), void *cbctx); + +/* call callback with lindef data for each breakpoint currently set */ +void dbgbpeach(dbgcxdef *ctx, + void (*fn)(void *, int, uchar *, uint), + void *fnctx); + +/* + * Get information on a specific breakpoint. Returns zero on success, + * non-zero on failure. + */ +int dbgbpgetinfo(dbgcxdef *ctx, int bpnum, char *descbuf, size_t descbuflen, + char *condbuf, size_t condbuflen); + +/* + * Evaluate an expression (a text string to be parsed) at a particular + * stack context level; returns error number. Invokes the callback + * function repeatedly to display the value string, and ends the display + * with a newline. If showtype is true, we'll include a type name + * prefix, otherwise we'll simply display the value. + */ +int dbgeval(dbgcxdef *ctx, char *expr, + void (*dispfn)(void *dispctx, const char *str, int strl), + void *dispctx, int level, int showtype); + +/* + * Evaluate an expression, extended version. For aggregate values + * (objects, lists), we'll invoke a callback function for each value + * contained by the aggregate value, passing the callback the name and + * relationship of the subitem. The relationship is simply the operator + * that should be used to join the parent expression and the subitem + * name to form the full subitem expression; for objects, it's ".", and + * for lists it's null (because for lists the subitem names will include + * brackets). 'speculative' is passed to dbgcompile; see the comments + * there for information on the purpose of this flag. + */ +int dbgevalext(dbgcxdef *ctx, char *expr, + void (*dispfn)(void *dispctx, const char *str, int strl), + void *dispctx, int level, int showtype, dattyp *dat, + void (*aggcb)(void *aggctx, const char *subname, + int subnamelen, const char *relationship), + void *aggctx, int speculative); + +/* + * enumerate local variables at a given stack context level by calling + * the given function once for each local variable + */ +void dbgenumlcl(dbgcxdef *ctx, int level, + void (*func)(void *ctx, const char *lclnam, size_t lclnamlen), + void *cbctx); + +/* + * Compile an expression in a given frame context. Returns an error + * number. Allocates a new object to contain the compiled code, and + * returns the object number in *objn; the caller is responsible for + * freeing the object when done with it. + * + * If 'speculative' is set to true, we'll prohibit the expression from + * making any assignments or calling any methods or functions. This + * mode can be used to try compiling an expression that the user could + * conceivably be interested in but has not expressly evaluated; for + * example, this can be used to implement "tooltip evaluation," where + * the debugger automatically shows a little pop-up window with the + * expression under the mouse cursor if the mouse cursor is left + * hovering over some text for a few moments. In such cases, since the + * user hasn't explicitly requested evaluation, it would be bad to make + * any changes to game state, hence the prohibition of assignments or + * calls. + */ +int dbgcompile(dbgcxdef *ctx, char *expr, dbgfdef *fr, objnum *objn, + int speculative); + +/* display a stack traceback through a user callback */ +void dbgstktr(dbgcxdef *ctx, + void (*dispfn)(void *dispctx, const char *str, int strl), + void *dispctx, int level, int toponly, int include_markers); + +/* format a display of where execution is stopped into a buffer */ +void dbgwhere(dbgcxdef *ctx, char *buf); + +/* set a watch expression; returns error or 0 for success */ +int dbgwxset(dbgcxdef *ctx, char *expr, int *wxnum, int level); + +/* delete a watch expression */ +int dbgwxdel(dbgcxdef *ctx, int wxnum); + +/* update all watch expressions */ +void dbgwxupd(dbgcxdef *ctx, + void (*dispfn)(void *dispctx, const char *txt, int len), + void *dispctx); + +/* switch to a new active lindef */ +void dbgswitch(struct lindef **linp, struct lindef *newlin); + + + +/* ======================================================================== */ +/* + * User Interface Routines. The routines are called by the debugger + * to perform user interaction. + */ + +/* + * Debugger user interface initialization, phase one. TADS calls this + * routine during startup, before reading the .GAM file, to let the user + * interface perform any initialization it requires before the .GAM file + * is loaded. + */ +void dbguini(dbgcxdef *ctx, const char *game_filename); + +/* + * Debugger user interface initialization, phase two. TADS calls this + * routine during startup, after read the .GAM file. The debugger user + * interface code can perform any required initialization that depends + * on the .GAM file having been read. + */ +void dbguini2(dbgcxdef *ctx); + +/* + * Determine if the debugger can resume from a run-time error. This + * reflects the capabilities of the user interface of the debugger. In + * particular, if the UI provides a way to change the instruction + * pointer, then the debugger can resume from an error, since the user + * can always move past the run-time error and continue execution. If + * the UI doesn't let the user change the instruction pointer, resuming + * from an error won't work, since the program will keep hitting the + * same error and re-entering the debugger. If this returns false, the + * run-time will trap to the debugger on an error, but will simply abort + * the current command when the debugger returns. If this returns true, + * the run-time will trap to the debugger on an error with the + * instruction pointer set back to the start of the line containing the + * error, and will thus re-try the same line of code when the debugger + * returns, unless the debugger explicitly moves the instruction pointer + * before returning. + */ +int dbgu_err_resume(dbgcxdef *ctx); + +/* + * Find a source file. origname is the name of the source file as it + * appears in the game's debugging information; this routine should + * figure out where the file actually is, and put the fully-qualified + * path to the file in fullname. The debugger calls this after it + * exhausts all of its other methods of finding a source file (such as + * searching the include path). + * + * Return true if the source file should be considered valid, false if + * not. Most implementations will simply return true if the file was + * found, false if not; however, this approach will cause the debugger + * to terminate with an error at start-up if the user hasn't set up the + * debugger's include path correctly before running the debugger. Some + * implementations, in particular GUI implementations, may wish to wait + * to find a file until the file is actually needed, rather than pester + * the user with file search dialogs repeatedly at start-up. + * + * must_find_file specifies how to respond if we can't find the file. + * If must_find_file is true, we should always return false if we can't + * find the file. If must_find_file is false, however, we can + * optionally return true even if we can't find the file. Doing so + * indicates that the debugger UI will defer locating the file until it + * is actually needed. + * + * If this routine returns true without actually finding the file, it + * should set fullname[0] to '\0' to indicate that fullname doesn't + * contain a valid filename. + */ +int dbgu_find_src(const char *origname, int origlen, + char *fullname, size_t full_len, int must_find_file); + + +/* + * Debugger user interface main command loop. If err is non-zero, the + * debugger was entered because a run-time error occurred; otherwise, if + * bphit is non-zero, it's the number of the breakpoint that was + * encountered; otherwise, the debugger was entered through a + * single-step of some kind. exec_ofs is the byte offset within the + * target object of the next instruction to be executed. This can be + * changed upon return, in which case execution will continue from the + * new offset, but the offset must be within the same method of the same + * object (or within the same function) as it was upon entry. + */ +void dbgucmd(dbgcxdef *ctx, int bphit, int err, unsigned int *exec_ofs); + +/* + * Debugger UI - quitting game. The runtime calls this routine just + * before the play loop is about to terminate after the game code has + * called the "quit" built-in function. If the debugger wants, it can + * take control here (just as with dbgucmd()) for as long as it wants. + * If the debugger wants to restart the game, it should call bifrst(). + * If this routine returns without signalling a RUN_RESTART error, TADS + * will terminate. If a RUN_RESTART error is signalled, TADS will + * resume the play loop. + */ +void dbguquitting(dbgcxdef *ctx); + +/* + * debugger user interface termination - this routine is called when the + * debugger is about to terminate, so that the user interface can close + * itself down (close windows, release memory, etc) + */ +void dbguterm(dbgcxdef *ctx); + +/* + * Debugger user interface: display an error. This is called mainly so + * that the debugger can display an error using special output + * formatting if the error occurs while debugging. + */ +void dbguerr(dbgcxdef *ctx, int errnum, char *msg); + +/* turn hidden output tracing on/off */ +void trchid(void); +void trcsho(void); + + +/* ======================================================================== */ +/* + * optional debugger macros - these compile to nothing when compiling a + * version for use without the debugger + */ + +#ifdef DBG_OFF +# define dbgenter(ctx, bp, self, target, prop, binum, argc) +# define dbgleave(ctx, exittype) +# define dbgdump(ctx) +# define dbgrst(ctx) ((void)0) +# define dbgframe(ctx, frofs, linofs) +# define dbgssi(ctx, ofs, instr, err, p) +#else /* DBG_OFF */ +# define dbgenter(ctx, bp, self, target, prop, binum, argc) \ + dbgent(ctx, bp, self, target, prop, binum, argc) +# define dbgleave(ctx, exittype) dbglv(ctx, exittype) +# define dbgdump(ctx) dbgds(ctx) +# define dbgrst(ctx) ((ctx)->dbgcxfcn = (ctx)->dbgcxdep = 0) +# define dbgframe(ctx, frofs, linofs) \ + (((ctx)->dbgcxfrm[(ctx)->dbgcxfcn - 1].dbgffr = (frofs)), \ + ((ctx)->dbgcxfrm[(ctx)->dbgcxfcn - 1].dbgflin = (linofs))) +# define dbgssi(ctx, ofs, instr, err, p) dbgss(ctx, ofs, instr, err, p) +#endif /* DBG_OFF */ + +/* ======================================================================== */ +/* private internal routines */ + +void dbgent(dbgcxdef *ctx, struct runsdef *bp, objnum self, objnum target, + prpnum prop, int binum, int argc); + +void dbglv(dbgcxdef *ctx, int exittype); + +void dbgds(dbgcxdef *ctx); + +void dbgss(dbgcxdef *ctx, uint ofs, int instr, int err, uchar *noreg *p); + +void dbgpval(struct dbgcxdef *ctx, struct runsdef *val, + void (*dispfn)(void *, const char *, int), + void *dispctx, int showtype); + +int dbgtabsea(struct toktdef *tab, char *name, int namel, int hash, + struct toksdef *ret); + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/error.cpp b/engines/glk/tads/tads2/error.cpp new file mode 100644 index 0000000000..793ddccb1d --- /dev/null +++ b/engines/glk/tads/tads2/error.cpp @@ -0,0 +1,32 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/tads2/error.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/tads2/error.h b/engines/glk/tads/tads2/error.h new file mode 100644 index 0000000000..96831068d7 --- /dev/null +++ b/engines/glk/tads/tads2/error.h @@ -0,0 +1,390 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* Error handling + * This package defines a set of macros that allows code to raise and + * handle exceptions.A macro is provided which signals an error, which + * does a non - local goto to the innermost enclosing exception handler. + * A set of macros sets up exception handling code. + * + * To catch exceptions that occur inside a block of code(i.e., in the + * code or in any subroutines called by the code), begin the block with + * ERRBEGIN.At the end of the protected code, place the exception + * handler, which starts with ERRCATCH.At the end of the exception + * handler, place ERREND.If no exception occurs, execution goes + * through the protected code, then resumes at the code following + * the ERREND. + * + * The exception handler can signal another error, which will cause + * the next enclosing frame to catch the error.Alternatively, if + * the exception handler doesn't signal an error or return, execution + * continues at the code following the ERREND.Exceptions that are + * signalled during exception handling will be caught by the next + * enclosing frame, unless the exception handler code is itself + * protected by another ERRBEGIN - ERREND block. + * + * To signal an error, use errsig(). + * + * To use a string argument in a signalled error, cover the string + * with errstr(ctx, str, len); for example: + * + * errsig1(ctx, ERR_XYZ, ERRTSTR, errstr(ctx, buf, strlen(buf))); + * + * This copies the string into a buffer that is unaffected by + * stack resetting during error signalling. + */ + +#ifndef GLK_TADS_TADS2_ERROR +#define GLK_TADS_TADS2_ERROR + +#include "glk/tads/tads2/lib.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* + * for compatility with old facility-free mechanism, signal with + * facility "TADS" + */ +#define errsig(ctx, err) errsigf(ctx, "TADS", err) +#define errsig1(c, e, t, a) errsigf1(c,"TADS",e,t,a) +#define errsig2(c, e, t1, a1, t2, a2) errsigf2(c,"TADS",e,t1,a1,t2,a2) +#define errlog(c, e) errlogf(c, "TADS", e) +#define errlog1(c, e, t, a) errlogf1(c,"TADS",e,t,a) +#define errlog2(c, e, t1, a1, t2, a2) errlogf2(c,"TADS",e,t1,a1,t2,a2) + + + +/*---------------------------- TADS Error Codes ----------------------------*/ +/* memory/cache manager errors */ +#define ERR_NOMEM 1 /* out of memory */ +#define ERR_FSEEK 2 /* error seeking in file */ +#define ERR_FREAD 3 /* error reading from file */ +#define ERR_NOPAGE 4 /* no more page slots */ +#define ERR_REALCK 5 /* attempting to reallocate a locked object */ +#define ERR_SWAPBIG 6 /* swapfile limit reached - out of virtual memory */ +#define ERR_FWRITE 7 /* error writing file */ +#define ERR_SWAPPG 8 /* exceeded swap page table limit */ +#define ERR_CLIUSE 9 /* requested client object number already in use */ +#define ERR_CLIFULL 10 /* client mapping table is full */ +#define ERR_NOMEM1 11 /* swapping/garbage collection failed to find enuf */ +#define ERR_NOMEM2 12 /* no memory to resize (expand) an object */ +#define ERR_OPSWAP 13 /* unable to open swap file */ +#define ERR_NOHDR 14 /* can't get a new object header */ +#define ERR_NOLOAD 15 /* mcm cannot find object to load (internal error) */ +#define ERR_LCKFRE 16 /* attempting to free a locked object (internal) */ +#define ERR_INVOBJ 17 /* invalid object */ +#define ERR_BIGOBJ 18 /* object too big - exceeds memory allocation limit */ + +/* lexical analysis errors */ +#define ERR_INVTOK 100 /* invalid token */ +#define ERR_STREOF 101 /* end of file while scanning string */ +#define ERR_TRUNC 102 /* symbol too long - truncated */ +#define ERR_NOLCLSY 103 /* no space in local symbol table */ +#define ERR_PRPDIR 104 /* invalid preprocessor (#) directive */ +#define ERR_INCNOFN 105 /* no filename in #include directive */ +#define ERR_INCSYN 106 /* invalid #include syntax */ +#define ERR_INCSEAR 107 /* can't find included file */ +#define ERR_INCMTCH 108 /* no matching delimiter in #include filename */ +#define ERR_MANYSYM 109 /* out of space for symbol table */ +#define ERR_LONGLIN 110 /* line too long */ +#define ERR_INCRPT 111 /* header file already included - ignored */ +#define ERR_PRAGMA 112 /* unknown pragma (ignored) */ +#define ERR_BADPELSE 113 /* unexpected #else */ +#define ERR_BADENDIF 114 /* unexpected #endif */ +#define ERR_BADELIF 115 /* unexpected #elif */ +#define ERR_MANYPIF 116 /* #if nesting too deep */ +#define ERR_DEFREDEF 117 /* #define symbol already defined */ +#define ERR_PUNDEF 118 /* #undef symbol not defined */ +#define ERR_NOENDIF 119 /* missing #endif */ +#define ERR_MACNEST 120 /* macros nested too deeply */ +#define ERR_BADISDEF 121 /* invalid argument for defined() operator */ +#define ERR_PIF_NA 122 /* #if is not implemented */ +#define ERR_PELIF_NA 123 /* #elif is not implemented */ +#define ERR_P_ERROR 124 /* error directive: %s */ +#define ERR_LONG_FILE_MACRO 125 /* __FILE__ expansion too long */ +#define ERR_LONG_LINE_MACRO 126 /* __LINE__ expansion too long */ + +/* undo errors */ +#define ERR_UNDOVF 200 /* operation is too big for undo log */ +#define ERR_NOUNDO 201 /* no more undo information */ +#define ERR_ICUNDO 202 /* incomplete undo (no previous savepoint) */ + +/* parser errors */ +#define ERR_REQTOK 300 /* expected token (arg 1) - found something else */ +#define ERR_REQSYM 301 /* expected symbol */ +#define ERR_REQPRP 302 /* expected a property name */ +#define ERR_REQOPN 303 /* expected operand */ +#define ERR_REQARG 304 /* expected comma or closing paren (arg list) */ +#define ERR_NONODE 305 /* no space for new parse node */ +#define ERR_REQOBJ 306 /* epxected object name */ +#define ERR_REQEXT 307 /* redefining symbol as external function */ +#define ERR_REQFCN 308 /* redefining symbol as function */ +#define ERR_NOCLASS 309 /* can't use CLASS with function/external function */ +#define ERR_REQUNO 310 /* required unary operator */ +#define ERR_REQBIN 311 /* required binary operator */ +#define ERR_INVBIN 312 /* invalid binary operator */ +#define ERR_INVASI 313 /* invalid assignment */ +#define ERR_REQVAR 314 /* required variable name */ +#define ERR_LCLSYN 315 /* required comma or semicolon in local list */ +#define ERR_REQRBR 316 /* required right brace (eof before end of group) */ +#define ERR_BADBRK 317 /* 'break' without 'while' */ +#define ERR_BADCNT 318 /* 'continue' without 'while' */ +#define ERR_BADELS 319 /* 'else' without 'if' */ +#define ERR_WEQASI 320 /* warning: possible use of '=' where ':=' intended */ +#define ERR_EOF 321 /* unexpected end of file */ +#define ERR_SYNTAX 322 /* general syntax error */ +#define ERR_INVOP 323 /* invalid operand type */ +#define ERR_NOMEMLC 324 /* no memory for new local symbol table */ +#define ERR_NOMEMAR 325 /* no memory for argument symbol table */ +#define ERR_FREDEF 326 /* redefining a function which is already defined */ +#define ERR_NOSW 327 /* 'case' or 'default' and not in switch block */ +#define ERR_SWRQCN 328 /* constant required in switch case value */ +#define ERR_REQLBL 329 /* label required for 'goto' */ +#define ERR_NOGOTO 330 /* 'goto' label never defined */ +#define ERR_MANYSC 331 /* too many superclasses for object */ +#define ERR_OREDEF 332 /* redefining symbol as object */ +#define ERR_PREDEF 333 /* property being redefined in object */ +#define ERR_BADPVL 334 /* invalid property value */ +#define ERR_BADVOC 335 /* bad vocabulary property value */ +#define ERR_BADTPL 336 /* bad template property value (need sstring) */ +#define ERR_LONGTPL 337 /* template base property name too long */ +#define ERR_MANYTPL 338 /* too many templates (internal compiler limit) */ +#define ERR_BADCMPD 339 /* bad value for compound word (sstring required) */ +#define ERR_BADFMT 340 /* bad value for format string (sstring needed) */ +#define ERR_BADSYN 341 /* invalid value for synonym (sstring required) */ +#define ERR_UNDFSYM 342 /* undefined symbol */ +#define ERR_BADSPEC 343 /* bad special word */ +#define ERR_NOSELF 344 /* "self" not valid in this context */ +#define ERR_STREND 345 /* warning: possible unterminated string */ +#define ERR_MODRPLX 346 /* modify/replace not allowed with external func */ +#define ERR_MODFCN 347 /* modify not allowed with function */ +#define ERR_MODFWD 348 /* modify/replace not allowed with forward func */ +#define ERR_MODOBJ 349 /* modify can only be used with a defined object */ +#define ERR_RPLSPEC 350 /* warning - replacing specialWords */ +#define ERR_SPECNIL 351 /* nil only allowed with modify specialWords */ +#define ERR_BADLCL 353 /* 'local' statement must precede executable code */ +#define ERR_IMPPROP 354 /* implied verifier '%s' is not a property */ +#define ERR_BADTPLF 355 /* invalid command template flag */ +#define ERR_NOTPLFLG 356 /* flags are not allowed with old file format */ +#define ERR_AMBIGBIN 357 /* warning: operator '%s' could be binary */ +#define ERR_PIA 358 /* warning: possibly incorrect assignment */ +#define ERR_BADSPECEXPR 359 /* invalid speculation evaluation */ + +/* code generation errors */ +#define ERR_OBJOVF 400 /* object cannot grow any bigger - code too big */ +#define ERR_NOLBL 401 /* no more temporary labels/fixups */ +#define ERR_LBNOSET 402 /* (internal error) label never set */ +#define ERR_INVLSTE 403 /* invalid datatype for list element */ +#define ERR_MANYDBG 404 /* too many debugger line records (internal limit) */ + +/* vocabulary setup errors */ +#define ERR_VOCINUS 450 /* vocabulary being redefined for object */ +#define ERR_VOCMNPG 451 /* too many vocwdef pages (internal limit) */ +#define ERR_VOCREVB 452 /* redefining same verb */ +#define ERR_VOCREVB2 453 /* redefining same verb - two arguments */ + +/* set-up errors */ +#define ERR_LOCNOBJ 500 /* location of object %s is not an object */ +#define ERR_CNTNLST 501 /* contents of object %s is not list */ +#define ERR_SUPOVF 502 /* overflow trying to build contents list */ +#define ERR_RQOBJNF 503 /* required object %s not found */ +#define ERR_WRNONF 504 /* warning - object %s not found */ +#define ERR_MANYBIF 505 /* too many built-in functions (internal error) */ + +/* fio errors */ +#define ERR_OPWGAM 600 /* unable to open game for writing */ +#define ERR_WRTGAM 601 /* error writing to game file */ +#define ERR_FIOMSC 602 /* too many sc's for writing in fiowrt */ +#define ERR_UNDEFF 603 /* undefined function */ +#define ERR_UNDEFO 604 /* undefined object */ +#define ERR_UNDEF 605 /* undefined symbols found */ +#define ERR_OPRGAM 606 /* unable to open game for reading */ +#define ERR_RDGAM 607 /* error reading game file */ +#define ERR_BADHDR 608 /* file has invalid header - not TADS game file */ +#define ERR_UNKRSC 609 /* unknown resource type in .gam file */ +#define ERR_UNKOTYP 610 /* unknown object type in OBJ resource */ +#define ERR_BADVSN 611 /* file saved by different incompatible version */ +#define ERR_LDGAM 612 /* error loading object on demand */ +#define ERR_LDBIG 613 /* object too big for load region (prob. internal) */ +#define ERR_UNXEXT 614 /* did not expect external function */ +#define ERR_WRTVSN 615 /* compiler cannot write the requested version */ +#define ERR_VNOCTAB 616 /* format version cannot be used with -ctab */ +#define ERR_BADHDRRSC 617 /* invalid resource file header in file %s */ +#define ERR_RDRSC 618 /* error reading resource file "xxx" */ + +/* character mapping errors */ +#define ERR_CHRNOFILE 700 /* unable to load character mapping file */ + +/* user interrupt */ +#define ERR_USRINT 990 /* user requested cancel of current operation */ + +/* run-time errors */ +#define ERR_STKOVF 1001 /* stack overflow */ +#define ERR_HPOVF 1002 /* heap overflow */ +#define ERR_REQNUM 1003 /* numeric value required */ +#define ERR_STKUND 1004 /* stack underflow */ +#define ERR_REQLOG 1005 /* logical value required */ +#define ERR_INVCMP 1006 /* invalid datatypes for magnitude comparison */ +#define ERR_REQSTR 1007 /* string value required */ +#define ERR_INVADD 1008 /* invalid datatypes for '+' operator */ +#define ERR_INVSUB 1009 /* invalid datatypes for binary '-' operator */ +#define ERR_REQVOB 1010 /* require object value */ +#define ERR_REQVFN 1011 /* required function pointer */ +#define ERR_REQVPR 1012 /* required property number value */ + +/* non-error conditions: run-time EXIT, ABORT, ASKIO, ASKDO */ +#define ERR_RUNEXIT 1013 /* 'exit' statement executed */ +#define ERR_RUNABRT 1014 /* 'abort' statement executed */ +#define ERR_RUNASKD 1015 /* 'askdo' statement executed */ +#define ERR_RUNASKI 1016 /* 'askio' executed; int arg 1 is prep */ +#define ERR_RUNQUIT 1017 /* 'quit' executed */ +#define ERR_RUNRESTART 1018 /* 'reset' executed */ +#define ERR_RUNEXITOBJ 1019 /* 'exitobj' executed */ + +#define ERR_REQVLS 1020 /* list value required */ +#define ERR_LOWINX 1021 /* index value too low (must be >= 1) */ +#define ERR_HIGHINX 1022 /* index value too high (must be <= length(list)) */ +#define ERR_INVTBIF 1023 /* invalid type for built-in function */ +#define ERR_INVVBIF 1024 /* invalid value for built-in function */ +#define ERR_BIFARGC 1025 /* wrong number of arguments to built-in */ +#define ERR_ARGC 1026 /* wrong number of arguments to user function */ +#define ERR_FUSEVAL 1027 /* string/list not allowed for fuse/daemon arg */ +#define ERR_BADSETF 1028 /* internal error in setfuse/setdaemon/notify */ +#define ERR_MANYFUS 1029 /* too many fuses */ +#define ERR_MANYDMN 1030 /* too many daemons */ +#define ERR_MANYNFY 1031 /* too many notifiers */ +#define ERR_NOFUSE 1032 /* fuse not found in remfuse */ +#define ERR_NODMN 1033 /* daemon not found in remdaemon */ +#define ERR_NONFY 1034 /* notifier not found in unnotify */ +#define ERR_BADREMF 1035 /* internal error in remfuse/remdaemon/unnotify */ +#define ERR_DMDLOOP 1036 /* load-on-demand loop: property not being set */ +#define ERR_UNDFOBJ 1037 /* undefined object in vocabulary tree */ +#define ERR_BIFCSTR 1038 /* c-string conversion overflows buffer */ +#define ERR_INVOPC 1039 /* invalid opcode */ +#define ERR_RUNNOBJ 1040 /* runtime error: property taken of non-object */ +#define ERR_EXTLOAD 1041 /* unable to load external function "%s" */ +#define ERR_EXTRUN 1042 /* error executing external function "%s" */ +#define ERR_CIRCSYN 1043 /* circular synonym */ +#define ERR_DIVZERO 1044 /* divide by zero */ +#define ERR_BADDEL 1045 /* can only delete objects created with "new" */ +#define ERR_BADNEWSC 1046 /* superclass for "new" cannot be a new object */ +#define ERR_VOCSTK 1047 /* insufficient space in parser stack */ +#define ERR_BADFILE 1048 /* invalid file handle */ + +#define ERR_RUNEXITPRECMD 1049 /* exited from preCommand */ + +/* run-time parser errors */ +#define ERR_PRS_SENT_UNK 1200 /* sentence structure not recognized */ +#define ERR_PRS_VERDO_FAIL 1201 /* verDoVerb failed */ +#define ERR_PRS_VERIO_FAIL 1202 /* verIoVerb failed */ +#define ERR_PRS_NO_VERDO 1203 /* no verDoVerb for direct object */ +#define ERR_PRS_NO_VERIO 1204 /* no verIoVerb for direct object */ +#define ERR_PRS_VAL_DO_FAIL 1205 /* direct object validation failed */ +#define ERR_PRS_VAL_IO_FAIL 1206 /* indirect object validation failed */ + +/* compiler/runtime/debugger driver errors */ +#define ERR_USAGE 1500 /* invalid usage */ +#define ERR_OPNINP 1501 /* error opening input file */ +#define ERR_NODBG 1502 /* game not compiled for debugging */ +#define ERR_ERRFIL 1503 /* unable to open error capture file */ +#define ERR_PRSCXSIZ 1504 /* parse pool + local size too large */ +#define ERR_STKSIZE 1505 /* stack size too large */ +#define ERR_OPNSTRFIL 1506 /* error opening string capture file */ +#define ERR_INVCMAP 1507 /* invalid character map file */ + +/* debugger errors */ +#define ERR_BPSYM 2000 /* symbol not found for breakpoint */ +#define ERR_BPPROP 2002 /* breakpoint symbol is not a property */ +#define ERR_BPFUNC 2003 /* breakpoint symbol is not a function */ +#define ERR_BPNOPRP 2004 /* property is not defined for object */ +#define ERR_BPPRPNC 2005 /* property is not code */ +#define ERR_BPSET 2006 /* breakpoint already set at this location */ +#define ERR_BPNOTLN 2007 /* breakpoint is not at a line (OPCLINE instr) */ +#define ERR_MANYBP 2008 /* too many breakpoints */ +#define ERR_BPNSET 2009 /* breakpoint to be deleted was not set */ +#define ERR_DBGMNSY 2010 /* too many symbols in debug expression (int lim) */ +#define ERR_NOSOURC 2011 /* unable to find source file %s */ +#define ERR_WTCHLCL 2012 /* illegal to assign to local in watch expr */ +#define ERR_INACTFR 2013 /* inactive frame (expression value not available) */ +#define ERR_MANYWX 2014 /* too many watch expressions */ +#define ERR_WXNSET 2015 /* watchpoint not set */ +#define ERR_EXTRTXT 2016 /* extraneous text at end of command */ +#define ERR_BPOBJ 2017 /* breakpoint symbol is not an object */ +#define ERR_DBGINACT 2018 /* debugger is not active */ +#define ERR_BPINUSE 2019 /* breakpoint is already used */ +#define ERR_RTBADSPECEXPR 2020 /* invalid speculative expression */ +#define ERR_NEEDLIN2 2021 /* -ds2 information not found - must recompile */ + +/* usage error messages */ +#define ERR_TCUS1 3000 /* first tc usage message */ +#define ERR_TCUSL 3024 /* last tc usage message */ +#define ERR_TCTGUS1 3030 /* first tc toggle message */ +#define ERR_TCTGUSL 3032 +#define ERR_TCZUS1 3040 /* first tc -Z suboptions usage message */ +#define ERR_TCZUSL 3041 +#define ERR_TC1US1 3050 /* first tc -1 suboptions usage message */ +#define ERR_TC1USL 3058 +#define ERR_TCMUS1 3070 /* first tc -m suboptions usage message */ +#define ERR_TCMUSL 3076 +#define ERR_TCVUS1 3080 /* first -v suboption usage message */ +#define ERR_TCVUSL 3082 +#define ERR_TRUSPARM 3099 +#define ERR_TRUS1 3100 /* first tr usage message */ +#define ERR_TRUSL 3117 +#define ERR_TRUSFT1 3118 /* first tr "footer" message */ +#define ERR_TRUSFTL 3119 /* last tr "footer" message */ +#define ERR_TRSUS1 3150 /* first tr -s suboptions usage message */ +#define ERR_TRSUSL 3157 +#define ERR_TDBUSPARM 3199 +#define ERR_TDBUS1 3200 /* first tdb usage message */ +#define ERR_TDBUSL 3214 /* last tdb usage message */ + +/* TR 16-bit MSDOS-specific usage messages */ +#define ERR_TRUS_DOS_1 3300 +#define ERR_TRUS_DOS_L 3300 + +/* TR 32-bit MSDOS console mode usage messages */ +#define ERR_TRUS_DOS32_1 3310 +#define ERR_TRUS_DOS32_L 3312 + +/* TADS/Graphic errors */ +#define ERR_GNOFIL 4001 /* can't find graphics file %s */ +#define ERR_GNORM 4002 /* can't find room %s */ +#define ERR_GNOOBJ 4003 /* can't find hot spot object %s */ +#define ERR_GNOICN 4004 /* can't find icon object %s */ + + +/* + * Special error flag - this is returned from execmd() when preparseCmd + * returns a command list. This indicates to voc1cmd that it should try + * the command over again, using the words in the new list. + */ +#define ERR_PREPRSCMDREDO 30000 /* preparseCmd returned a list */ +#define ERR_PREPRSCMDCAN 30001 /* preparseCmd returned 'nil' to cancel */ + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/error_handling.cpp b/engines/glk/tads/tads2/error_handling.cpp new file mode 100644 index 0000000000..a2300ee834 --- /dev/null +++ b/engines/glk/tads/tads2/error_handling.cpp @@ -0,0 +1,32 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "error_handling.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/tads2/error_handling.h b/engines/glk/tads/tads2/error_handling.h new file mode 100644 index 0000000000..a225bbea74 --- /dev/null +++ b/engines/glk/tads/tads2/error_handling.h @@ -0,0 +1,338 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* Library error handling definitions + * All of the functions and macros in here are named ERRxxx because + * this file was based on the TADS err.h, which used the ERRxxx naming + * convention, and it would be a lot of trouble to change. + * + * This package defines a set of macros that allows code to raise and + * handle exceptions. A macro is provided which signals an error, which + * does a non-local goto to the innermost enclosing exception handler. + * A set of macros sets up exception handling code. + * + * To catch exceptions that occur inside a block of code (i.e., in the + * code or in any subroutines called by the code), begin the block with + * ERRBEGIN. At the end of the protected code, place the exception + * handler, which starts with ERRCATCH. At the end of the exception + * handler, place ERREND. If no exception occurs, execution goes + * through the protected code, then resumes at the code following + * the ERREND. + * + * The exception handler can signal another error, which will cause + * the next enclosing frame to catch the error. Alternatively, if + * the exception handler doesn't signal an error or return, execution + * continues at the code following the ERREND. Exceptions that are + * signalled during exception handling will be caught by the next + * enclosing frame, unless the exception handler code is itself + * protected by another ERRBEGIN-ERREND block. + * + * To signal an error, use errsig(). + * + * To use a string argument in a signalled error, cover the string + * with errstr(ctx, str, len); for example: + * + * errsig1(ctx, ERR_XYZ, ERRTSTR, errstr(ctx, buf, strlen(buf))); + * + * This copies the string into a buffer that is unaffected by + * stack resetting during error signalling. + */ + +#ifndef GLK_TADS_TADS2_ERROR_HANDLING +#define GLK_TADS_TADS2_ERROR_HANDLING + +#include "glk/tads/tads2/lib.h" +#include "glk/tads/osfrobtads.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +// TODO: Clean away use of jmp_buf, since ScummVM doesn't allow it +struct jmp_buf {}; + +/** + * Maximum length of a facility identifier + */ +#define ERRFACMAX 6 + +union erradef { + int erraint; /* integer argument */ + char *errastr; /* text string argument */ +}; + +struct errdef { + struct errdef *errprv; /* previous error frame */ + int errcode; /* error code of exception being handled */ + char errfac[ERRFACMAX+1]; /* facility of current error */ + erradef erraav[10]; /* parameters for error */ + int erraac; /* count of parameters in argc */ + jmp_buf errbuf; /* jump buffer for current error frame */ +}; + +#define ERRBUFSIZ 512 + +/** + * Seek location record for an error message by number + */ +struct errmfdef { + uint errmfnum; /* error number */ + ulong errmfseek; /* seek location of this message */ +}; +typedef struct errmfdef errmfdef; + +struct errcxdef { + errdef *errcxptr; /* current error frame */ + void (*errcxlog)(void *, char *fac, int err, int argc, erradef *); + /* error logging callback function */ + void *errcxlgc; /* context for error logging callback */ + int errcxofs; /* offset in argument buffer */ + char errcxbuf[ERRBUFSIZ]; /* space for argument strings */ + osfildef *errcxfp; /* message file, if one is being used */ + errmfdef *errcxseek; /* seek locations of messages in file */ + uint errcxsksz; /* size of errcxseek array */ + ulong errcxbase; /* offset in physical file of logical error file */ + struct appctxdef *errcxappctx; /* host application context */ +}; + +/** + * Begin protected code + */ +#define ERRBEGIN(ctx) \ + { \ + errdef fr_; \ + if ((fr_.errcode = setjmp(fr_.errbuf)) == 0) \ + { \ + fr_.errprv = (ctx)->errcxptr; \ + (ctx)->errcxptr = &fr_; + +/** + * End protected code, begin error handler + */ +#define ERRCATCH(ctx, e) \ + assert(1==1 && (ctx)->errcxptr != fr_.errprv); \ + (ctx)->errcxptr = fr_.errprv; \ + } \ + else \ + { \ + assert(2==2 && (ctx)->errcxptr != fr_.errprv); \ + (e) = fr_.errcode; \ + (ctx)->errcxptr = fr_.errprv; + +/* retrieve argument (int, string) in current error frame */ +#define errargint(argnum) (fr_.erraav[argnum].erraint) +#define errargstr(argnum) (fr_.erraav[argnum].errastr) + + +#define ERREND(ctx) \ + } \ + } + +/* end protected code, begin cleanup (no handling; just cleaning up) */ +#define ERRCLEAN(ctx) \ + assert((ctx)->errcxptr != fr_.errprv); \ + (ctx)->errcxptr = fr_.errprv; \ + } \ + else \ + { \ + assert((ctx)->errcxptr != fr_.errprv); \ + (ctx)->errcxptr = fr_.errprv; + +#define ERRENDCLN(ctx) \ + errrse(ctx); \ + } \ + } + + + +/* argument types for errors with arguments */ +#define ERRTINT erraint +#define ERRTSTR errastr + +/* set argument count in error frame */ +#define errargc(ctx,cnt) ((ctx)->errcxptr->erraac=(cnt)) + +/* enter string argument; returns pointer to argument used in errargv */ +#ifdef ERR_NO_MACRO +char *errstr(errcxdef *ctx, const char *str, int len); +#else /* ERR_NO_MACRO */ + +#define errstr(ctx,str,len) \ + ((memcpy(&(ctx)->errcxbuf[(ctx)->errcxofs],str,(size_t)len), \ + (ctx)->errcxofs += (len), \ + (ctx)->errcxbuf[(ctx)->errcxofs++] = '\0'), \ + &(ctx)->errcxbuf[(ctx)->errcxofs-(len)-1]) + +#endif /* ERR_NO_MACRO */ + +/* set argument in error frame argument vector */ +#define errargv(ctx,index,typ,arg) \ + ((ctx)->errcxptr->erraav[index].typ=(arg)) + +/* signal an error with argument count already set */ +#ifdef ERR_NO_MACRO +void errsign(errcxdef *ctx, int e, char *facility); +#else /* ERR_NO_MACRO */ +# ifdef DEBUG +void errjmp(jmp_buf buf, int e); +# define errsign(ctx, e, fac) \ + (strncpy((ctx)->errcxptr->errfac, fac, ERRFACMAX),\ + (ctx)->errcxptr->errfac[ERRFACMAX]='\0',\ + (ctx)->errcxofs=0, errjmp((ctx)->errcxptr->errbuf, e)) +# else /* DEBUG */ +# define errsign(ctx, e, fac) \ + (strncpy((ctx)->errcxptr->errfac, fac, ERRFACMAX),\ + (ctx)->errcxptr->errfac[ERRFACMAX]='\0',\ + (ctx)->errcxofs=0, longjmp((ctx)->errcxptr->errbuf, e)) +# endif /* DEBUG */ +#endif /* ERR_NO_MACRO */ + + +/* signal an error with no arguments */ +#ifdef ERR_NO_MACRO +void errsigf(errcxdef *ctx, char *facility, int err); +#else /* ERR_NO_MACRO */ +#define errsigf(ctx, fac, e) (errargc(ctx,0),errsign(ctx,e,fac)) +#endif /* ERR_NO_MACRO */ + +/* signal an error with one argument */ +#define errsigf1(ctx, fac, e, typ1, arg1) \ + (errargv(ctx,0,typ1,arg1),errargc(ctx,1),errsign(ctx,e,fac)) + +/* signal an error with two arguments */ +#define errsigf2(ctx, fac, e, typ1, arg1, typ2, arg2) \ + (errargv(ctx,0,typ1,arg1), errargv(ctx,1,typ2,arg2), \ + errargc(ctx,2), errsign(ctx,e,fac)) + +/* resignal the current error - only usable within exception handlers */ +#ifdef ERR_NO_MACRO +void errrse1(errcxdef *ctx, errdef *fr); +# define errrse(ctx) errrse1(ctx, &fr_) +#else /* ERR_NO_MACRO */ + +/* void errrse(errcxdef *ctx); */ +# define errrse(ctx) \ + (errargc(ctx, fr_.erraac),\ + memcpy((ctx)->errcxptr->erraav, fr_.erraav, \ + (size_t)(fr_.erraac*sizeof(erradef))),\ + errsign(ctx, fr_.errcode, fr_.errfac)) + +#endif /* ERR_NO_MACRO */ + +/* + * For use in an error handler (ERRCATCH..ERREND) only: Copy the + * parameters from the error currently being handled to the enclosing + * frame. This is useful when "keeping" an error being handled - i.e., + * the arguments will continue to be used outside of the + * ERRCATCH..ERREND code. + */ +/* void errkeepargs(errcxdef *ctx); */ +#define errkeepargs(ctx) errcopyargs(ctx, &fr_) + +/* + * copy the parameters for an error from another frame into the current + * frame - this can be used when we want to be able to display an error + * that occurred in an inner frame within code that is protected by a + * new enclosing error frame + */ +/* void errcopyargs(errcxdef *ctx, errdef *fr); */ +#define errcopyargs(ctx, fr) \ + (errargc((ctx), (fr)->erraac), \ + memcpy((ctx)->errcxptr->erraav, (fr)->erraav, \ + (size_t)((fr)->erraac*sizeof(erradef)))) + +/* log error that's been caught, using arguments already caught */ +#define errclog(ctx) \ + ((*(ctx)->errcxlog)((ctx)->errcxlgc,fr_.errfac,fr_.errcode,\ + fr_.erraac,fr_.erraav)) + +/* log an error that's been set up but not signalled yet */ +#define errprelog(ctx, err) \ + ((*(ctx)->errcxlog)((ctx)->errcxlgc,(ctx)->errcxptr->errfac,\ + err,(ctx)->errcxptr->erraac,\ + (ctx)->errcxptr->erraav)) + +/* log an error (no signalling, just reporting) */ +#ifdef ERR_NO_MACRO +void errlogn(errcxdef *ctx, int err, char *facility); +#else /* ERR_NO_MACRO */ + +#define errlogn(ctx,err,fac) \ + ((ctx)->errcxofs=0,\ + (*(ctx)->errcxlog)((ctx)->errcxlgc,fac,err,(ctx)->errcxptr->erraac,\ + (ctx)->errcxptr->erraav)) + +#endif /* ERR_NO_MACRO */ + +/* log an error with no arguments */ +#ifdef ERR_NO_MACRO +void errlogf(errcxdef *ctx, char *facility, int err); +#else /* ERR_NO_MACRO */ + +/* void errlogf(errcxdef *ctx, char *facility, int err); */ +#define errlogf(ctx,fac,err) (errargc(ctx,0),errlogn(ctx,err,fac)) + +#endif /* ERR_NO_MACRO */ + +/* log an error with one argument */ +#define errlogf1(ctx, fac, e, typ1, arg1) \ + (errargv(ctx,0,typ1,arg1),errargc(ctx,1),errlogn(ctx,e,fac)) + +/* log an error with two arguments */ +#define errlogf2(ctx, fac, e, typ1, arg1, typ2, arg2) \ + (errargv(ctx,0,typ1,arg1),errargv(ctx,1,typ2,arg2),\ + errargc(ctx,2),errlogn(ctx,e,fac)) + + +/* + * Format an error message, sprintf-style, using arguments in an + * erradef array (which is passed to the error-logging callback). + * Returns the length of the output string, even if the actual + * output string was truncated because the outbuf was too short. + * (If called with outbufl == 0, nothing will be written out, but + * the size of the buffer needed, minus the terminating null byte, + * will be computed and returned.) + */ +int errfmt(char *outbuf, int outbufl, char *fmt, int argc, + erradef *argv); + +/* get the text of an error */ +void errmsg(errcxdef *ctx, char *outbuf, uint outbufl, uint err); + +/* initialize error subsystem, opening error message file if necessary */ +void errini(errcxdef *ctx, osfildef *fp); + +/* allocate and initialize error context, free error context */ +errcxdef *lerini(); +void lerfre(errcxdef *ctx); + +/* error message structure - number + text */ +struct errmdef { + uint errmerr; /* error number */ + char *errmtxt; /* text of error message */ +}; + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/file_io.cpp b/engines/glk/tads/tads2/file_io.cpp new file mode 100644 index 0000000000..581601fbf0 --- /dev/null +++ b/engines/glk/tads/tads2/file_io.cpp @@ -0,0 +1,32 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/tads2/file_io.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/tads2/file_io.h b/engines/glk/tads/tads2/file_io.h new file mode 100644 index 0000000000..8599c4909f --- /dev/null +++ b/engines/glk/tads/tads2/file_io.h @@ -0,0 +1,137 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * file i/o interface + */ + +#ifndef GLK_TADS_TADS2_FILE_IO +#define GLK_TADS_TADS2_FILE_IO + +#include "glk/tads/tads2/lib.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* forward declarations */ +struct voccxdef; + +/* load-on-demand context (passed in by mcm in load callback) */ +typedef struct fiolcxdef fiolcxdef; +struct fiolcxdef +{ + osfildef *fiolcxfp; /* file pointer of load file */ + errcxdef *fiolcxerr; /* error handling context */ + ulong fiolcxst; /* starting offset in file */ + uint fiolcxflg; /* flags from original load file */ + uint fiolcxseed; /* fioxor seed */ + uint fiolcxinc; /* fioxor increment */ +}; + +/* write game to binary file */ +void fiowrt(struct mcmcxdef *mctx, voccxdef *vctx, + struct tokcxdef *tokctx, struct tokthdef *tab, + uchar *fmts, uint fmtl, char *fname, uint flags, objnum preinit, + int extc, uint prpcnt, char *filever); + +/* flag values for use with fiowrt */ +#define FIOFSYM 0x01 /* include symbol table in output file */ +#define FIOFLIN 0x02 /* include source file tracking information */ +#define FIOFPRE 0x04 /* preinit needs to be run after reading game */ +#define FIOFCRYPT 0x08 /* "encrypt" objects prior to writing them */ +#define FIOFBIN 0x10 /* writing precompiled header */ +#define FIOFFAST 0x20 /* fast-load records are in file */ +#define FIOFCASE 0x40 /* case folding was turned on in original compile */ +#define FIOFLIN2 0x80 /* new-style line records */ + +/* read game from binary file; sets up loader callback context */ +void fiord(struct mcmcxdef *mctx, voccxdef *vctx, + struct tokcxdef *tctx, + char *fname, char *exename, + struct fiolcxdef *setupctx, objnum *preinit, uint *flagp, + struct tokpdef *path, uchar **fmtsp, uint *fmtlp, + uint *pcntptr, int flags, struct appctxdef *appctx, + char *argv0); + +/* shut down load-on-demand subsystem, close load file */ +void fiorcls(fiolcxdef *ctx); + +/* loader callback - load an object on demand */ +void OS_LOADDS fioldobj(void *ctx, mclhd handle, uchar *ptr, + ushort siz); + +/* + * Save a game - returns TRUE on failure. We'll save the file to + * 'fname'. 'game_fname' is the name of the game file; if this is not + * null, we'll save it to the saved game file so that the player can + * later start the game by specifying only the saved game file to the + * run-time. 'game_fname' can be null, in which case we'll omit the + * game file information. + */ +int fiosav(voccxdef *vctx, char *fname, char *game_fname); + +/* + * fiorso() result codes + */ +#define FIORSO_SUCCESS 0 /* success */ +#define FIORSO_FILE_NOT_FOUND 1 /* file not found */ +#define FIORSO_NOT_SAVE_FILE 2 /* not a saved game file */ +#define FIORSO_BAD_FMT_VSN 3 /* incompatible file format version */ +#define FIORSO_BAD_GAME_VSN 4 /* file saved by another game or version */ +#define FIORSO_READ_ERROR 5 /* error reading from the file */ +#define FIORSO_NO_PARAM_FILE 6 /* no parameter file (for restore(nil)) */ + +/* restore a game - returns TRUE on failure */ +int fiorso(voccxdef *vctx, char *fname); + +/* + * Look in a saved game file to determine if it has information on which + * GAM file created it. If the GAM file information is available, this + * routine returns true and stores the game file name in the given + * buffer; if the information isn't available, we'll return false. + */ +int fiorso_getgame(char *saved_file, char *buf, size_t buflen); + +/* encrypt/decrypt an object */ +void fioxor(uchar *p, uint siz, uint seed, uint inc); + +/* strings stored in binary game file for identification and validation */ + +/* file header string */ +#define FIOFILHDR "TADS2 bin\012\015\032" + +/* resource file header string */ +#define FIOFILHDRRSC "TADS2 rsc\012\015\032" + +/* CURRENT file format version string */ +#define FIOVSNHDR "v2.2.0" + +/* other file format versions that can be READ by this version */ +#define FIOVSNHDR2 "v2.0.0" +#define FIOVSNHDR3 "v2.0.1" + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/ler.cpp b/engines/glk/tads/tads2/ler.cpp deleted file mode 100644 index d996977f0e..0000000000 --- a/engines/glk/tads/tads2/ler.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "ler.h" - -namespace Glk { -namespace TADS { -namespace TADS2 { - -#define TRDLOGERR_PREFIX "\n[An error has occurred within TADS: " - -int errcxdef::errfmt(char *outbuf, int outbufl, char *fmt, int argc, erradef *argv) { - int outlen = 0; - int argi = 0; - int len; - char buf[20]; - const char *p = nullptr; - char fmtchar; - - while (*fmt != '\0' && outbufl > 1) { - switch(*fmt) { - case '\\': - ++fmt; - len = 1; - switch(*fmt) { - case '\0': - --fmt; - break; - case '\n': - p = "\n"; - break; - case '\t': - p = "\t"; - break; - default: - p = fmt; - break; - } - break; - - case '%': - ++fmt; - fmtchar = *fmt; - if (argi >= argc) fmtchar = 1; // too many - ignore it - switch(fmtchar) { - case '\0': - --fmt; - len = 0; - break; - - case '%': - p = "%"; - len = 1; - break; - - case 'd': - sprintf(buf, "%d", argv[argi].erraint); - len = strlen(buf); - p = buf; - break; - - case 'u': - sprintf(buf, "%u", argv[argi].erraint); - len = strlen(buf); - p = buf; - break; - - case 's': - p = argv[argi].errastr; - len = strlen(p); - break; - - default: - p = ""; - len = 0; - --argi; - break; - } - ++argi; - break; - - default: - p = fmt; - len = 1; - break; - } - - /* copy output that was set up above */ - if (len != 0) { - if (outbufl >= len) { - memcpy(outbuf, p, (size_t)len); - outbufl -= len; - outbuf += len; - } else if (outbufl > 1) { - memcpy(outbuf, p, (size_t)outbufl - 1); - outbufl = 1; - } - outlen += len; - } - ++fmt; - } - - // add a null terminator - if (outbufl != 0) - *outbuf++ = '\0'; - - // return the length - return outlen; -} - -void errcxdef::errcxlog(void *ctx0, char *fac, int err, int argc, erradef *argv) { -#ifdef TODO - errcxdef *ctx = (errcxdef *)ctx0; - char buf[256]; - char msg[256]; - - // display the prefix message to the console and log file - sprintf(buf, TRDLOGERR_PREFIX, fac, err); - trdptf("%s", buf); - out_logfile_print(buf, false); - - /* display the error message text to the console and log file */ - errmsg(ctx, msg, (uint)sizeof(msg), err); - errfmt(buf, (int)sizeof(buf), msg, argc, argv); - trdptf("%s]\n", buf); - out_logfile_print(buf, false); - out_logfile_print("]", true); -#endif -} - -} // End of namespace TADS2 -} // End of namespace TADS -} // End of namespace Glk diff --git a/engines/glk/tads/tads2/ler.h b/engines/glk/tads/tads2/ler.h deleted file mode 100644 index 57e3e63c2c..0000000000 --- a/engines/glk/tads/tads2/ler.h +++ /dev/null @@ -1,617 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifndef GLK_TADS_TADS2_LER -#define GLK_TADS_TADS2_LER - -#include "common/scummsys.h" -#include "common/stream.h" -#include "common/algorithm.h" - -namespace Glk { -namespace TADS { -namespace TADS2 { - -// maximum length of a facility identifier -#define ERRFACMAX 6 - -enum ErrorCode { - /* memory/cache manager errors */ - ERR_NOMEM = 1, /* out of memory */ - ERR_FSEEK = 2, /* error seeking in file */ - ERR_FREAD = 3, /* error reading from file */ - ERR_NOPAGE = 4, /* no more page slots */ - ERR_REALCK = 5, /* attempting to reallocate a locked object */ - ERR_SWAPBIG = 6, /* swapfile limit reached - out of virtual memory */ - ERR_FWRITE = 7, /* error writing file */ - ERR_SWAPPG = 8, /* exceeded swap page table limit */ - ERR_CLIUSE = 9, /* requested client object number already in use */ - ERR_CLIFULL = 10, /* client mapping table is full */ - ERR_NOMEM1 = 11, /* swapping/garbage collection failed to find enuf */ - ERR_NOMEM2 = 12, /* no memory to resize (expand) an object */ - ERR_OPSWAP = 13, /* unable to open swap file */ - ERR_NOHDR = 14, /* can't get a new object header */ - ERR_NOLOAD = 15, /* mcm cannot find object to load (internal error) */ - ERR_LCKFRE = 16, /* attempting to free a locked object (internal) */ - ERR_INVOBJ = 17, /* invalid object */ - ERR_BIGOBJ = 18, /* object too big - exceeds memory allocation limit */ - - /* lexical analysis errors */ - ERR_INVTOK = 100, /* invalid token */ - ERR_STREOF = 101, /* end of file while scanning string */ - ERR_TRUNC = 102, /* symbol too long - truncated */ - ERR_NOLCLSY = 103, /* no space in local symbol table */ - ERR_PRPDIR = 104, /* invalid preprocessor (#) directive */ - ERR_INCNOFN = 105, /* no filename in #include directive */ - ERR_INCSYN = 106, /* invalid #include syntax */ - ERR_INCSEAR = 107, /* can't find included file */ - ERR_INCMTCH = 108, /* no matching delimiter in #include filename */ - ERR_MANYSYM = 109, /* out of space for symbol table */ - ERR_LONGLIN = 110, /* line too long */ - ERR_INCRPT = 111, /* header file already included - ignored */ - ERR_PRAGMA = 112, /* unknown pragma (ignored) */ - ERR_BADPELSE = 113, /* unexpected #else */ - ERR_BADENDIF = 114, /* unexpected #endif */ - ERR_BADELIF = 115, /* unexpected #elif */ - ERR_MANYPIF = 116, /* #if nesting too deep */ - ERR_DEFREDEF = 117, /* symbol already defined */ - ERR_PUNDEF = 118, /* #undef symbol not defined */ - ERR_NOENDIF = 119, /* missing #endif */ - ERR_MACNEST = 120, /* macros nested too deeply */ - ERR_BADISDEF = 121, /* invalid argument for defined() operator */ - ERR_PIF_NA = 122, /* #if is not implemented */ - ERR_PELIF_NA = 123, /* #elif is not implemented */ - ERR_P_ERROR = 124, /* error directive: %s */ - ERR_LONG_FILE_MACRO = 125, /* __FILE__ expansion too long */ - ERR_LONG_LINE_MACRO = 126, /* __LINE__ expansion too long */ - - /* undo errors */ - ERR_UNDOVF = 200, /* operation is too big for undo log */ - ERR_NOUNDO = 201, /* no more undo information */ - ERR_ICUNDO = 202, /* incomplete undo (no previous savepoint) */ - - /* parser errors */ - ERR_REQTOK = 300, /* expected token (arg 1) - found something else */ - ERR_REQSYM = 301, /* expected symbol */ - ERR_REQPRP = 302, /* expected a property name */ - ERR_REQOPN = 303, /* expected operand */ - ERR_REQARG = 304, /* expected comma or closing paren (arg list) */ - ERR_NONODE = 305, /* no space for new parse node */ - ERR_REQOBJ = 306, /* epxected object name */ - ERR_REQEXT = 307, /* redefining symbol as external function */ - ERR_REQFCN = 308, /* redefining symbol as function */ - ERR_NOCLASS = 309, /* can't use CLASS with function/external function */ - ERR_REQUNO = 310, /* required unary operator */ - ERR_REQBIN = 311, /* required binary operator */ - ERR_INVBIN = 312, /* invalid binary operator */ - ERR_INVASI = 313, /* invalid assignment */ - ERR_REQVAR = 314, /* required variable name */ - ERR_LCLSYN = 315, /* required comma or semicolon in local list */ - ERR_REQRBR = 316, /* required right brace (eof before end of group) */ - ERR_BADBRK = 317, /* 'break' without 'while' */ - ERR_BADCNT = 318, /* 'continue' without 'while' */ - ERR_BADELS = 319, /* 'else' without 'if' */ - ERR_WEQASI = 320, /* warning: possible use of '=' where ':=' intended */ - ERR_EOF = 321, /* unexpected end of file */ - ERR_SYNTAX = 322, /* general syntax error */ - ERR_INVOP = 323, /* invalid operand type */ - ERR_NOMEMLC = 324, /* no memory for new local symbol table */ - ERR_NOMEMAR = 325, /* no memory for argument symbol table */ - ERR_FREDEF = 326, /* redefining a function which is already defined */ - ERR_NOSW = 327, /* 'case' or 'default' and not in switch block */ - ERR_SWRQCN = 328, /* constant required in switch case value */ - ERR_REQLBL = 329, /* label required for 'goto' */ - ERR_NOGOTO = 330, /* 'goto' label never defined */ - ERR_MANYSC = 331, /* too many superclasses for object */ - ERR_OREDEF = 332, /* redefining symbol as object */ - ERR_PREDEF = 333, /* property being redefined in object */ - ERR_BADPVL = 334, /* invalid property value */ - ERR_BADVOC = 335, /* bad vocabulary property value */ - ERR_BADTPL = 336, /* bad template property value (need sstring) */ - ERR_LONGTPL = 337, /* template base property name too long */ - ERR_MANYTPL = 338, /* too many templates (internal compiler limit) */ - ERR_BADCMPD = 339, /* bad value for compound word (sstring required) */ - ERR_BADFMT = 340, /* bad value for format string (sstring needed) */ - ERR_BADSYN = 341, /* invalid value for synonym (sstring required) */ - ERR_UNDFSYM = 342, /* undefined symbol */ - ERR_BADSPEC = 343, /* bad special word */ - ERR_NOSELF = 344, /* "self" not valid in this context */ - ERR_STREND = 345, /* warning: possible unterminated string */ - ERR_MODRPLX = 346, /* modify/replace not allowed with external func */ - ERR_MODFCN = 347, /* modify not allowed with function */ - ERR_MODFWD = 348, /* modify/replace not allowed with forward func */ - ERR_MODOBJ = 349, /* modify can only be used with a defined object */ - ERR_RPLSPEC = 350, /* warning - replacing specialWords */ - ERR_SPECNIL = 351, /* nil only allowed with modify specialWords */ - ERR_BADLCL = 353, /* 'local' statement must precede executable code */ - ERR_IMPPROP = 354, /* implied verifier '%s' is not a property */ - ERR_BADTPLF = 355, /* invalid command template flag */ - ERR_NOTPLFLG = 356, /* flags are not allowed with old file format */ - ERR_AMBIGBIN = 357, /* warning: operator '%s' could be binary */ - ERR_PIA = 358, /* warning: possibly incorrect assignment */ - ERR_BADSPECEXPR = 359, /* invalid speculation evaluation */ - - /* code generation errors */ - ERR_OBJOVF = 400, /* object cannot grow any bigger - code too big */ - ERR_NOLBL = 401, /* no more temporary labels/fixups */ - ERR_LBNOSET = 402, /* (internal error) label never set */ - ERR_INVLSTE = 403, /* invalid datatype for list element */ - ERR_MANYDBG = 404, /* too many debugger line records (internal limit) */ - - /* vocabulary setup errors */ - ERR_VOCINUS = 450, /* vocabulary being redefined for object */ - ERR_VOCMNPG = 451, /* too many vocwdef pages (internal limit) */ - ERR_VOCREVB = 452, /* redefining same verb */ - ERR_VOCREVB2 = 453, /* redefining same verb - two arguments */ - - /* set-up errors */ - ERR_LOCNOBJ = 500, /* location of object %s is not an object */ - ERR_CNTNLST = 501, /* contents of object %s is not list */ - ERR_SUPOVF = 502, /* overflow trying to build contents list */ - ERR_RQOBJNF = 503, /* required object %s not found */ - ERR_WRNONF = 504, /* warning - object %s not found */ - ERR_MANYBIF = 505, /* too many built-in functions (internal error) */ - - /* fio errors */ - ERR_OPWGAM = 600, /* unable to open game for writing */ - ERR_WRTGAM = 601, /* error writing to game file */ - ERR_FIOMSC = 602, /* too many sc's for writing in fiowrt */ - ERR_UNDEFF = 603, /* undefined function */ - ERR_UNDEFO = 604, /* undefined object */ - ERR_UNDEF = 605, /* undefined symbols found */ - ERR_OPRGAM = 606, /* unable to open game for reading */ - ERR_RDGAM = 607, /* error reading game file */ - ERR_BADHDR = 608, /* file has invalid header - not TADS game file */ - ERR_UNKRSC = 609, /* unknown resource type in .gam file */ - ERR_UNKOTYP = 610, /* unknown object type in OBJ resource */ - ERR_BADVSN = 611, /* file saved by different incompatible version */ - ERR_LDGAM = 612, /* error loading object on demand */ - ERR_LDBIG = 613, /* object too big for load region (prob. internal) */ - ERR_UNXEXT = 614, /* did not expect external function */ - ERR_WRTVSN = 615, /* compiler cannot write the requested version */ - ERR_VNOCTAB = 616, /* format version cannot be used with -ctab */ - ERR_BADHDRRSC = 617, /* invalid resource file header in file %s */ - ERR_RDRSC = 618, /* error reading resource file "xxx" */ - - /* character mapping errors */ - ERR_CHRNOFILE = 700, /* unable to load character mapping file */ - - /* user interrupt */ - ERR_USRINT = 990, /* user requested cancel of current operation */ - - /* run-time errors */ - ERR_STKOVF = 1001, /* stack overflow */ - ERR_HPOVF = 1002, /* heap overflow */ - ERR_REQNUM = 1003, /* numeric value required */ - ERR_STKUND = 1004, /* stack underflow */ - ERR_REQLOG = 1005, /* logical value required */ - ERR_INVCMP = 1006, /* invalid datatypes for magnitude comparison */ - ERR_REQSTR = 1007, /* string value required */ - ERR_INVADD = 1008, /* invalid datatypes for '+' operator */ - ERR_INVSUB = 1009, /* invalid datatypes for binary '-' operator */ - ERR_REQVOB = 1010, /* require object value */ - ERR_REQVFN = 1011, /* required function pointer */ - ERR_REQVPR = 1012, /* required property number value */ - - /* non-error conditions: run-time EXIT, ABORT, ASKIO, ASKDO */ - ERR_RUNEXIT = 1013, /* 'exit' statement executed */ - ERR_RUNABRT = 1014, /* 'abort' statement executed */ - ERR_RUNASKD = 1015, /* 'askdo' statement executed */ - ERR_RUNASKI = 1016, /* 'askio' executed; int arg 1 is prep */ - ERR_RUNQUIT = 1017, /* 'quit' executed */ - ERR_RUNRESTART = 1018, /* 'reset' executed */ - ERR_RUNEXITOBJ = 1019, /* 'exitobj' executed */ - - ERR_REQVLS = 1020, /* list value required */ - ERR_LOWINX = 1021, /* index value too low (must be >= 1) */ - ERR_HIGHINX = 1022, /* index value too high (must be <= length(list)) */ - ERR_INVTBIF = 1023, /* invalid type for built-in function */ - ERR_INVVBIF = 1024, /* invalid value for built-in function */ - ERR_BIFARGC = 1025, /* wrong number of arguments to built-in */ - ERR_ARGC = 1026, /* wrong number of arguments to user function */ - ERR_FUSEVAL = 1027, /* string/list not allowed for fuse/daemon arg */ - ERR_BADSETF = 1028, /* internal error in setfuse/setdaemon/notify */ - ERR_MANYFUS = 1029, /* too many fuses */ - ERR_MANYDMN = 1030, /* too many daemons */ - ERR_MANYNFY = 1031, /* too many notifiers */ - ERR_NOFUSE = 1032, /* fuse not found in remfuse */ - ERR_NODMN = 1033, /* daemon not found in remdaemon */ - ERR_NONFY = 1034, /* notifier not found in unnotify */ - ERR_BADREMF = 1035, /* internal error in remfuse/remdaemon/unnotify */ - ERR_DMDLOOP = 1036, /* load-on-demand loop: property not being set */ - ERR_UNDFOBJ = 1037, /* undefined object in vocabulary tree */ - ERR_BIFCSTR = 1038, /* c-string conversion overflows buffer */ - ERR_INVOPC = 1039, /* invalid opcode */ - ERR_RUNNOBJ = 1040, /* runtime error: property taken of non-object */ - ERR_EXTLOAD = 1041, /* unable to load external function "%s" */ - ERR_EXTRUN = 1042, /* error executing external function "%s" */ - ERR_CIRCSYN = 1043, /* circular synonym */ - ERR_DIVZERO = 1044, /* divide by zero */ - ERR_BADDEL = 1045, /* can only delete objects created with "new" */ - ERR_BADNEWSC = 1046, /* superclass for "new" cannot be a new object */ - ERR_VOCSTK = 1047, /* insufficient space in parser stack */ - ERR_BADFILE = 1048, /* invalid file handle */ - - ERR_RUNEXITPRECMD = 1049, /* exited from preCommand */ - - /* run-time parser errors */ - ERR_PRS_SENT_UNK = 1200, /* sentence structure not recognized */ - ERR_PRS_VERDO_FAIL = 1201, /* verDoVerb failed */ - ERR_PRS_VERIO_FAIL = 1202, /* verIoVerb failed */ - ERR_PRS_NO_VERDO = 1203, /* no verDoVerb for direct object */ - ERR_PRS_NO_VERIO = 1204, /* no verIoVerb for direct object */ - ERR_PRS_VAL_DO_FAIL = 1205, /* direct object validation failed */ - ERR_PRS_VAL_IO_FAIL = 1206, /* indirect object validation failed */ - - /* compiler/runtime/debugger driver errors */ - ERR_USAGE = 1500, /* invalid usage */ - ERR_OPNINP = 1501, /* error opening input file */ - ERR_NODBG = 1502, /* game not compiled for debugging */ - ERR_ERRFIL = 1503, /* unable to open error capture file */ - ERR_PRSCXSIZ = 1504, /* parse pool + local size too large */ - ERR_STKSIZE = 1505, /* stack size too large */ - ERR_OPNSTRFIL = 1506, /* error opening string capture file */ - ERR_INVCMAP = 1507, /* invalid character map file */ - - /* debugger errors */ - ERR_BPSYM = 2000, /* symbol not found for breakpoint */ - ERR_BPPROP = 2002, /* breakpoint symbol is not a property */ - ERR_BPFUNC = 2003, /* breakpoint symbol is not a function */ - ERR_BPNOPRP = 2004, /* property is not defined for object */ - ERR_BPPRPNC = 2005, /* property is not code */ - ERR_BPSET = 2006, /* breakpoint already set at this location */ - ERR_BPNOTLN = 2007, /* breakpoint is not at a line (OPCLINE instr) */ - ERR_MANYBP = 2008, /* too many breakpoints */ - ERR_BPNSET = 2009, /* breakpoint to be deleted was not set */ - ERR_DBGMNSY = 2010, /* too many symbols in debug expression (int lim) */ - ERR_NOSOURC = 2011, /* unable to find source file %s */ - ERR_WTCHLCL = 2012, /* illegal to assign to local in watch expr */ - ERR_INACTFR = 2013, /* inactive frame (expression value not available) */ - ERR_MANYWX = 2014, /* too many watch expressions */ - ERR_WXNSET = 2015, /* watchpoint not set */ - ERR_EXTRTXT = 2016, /* extraneous text at end of command */ - ERR_BPOBJ = 2017, /* breakpoint symbol is not an object */ - ERR_DBGINACT = 2018, /* debugger is not active */ - ERR_BPINUSE = 2019, /* breakpoint is already used */ - ERR_RTBADSPECEXPR = 2020, /* invalid speculative expression */ - ERR_NEEDLIN2 = 2021, /* -ds2 information not found - must recompile */ - - /* usage error messages */ - ERR_TCUS1 = 3000, /* first tc usage message */ - ERR_TCUSL = 3024, /* last tc usage message */ - ERR_TCTGUS1 = 3030, /* first tc toggle message */ - ERR_TCTGUSL = 3032, - ERR_TCZUS1 = 3040, /* first tc -Z suboptions usage message */ - ERR_TCZUSL = 3041, - ERR_TC1US1 = 3050, /* first tc -1 suboptions usage message */ - ERR_TC1USL = 3058, - ERR_TCMUS1 = 3070, /* first tc -m suboptions usage message */ - ERR_TCMUSL = 3076, - ERR_TCVUS1 = 3080, /* first -v suboption usage message */ - ERR_TCVUSL = 3082, - ERR_TRUSPARM = 3099, - ERR_TRUS1 = 3100, /* first tr usage message */ - ERR_TRUSL = 3117, - ERR_TRUSFT1 = 3118, /* first tr "footer" message */ - ERR_TRUSFTL = 3119, /* last tr "footer" message */ - ERR_TRSUS1 = 3150, /* first tr -s suboptions usage message */ - ERR_TRSUSL = 3157, - ERR_TDBUSPARM = 3199, - ERR_TDBUS1 = 3200, /* first tdb usage message */ - ERR_TDBUSL = 3214, /* last tdb usage message */ - - /* TR 16-bit MSDOS-specific usage messages */ - ERR_TRUS_DOS_1 = 3300, - ERR_TRUS_DOS_L = 3300, - - /* TR 32-bit MSDOS console mode usage messages */ - ERR_TRUS_DOS32_1 = 3310, - ERR_TRUS_DOS32_L = 3312, - - /* TADS/Graphic errors */ - ERR_GNOFIL = 4001, /* can't find graphics file %s */ - ERR_GNORM = 4002, /* can't find room %s */ - ERR_GNOOBJ = 4003, /* can't find hot spot object %s */ - ERR_GNOICN = 4004 /* can't find icon object %s */ -}; - -/* - * Special error flag - this is returned from execmd() when preparseCmd - * returns a command list. This indicates to voc1cmd that it should try - * the command over again, using the words in the new list. - */ -#define ERR_PREPRSCMDREDO 30000 /* preparseCmd returned a list */ -#define ERR_PREPRSCMDCAN 30001 /* preparseCmd returned 'nil' to cancel */ - -union erradef { - int erraint; // integer argument - char *errastr; // text string argument -}; - -struct errdef { - errdef * errprv; // previous error frame - int errcode; // error code of exception being handled - char errfac[ERRFACMAX+1]; // facility of current error - erradef erraav[10]; // parameters for error - int erraac; // count of parameters in argc -// jmp_buf errbuf; // jump buffer for current error frame -}; - -#define ERRBUFSIZ 512 - -class TADS2; - -// seek location record for an error message by number -struct errmfdef { - uint errmfnum; // error number - size_t errmfseek; // seek location of this message -}; - -class errcxdef { -public: - errdef *errcxptr; // current error frame - void *errcxlgc; // context for error logging callback - int errcxofs; // offset in argument buffer - char errcxbuf[ERRBUFSIZ]; // space for argument strings - Common::SeekableReadStream *errcxfp; // message file, if one is being used - errmfdef *errcxseek; // seek locations of messages in file - uint errcxsksz; // size of errcxseek array - size_t errcxbase; // offset in physical file of logical error file - TADS2 * errcxappctx; // host application context -public: - /** - * Format an error message, sprintf-style, using arguments in an - * erradef array (which is passed to the error-logging callback). - * Returns the length of the output string, even if the actual - * output string was truncated because the outbuf was too short. - * (If called with outbufl == 0, nothing will be written out, but - * the size of the buffer needed, minus the terminating null byte, - * will be computed and returned.) - */ - static int errfmt(char *outbuf, int outbufl, char *fmt, int argc, erradef *argv); - public: - errcxdef() : errcxptr(nullptr), errcxlgc(nullptr), errcxofs(0), - errcxseek(nullptr), errcxsksz(0), errcxbase(0), errcxappctx(nullptr) { - Common::fill(&errcxbuf[0], &errcxbuf[ERRBUFSIZ], '\0'); - } - - /** - * Error logging method - */ - void errcxlog(void *ctx0, char *fac, int err, int argc, erradef *argv); -}; - -// begin protected code -#define ERRBEGIN(ctx) \ - { \ - errdef fr_; \ - if ((fr_.errcode = setjmp(fr_.errbuf)) == 0) \ - { \ - fr_.errprv = (ctx)->errcxptr; \ - (ctx)->errcxptr = &fr_; - -// end protected code, begin error handler -#define ERRCATCH(ctx, e) \ - assert(1==1 && (ctx)->errcxptr != fr_.errprv); \ - (ctx)->errcxptr = fr_.errprv; \ - } \ - else \ - { \ - assert(2==2 && (ctx)->errcxptr != fr_.errprv); \ - (e) = fr_.errcode; \ - (ctx)->errcxptr = fr_.errprv; - -// retrieve argument (int, string) in current error frame -#define errargint(argnum) (fr_.erraav[argnum].erraint) -#define errargstr(argnum) (fr_.erraav[argnum].errastr) - - -#define ERREND(ctx) \ - } \ - } - -// end protected code, begin cleanup (no handling; just cleaning up) -#define ERRCLEAN(ctx) \ - assert((ctx)->errcxptr != fr_.errprv); \ - (ctx)->errcxptr = fr_.errprv; \ - } \ - else \ - { \ - assert((ctx)->errcxptr != fr_.errprv); \ - (ctx)->errcxptr = fr_.errprv; - -#define ERRENDCLN(ctx) \ - errrse(ctx); \ - } \ - } - - - -// argument types for errors with arguments -#define ERRTINT erraint -#define ERRTSTR errastr - -// set argument count in error frame -#define errargc(ctx,cnt) ((ctx)->errcxptr->erraac=(cnt)) - -// enter string argument; returns pointer to argument used in errargv -#ifdef ERR_NO_MACRO -char *errstr(errcxdef *ctx, const char *str, int len); -#else /* ERR_NO_MACRO */ - -#define errstr(ctx,str,len) \ - ((memcpy(&(ctx)->errcxbuf[(ctx)->errcxofs],str,(size_t)len), \ - (ctx)->errcxofs += (len), \ - (ctx)->errcxbuf[(ctx)->errcxofs++] = '\0'), \ - &(ctx)->errcxbuf[(ctx)->errcxofs-(len)-1]) - -#endif /* ERR_NO_MACRO */ - -/* set argument in error frame argument vector */ -#define errargv(ctx,index,typ,arg) \ - ((ctx)->errcxptr->erraav[index].typ=(arg)) - -// signal an error with argument count already set -#ifdef ERR_NO_MACRO -void errsign(errcxdef *ctx, int e, char *facility); -#else /* ERR_NO_MACRO */ -# ifdef DEBUG -void errjmp(jmp_buf buf, int e); -# define errsign(ctx, e, fac) \ - (strncpy((ctx)->errcxptr->errfac, fac, ERRFACMAX),\ - (ctx)->errcxptr->errfac[ERRFACMAX]='\0',\ - (ctx)->errcxofs=0, errjmp((ctx)->errcxptr->errbuf, e)) -# else /* DEBUG */ -# define errsign(ctx, e, fac) \ - (strncpy((ctx)->errcxptr->errfac, fac, ERRFACMAX),\ - (ctx)->errcxptr->errfac[ERRFACMAX]='\0',\ - (ctx)->errcxofs=0, longjmp((ctx)->errcxptr->errbuf, e)) -# endif /* DEBUG */ -#endif /* ERR_NO_MACRO */ - - -// signal an error with no arguments -#ifdef ERR_NO_MACRO -void errsigf(errcxdef *ctx, char *facility, int err); -#else /* ERR_NO_MACRO */ -#define errsigf(ctx, fac, e) (errargc(ctx,0),errsign(ctx,e,fac)) -#endif /* ERR_NO_MACRO */ - -// signal an error with one argument -#define errsigf1(ctx, fac, e, typ1, arg1) \ - (errargv(ctx,0,typ1,arg1),errargc(ctx,1),errsign(ctx,e,fac)) - -// signal an error with two arguments -#define errsigf2(ctx, fac, e, typ1, arg1, typ2, arg2) \ - (errargv(ctx,0,typ1,arg1), errargv(ctx,1,typ2,arg2), \ - errargc(ctx,2), errsign(ctx,e,fac)) - -// resignal the current error - only usable within exception handlers -#ifdef ERR_NO_MACRO -void errrse1(errcxdef *ctx, errdef *fr); -# define errrse(ctx) errrse1(ctx, &fr_) -#else /* ERR_NO_MACRO */ - -// void errrse(errcxdef *ctx); -# define errrse(ctx) \ - (errargc(ctx, fr_.erraac),\ - memcpy((ctx)->errcxptr->erraav, fr_.erraav, \ - (size_t)(fr_.erraac*sizeof(erradef))),\ - errsign(ctx, fr_.errcode, fr_.errfac)) - -#endif /* ERR_NO_MACRO */ - -/** - * For use in an error handler (ERRCATCH..ERREND) only: Copy the - * parameters from the error currently being handled to the enclosing - * frame. This is useful when "keeping" an error being handled - i.e., - * the arguments will continue to be used outside of the - * ERRCATCH..ERREND code. - */ -#define errkeepargs(ctx) errcopyargs(ctx, &fr_) - -/** - * copy the parameters for an error from another frame into the current - * frame - this can be used when we want to be able to display an error - * that occurred in an inner frame within code that is protected by a - * new enclosing error frame - */ -#define errcopyargs(ctx, fr) \ - (errargc((ctx), (fr)->erraac), \ - memcpy((ctx)->errcxptr->erraav, (fr)->erraav, \ - (size_t)((fr)->erraac*sizeof(erradef)))) - -// log error that's been caught, using arguments already caught -#define errclog(ctx) \ - ((*(ctx)->errcxlog)((ctx)->errcxlgc,fr_.errfac,fr_.errcode,\ - fr_.erraac,fr_.erraav)) - -// log an error that's been set up but not signalled yet -#define errprelog(ctx, err) \ - ((*(ctx)->errcxlog)((ctx)->errcxlgc,(ctx)->errcxptr->errfac,\ - err,(ctx)->errcxptr->erraac,\ - (ctx)->errcxptr->erraav)) - -// log an error (no signalling, just reporting) -#ifdef ERR_NO_MACRO -void errlogn(errcxdef *ctx, int err, char *facility); -#else /* ERR_NO_MACRO */ - -#define errlogn(ctx,err,fac) \ - ((ctx)->errcxofs=0,\ - (*(ctx)->errcxlog)((ctx)->errcxlgc,fac,err,(ctx)->errcxptr->erraac,\ - (ctx)->errcxptr->erraav)) - -#endif /* ERR_NO_MACRO */ - -// log an error with no arguments -#ifdef ERR_NO_MACRO -void errlogf(errcxdef *ctx, char *facility, int err); -#else /* ERR_NO_MACRO */ - -// void errlogf(errcxdef *ctx, char *facility, int err); -#define errlogf(ctx,fac,err) (errargc(ctx,0),errlogn(ctx,err,fac)) - -#endif /* ERR_NO_MACRO */ - -// log an error with one argument -#define errlogf1(ctx, fac, e, typ1, arg1) \ - (errargv(ctx,0,typ1,arg1),errargc(ctx,1),errlogn(ctx,e,fac)) - -// log an error with two arguments -#define errlogf2(ctx, fac, e, typ1, arg1, typ2, arg2) \ - (errargv(ctx,0,typ1,arg1),errargv(ctx,1,typ2,arg2),\ - errargc(ctx,2),errlogn(ctx,e,fac)) - -/** - * For compatility with old facility-free mechanism, signal with facility "TADS" - */ -#define errsig(ctx, err) errsigf(ctx, "TADS", err) -#define errsig1(c, e, t, a) errsigf1(c,"TADS",e,t,a) -//#define errsig2(c, e, t1, a1, t2, a2) errsigf2(c,"TADS",e,t1,a1,t2,a2) -#define errlog(c, e) errlogf(c, "TADS", e) -#define errlog1(c, e, t, a) errlogf1(c,"TADS",e,t,a) -#define errlog2(c, e, t1, a1, t2, a2) errlogf2(c,"TADS",e,t1,a1,t2,a2) - -#define errsig2(c, e, t1, a1, t2, a2) error("Error occurred") - -// get the text of an error -void errmsg(errcxdef *ctx, char *outbuf, uint outbufl, uint err); - -// initialize error subsystem, opening error message file if necessary -void errini(errcxdef *ctx, Common::SeekableReadStream *fp); - -// allocate and initialize error context, free error context -errcxdef *lerini(); -void lerfre(errcxdef *ctx); - -// error message structure - number + text -struct errmdef { - uint errmerr; // error number - char *errmtxt; // text of error message -}; - -} // End of namespace TADS2 -} // End of namespace TADS -} // End of namespace Glk - -#endif diff --git a/engines/glk/tads/tads2/lib.h b/engines/glk/tads/tads2/lib.h new file mode 100644 index 0000000000..605f072390 --- /dev/null +++ b/engines/glk/tads/tads2/lib.h @@ -0,0 +1,164 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_TADS_TADS2_LIB +#define GLK_TADS_TADS2_LIB + +#include "common/scummsys.h" +#include "common/algorithm.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* short-hand for various types */ + +typedef unsigned char uchar; +typedef unsigned short ushort; +typedef unsigned long ulong; + +typedef byte ub1; +typedef signed char sb1; +typedef char b1; +typedef unsigned int ub2; +typedef signed int sb2; +typedef int b2; +typedef unsigned long ub4; +typedef signed long sb4; +typedef long b4; +typedef int eword; +typedef int32 int32_t; +typedef uint32 uint32_t; + + +/* maximum/minimum portable values for various types */ +#define UB4MAXVAL 0xffffffffUL +#define UB2MAXVAL 0xffffU +#define UB1MAXVAL 0xffU +#define SB4MAXVAL 0x7fffffffL +#define SB2MAXVAL 0x7fff +#define SB1MAXVAL 0x7f +#define SB4MINVAL (-(0x7fffffff)-1) +#define SB2MINVAL (-(0x7fff)-1) +#define SB1MINVAL (-(0x7f)-1) + +/* clear a struture */ +#define CLRSTRUCT(x) memset(&(x), 0, (size_t)sizeof(x)) +#define CPSTRUCT(dst,src) memcpy(&(dst), &(src), (size_t)sizeof(dst)) + +/* TRUE and FALSE */ +#define TRUE true +#define FALSE false + + +/* bitwise operations */ +#define bit(va, bt) ((va) & (bt)) +#define bis(va, bt) ((va) |= (bt)) +#define bic(va, bt) ((va) &= ~(bt)) + +/* + * noreg/NOREG - use for variables changed in error-protected code that + * are used in error handling code. Use 'noreg' on the declaration like + * a storage class qualifier. Use 'NOREG' as an argument call, passing + * the addresses of all variables declared noreg. For non-ANSI + * compilers, a routine osnoreg(/o_ void *arg0, ... _o/) must be + * defined. + */ +#ifdef OSANSI +# define noreg volatile +# define NOREG(arglist) +#else /* OSANSI */ +# define noreg +# define NOREG(arglist) osnoreg arglist ; +#endif /* OSANSI */ + +/* + * Linting directives. You can define these before including this file + * if you have a fussy compiler. + */ +#ifdef LINT +# ifndef NOTREACHED +# define NOTREACHED return +# endif +# ifndef NOTREACHEDV +# define NOTREACHEDV(t) return((t)0) +# endif +# ifndef VARUSED +# define VARUSED(v) varused(v) +# endif +void varused(); +#else /* LINT */ +# ifndef NOTREACHED +# define NOTREACHED +# endif +# ifndef NOTREACHEDV +# define NOTREACHEDV(t) +# endif +# ifndef VARUSED +# define VARUSED(v) +# endif +#endif /* LINT */ + +/* conditionally compile code if debugging is enabled */ +#ifdef DEBUG +# define IF_DEBUG(x) x +#else /* DEBUG */ +# define IF_DEBUG(x) +#endif /* DEBUG */ + +#ifndef offsetof +# define offsetof(s_name, m_name) (size_t)&(((s_name *)0)->m_name) +#endif /* offsetof */ + +/* + * Define our own version of isspace(), so that we don't try to interpret + * anything outside of the normal ASCII set as spaces. + */ +#define t_isspace(c) \ + (((unsigned char)(c)) <= 127 && isspace((unsigned char)(c))) + + +/* round a size to worst-case alignment boundary */ +#define osrndsz(s) (((s)+3) & ~3) + +/* round a pointer to worst-case alignment boundary */ +#define osrndpt(p) ((uchar *)((((unsigned long)(p)) + 3) & ~3)) + +/* read unaligned portable unsigned 2-byte value, returning int */ +#define osrp2(p) READ_LE_UINT16(p) + +/* read unaligned portable signed 2-byte value, returning int */ +#define osrp2s(p) READ_LE_INT16(p) + +/* write int to unaligned portable 2-byte value */ +#define oswp2(p, i) WRITE_LE_UINT16(p, i) +#define oswp2s(p, i) WRITE_LE_INT16(p, i) + +/* read unaligned portable 4-byte value, returning unsigned long */ +#define osrp4(p) READ_LE_UINT32(p) +#define osrp4s(p) READ_LE_INT32(p) + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/line_source.h b/engines/glk/tads/tads2/line_source.h new file mode 100644 index 0000000000..a7e5454c60 --- /dev/null +++ b/engines/glk/tads/tads2/line_source.h @@ -0,0 +1,129 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* Line source definitions + * + * A line source is a mechanism for reading source text. The tokenizer + * reads its input from a line source. This is the basic class + * definition; individual line sources will define the functions and + * class data needed. + */ + +#ifndef GLK_TADS_TADS2_LINE_SOURCE +#define GLK_TADS_TADS2_LINE_SOURCE + +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/object.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + + +/** + * Line source superclass structure + */ +struct lindef { + int (*lingetp)(lindef *lin); /* get next line */ + void (*linclsp)(lindef *lin); /* close line source */ + void (*linppos)(lindef *lin, char *buf, uint buflen); + /* write printable rep of position to buf (for error reporting) */ + void (*linglop)(lindef *lin, uchar *buf); + /* generate a line record for an OPCLINE instruction */ + int (*linwrtp)(lindef *lin, osfildef *fp); + /* write line source information to binary file; TRUE ==> error */ + void (*lincmpp)(lindef *lin, uchar *buf); + /* give location of compiled code for current line to line source */ + void (*linactp)(lindef *lin); /* activate for dbg */ + void (*lindisp)(lindef *lin); /* disactivate */ + void (*lintellp)(lindef *lin, uchar *pos); /* get position */ + void (*linseekp)(lindef *lin, uchar *pos); /* seek */ + int (*linreadp)(lindef *lin, uchar *buf, uint siz); /* fread */ + void (*linpaddp)(lindef *lin, uchar *pos, long delta); + /* add an offset to a position value */ + int (*linqtopp)(lindef *lin, uchar *pos); /* at top? */ + int (*lingetsp)(lindef *lin, uchar *buf, uint siz); /* get line */ + void (*linnamp)(lindef *lin, char *buf); /* get source name */ + void (*linfindp)(lindef *lin, char *buf, objnum *objp, + uint *ofsp); /* find nearest line record */ + void (*lingotop)(lindef *lin, int where); /* seek global */ + long (*linofsp)(lindef *lin); /* byte offset in line source */ + void (*linrenp)(lindef *lin, objnum oldnum, objnum newnum); + /* renumber an object (for "modify") */ + void (*lindelp)(lindef *lin, objnum objn); + /* delete an object (for "replace") */ + ulong (*linlnump)(lindef *lin); /* get the current line number */ +#define LINGOTOP OSFSK_SET /* go to top of line source */ +#define LINGOEND OSFSK_END /* go to end of line source */ + lindef *linpar; /* parent of current line source */ + lindef *linnxt; /* next line in line source chain */ + int linid; /* serial number of line source (for debugger) */ + char *linbuf; /* pointer to current line */ + ushort linflg; /* flags */ +#define LINFEOF 0x01 /* line source is at end of file */ +#define LINFMORE 0x02 /* there's more to the line than linlen */ +#define LINFDBG 0x04 /* debug record already generated for line */ +#define LINFNOINC 0x08 /* ignore # directives from this line source */ +#define LINFCMODE 0x10 /* line source is parsed in C-mode */ + ushort linlen; /* length of the line */ + ushort linlln; /* length of line record generated by lingloc */ +}; + +/** + * Maximum allowed value for linlln, in bytes. This allows subsystems + * that need to maintain local copies of seek locations to know how big + * an area to allocate for them. + */ +#define LINLLNMAX 20 + +/* macros to cover calls to functions */ +#define linget(lin) ((*((lindef *)(lin))->lingetp)((lindef *)(lin))) +#define lincls(lin) ((*((lindef *)(lin))->linclsp)((lindef *)(lin))) +#define linppos(lin, buf, buflen) \ + ((*((lindef *)(lin))->linppos)((lindef *)(lin), buf, buflen)) +#define linglop(lin, buf) ((*((lindef *)(lin))->linglop)(lin, buf)) +#define linwrt(lin, fp) ((*((lindef *)(lin))->linwrtp)(lin, fp)) +#define lincmpinf(lin, buf) ((*((lindef *)(lin))->lincmpp)(lin, buf)) +#define linactiv(lin) ((*((lindef *)(lin))->linactp)(lin)) +#define lindisact(lin) ((*((lindef *)(lin))->lindisp)(lin)) +#define lintell(lin, pos) ((*((lindef *)(lin))->lintellp)(lin, pos)) +#define linseek(lin, pos) ((*((lindef *)(lin))->linseekp)(lin, pos)) +#define linread(lin, buf, siz) ((*((lindef *)(lin))->linreadp)(lin, buf, siz)) +#define linpadd(lin, pos, delta) \ + ((*((lindef *)(lin))->linpaddp)(lin, pos, delta)) +#define linqtop(lin, pos) ((*((lindef *)(lin))->linqtopp)(lin, pos)) +#define lingets(lin, buf, siz) ((*((lindef *)(lin))->lingetsp)(lin, buf, siz)) +#define linnam(lin, buf) ((*((lindef *)(lin))->linnamp)(lin, buf)) +#define linlnum(lin) ((*((lindef *)(lin))->linlnump)(lin)) +#define linfind(lin, buf, objp, ofsp) \ + ((*((lindef *)(lin))->linfindp)(lin, buf, objp, ofsp)) +#define lingoto(lin, where) ((*((lindef *)(lin))->lingotop)(lin, where)) +#define linofs(lin) ((*((lindef *)(lin))->linofsp)(lin)) +#define linrenum(lin, oldnum, newnum) \ + ((*((lindef *)(lin))->linrenp)(lin, oldnum, newnum)) +#define lindelnum(lin, objn) ((*((lindef *)(lin))->lindelp)(lin, objn)) + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/memory_cache.cpp b/engines/glk/tads/tads2/memory_cache.cpp new file mode 100644 index 0000000000..3bab3b034b --- /dev/null +++ b/engines/glk/tads/tads2/memory_cache.cpp @@ -0,0 +1,31 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/tads2/memory_cache.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/tads2/memory_cache.h b/engines/glk/tads/tads2/memory_cache.h new file mode 100644 index 0000000000..ec0383b7e1 --- /dev/null +++ b/engines/glk/tads/tads2/memory_cache.h @@ -0,0 +1,403 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * Memory cache manager + */ + +#ifndef GLK_TADS_TADS2_MEMORY_CACHE +#define GLK_TADS_TADS2_MEMORY_CACHE + +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/memory_cache_loader.h" +#include "glk/tads/tads2/memory_cache_swap.h" +#include "glk/tads/osfrobtads.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* if the os layer doesn't want long mcm macros, we don't either */ +#ifdef OS_MCM_NO_MACRO +# define MCM_NO_MACRO +#endif + +/* + * mcmon - cache object number. Each object in the cache is referenced + * by an object number, which is a number of this type. + */ +typedef ushort mcmon; + +/* invalid object number - used to indicate N/A or no object */ +#define MCMONINV ((mcmon)~0) + +/* invalid page number */ +#define MCMPINV ((uint)~0) + +union mcmodefloc { + mcsseg mcmolocs; /* swap segment handle */ + mclhd mcmolocl; /* load file object handle */ +}; + +/* + * mcmodef - cache object definition. Each cache object is managed by + * this structure. Pointers for a doubly-linked list are provided; + * these are used to maintain a recent-use list for objects in memory, + * and to maintain a free list for cache object entries not in use. A + * set of flag bits is provided, as is the size of the object and its + * location (which is a memory pointer for objects in memory, a swap + * file handle for swapped-out objects, or a load-file handle for + * objects that are unloaded). + */ +struct mcmodef { + uchar *mcmoptr; /* memory pointer (if object is present) */ + mcmodefloc mcmoloc; /* object's external location */ +#define mcmoswh mcmoloc.mcmolocs +#define mcmoldh mcmoloc.mcmolocl + mcmon mcmonxt; /* next object in this list */ + mcmon mcmoprv; /* previous object in this list */ + ushort mcmoflg; /* flags for this cache object */ +#define MCMOFDIRTY 0x01 /* object has been written */ +#define MCMOFNODISC 0x02 /* not in load file (can't be discarded) */ +#define MCMOFLOCK 0x04 /* object is locked */ +#define MCMOFPRES 0x08 /* object is present in memory */ +#define MCMOFLRU 0x10 /* object is in LRU chain */ +#define MCMOFPAGE 0x20 /* object is a cache manager page */ +#define MCMOFNOSWAP 0x40 /* object cannot be swapped out */ +#define MCMOFFREE 0x80 /* entry refers to a free block of memory */ +#define MCMOFREVRT 0x100 /* call revert callback upon loading */ + uchar mcmolcnt; /* lock count */ + ushort mcmosiz; /* size of the object */ +}; + +/* heap header - allocate one of these in each heap */ +struct mcmhdef { + mcmhdef *mcmhnxt; /* next heap in this chain */ +}; + +/* GLOBAL cache manager context: tracks cache manager state */ +struct mcmcx1def { + mcmodef **mcmcxtab; /* page table for the cache */ + errcxdef *mcmcxerr; /* error handling context */ + mcmhdef *mcmcxhpch; /* heap chain pointer */ + mcscxdef mcmcxswc; /* swap manager context */ + mclcxdef mcmcxldc; /* loader context */ + ulong mcmcxmax; /* maximum amount of actual heap we can ever alloc */ + mcmon mcmcxlru; /* least recently used object still in memory */ + mcmon mcmcxmru; /* most recently used object */ + mcmon mcmcxfre; /* head of free list */ + mcmon mcmcxunu; /* head of unused list */ + ushort mcmcxpage; /* last page table slot used */ + ushort mcmcxpgmx; /* maximum number of pages we can allocate */ + void (*mcmcxcsw)(mcmcx1def *, mcmon, mcsseg, mcsseg); + /* change swap handle in object to new swap handle */ +}; + +/* CLIENT cache manager context: used by client to request mcm services */ +struct mcmcxdef { + mcmcx1def *mcmcxgl; /* global cache manager context */ + uint mcmcxflg; /* flags */ + uint mcmcxmsz; /* maximum size of mapping table */ + void (*mcmcxldf)(void *ctx, mclhd handle, uchar *ptr, + ushort siz); /* callback to load objects */ + void *mcmcxldc; /* context for load callback */ + void (*mcmcxrvf)(void *ctx, mcmon objn); /* revert object */ + void *mcmcxrvc; /* context for revert callback */ + mcmon *mcmcxmtb[1]; /* mapping table */ +}; + +/* context flags */ +#define MCMCXF_NO_PRP_DEL 0x0001 /* PRPFDEL is invalid in this game file */ + +/* convert from a client object number to a global object number */ +/* mcmon mcmc2g(mcmcxdef *ctx, mcmon objn); */ +#define mcmc2g(ctx, objn) ((ctx)->mcmcxmtb[(objn)>>8][(objn)&255]) + +/* + * FREE LIST: this is a list, headed by context->mcmcxfre and chained + * forward and back by mcmonxt and mcmoprv, consisting of free memory + * blocks. These refer to blocks in the heap that are not used by any + * client objects. + */ +/* + * UNUSED LIST: this is a list, headed by context->mcmcxunu and chained + * forward by mcmonxt (not back, because it's never necessary to take + * anything out of the list except at the head, nor to search the list + * backwards), of unused cache object entries. These entries are not + * associated with any client object or with any heap memory. This list + * is used to get a new cache object header, and deleted cache objects + * are placed onto this list. + */ +/* + * LRU LIST: this is a list of in-memory blocks in ascending order of + * recency of use by the client. Each time a client unlocks a block, + * the block is moved to the most recent position in the list (the end + * of the list). To make it fast to add a new object, we keep a pointer + * to the end of the list as well as to the beginning. The start of the + * list is at context->mcmcxlru, and is the least recently unlocked + * block still in memory. The end of the list is at context->mcmcxmru, + * and is the most recently unlocked block. + */ + +/* + * initialize the cache manager, returning a context for cache manager + * operations; a null pointer is returned if insufficient heap memory is + * available for initialization. The 'max' argument specifies the + * maximum amount of actual low-level heap memory that the cache manager + * can ever allocate on behalf of this context (of course, it can + * overcommit the heap through swapping). If 'max' is less than the + * size of a single heap allocation, it is adjusted upwards to that + * minimum. + */ +mcmcx1def *mcmini(ulong max, uint pages, ulong swapsize, + osfildef *swapfp, char *swapfilename, errcxdef *errctx); + +/* terminate the cache manager - frees the structure and all cache memory */ +void mcmterm(mcmcx1def *ctx); + +/* allocate a client context */ +mcmcxdef *mcmcini(mcmcx1def *globalctx, uint pages, + void (*loadfn)(void *, mclhd, uchar *, ushort), + void *loadctx, + void (*revertfn)(void *, mcmon), void *revertctx); + +/* terminate a client context - frees the structure memory */ +void mcmcterm(mcmcxdef *ctx); + + +/* + * Lock a cache object, bringing it into memory if necessary. Returns + * a pointer to the memory containing the object. A null pointer is + * returned in case of error. The object remains fixed in memory at the + * returned location until unlocked. Locks are stacked; that is, if + * an object is locked twice in succession, it needs to be unlocked + * twice in succession before it is actually unlocked. + */ +#ifdef MCM_NO_MACRO +uchar *mcmlck(mcmcxdef *ctx, mcmon objnum); +#else /* MCM_NO_MACRO */ + +/* uchar *mcmlck(mcmcxdef *ctx, mcmon objnum); */ +#define mcmlck(ctx,num) \ + ((mcmobje(ctx,num)->mcmoflg & MCMOFPRES ) ? \ + ((mcmobje(ctx,num)->mcmoflg|=MCMOFLOCK), \ + ++(mcmobje(ctx,num)->mcmolcnt), mcmobje(ctx,num)->mcmoptr) \ + : mcmload(ctx,num)) + +#endif /* MCM_NO_MACRO */ + +/* + * Unlock a cache object, allowing it to be moved and swapped. + * Unlocking an object moves it to the end (i.e., most recently used) + * position on the LRU chain, making it the least favorable to swap out + * or discard. This happens at unlock time (rather than lock time) + * because presumably the client has been using the object the entire + * time it was locked. For this reason, and to keep memory unfragmented + * as much as possible, objects should not be kept locked except when + * actually in use. Note that locks nest; if an object is locked three + * times without an intervening unlock, it must be unlocked three times + * in a row. An object can be unlocked even if it's not locked; doing + * so has no effect. + */ +#ifdef MCM_NO_MACRO +void mcmunlck(mcmcxdef *ctx, mcmon objnum); +#else /* MCM_NO_MACRO */ + +/* void mcmunlck(mcmcxdef *ctx, mcmon objnum); */ +#define mcmunlck(ctx,obj) \ + ((mcmobje(ctx,obj)->mcmoflg & MCMOFLOCK) ? \ + (--(mcmobje(ctx,obj)->mcmolcnt) ? (void)0 : \ + ((mcmobje(ctx,obj)->mcmoflg&=(~MCMOFLOCK)), \ + mcmuse((ctx)->mcmcxgl,mcmc2g(ctx,obj)))) : (void)0) + +#endif /* MCM_NO_MACRO */ + +/* + * Allocate a new cache object. The new object is locked upon return. + * A pointer to the memory for the new object is returned, and + * the object number is returned at *nump. A null pointer is returned + * if the object cannot be allocated. + */ +/* uchar *mcmalo(mcmcxdef *ctx, ushort siz, mcmon *nump); */ +#define mcmalo(ctx, siz, nump) mcmalo0(ctx, siz, nump, MCMONINV, FALSE) + +/* + * Reserve space for an object, giving it a particular client object + * number. This doesn't actually allocate any space for the object, but + * just sets it up so that it can be loaded by the client when it's + * needed. + */ +void mcmrsrv(mcmcxdef *ctx, ushort siz, mcmon clinum, mclhd loadhd); + +/* + * Allocate a new cache object, and associate it with a particular + * client object number. An error is signalled if the client object + * number is already in use. + */ +/* uchar *mcmalonum(mcmcxdef *ctx, ushort siz, mcmon num); */ +#define mcmalonum(ctx, siz, num) mcmalo0(ctx, siz, (mcmon *)0, num, FALSE) + +/* + * Reallocate an existing object. The object's size is increased + * or reduced according to newsize. The object is locked if it is + * not already, and the address of the object's memory is returned. + * Note that the object can move when reallocated, even if it was + * locked before the call. + */ +uchar *mcmrealo(mcmcxdef *ctx, mcmon objnum, ushort newsize); + +/* + * Touch a cache object (rendering it dirty). When an object is + * written, the client must touch it to ensure that the version in + * memory is not discarded. The cache manager attempts to optimize + * activity by not writing objects that can be reconstructed from the + * load or swap file. Touching the object informs the cache manager + * that the object is different from any version it has in a swap or + * load file. + */ +/* void mcmtch(mcmcxdef *ctx, mcmon objnum); */ +#define mcmtch(ctx,obj) \ + (mcmobje(ctx,obj)->mcmoflg |= MCMOFDIRTY) +/* was: (mcmobje(ctx,obj)->mcmoflg |= (MCMOFDIRTY | MCMOFNODISC)) */ + +/* get size of a cache manager object - object need not be locked */ +/* ushort mcmobjsiz(mcmcxdef *ctx, mcmon objn); */ +#define mcmobjsiz(ctx, objn) (mcmobje(ctx, objn)->mcmosiz) + +/* determine if object has ever been touched */ +/* int mcmobjdirty(mcmcxdef *ctx, mcmon objn); */ +#define mcmobjdirty(ctx, objn) \ + (mcmobje(ctx, objn)->mcmoflg & (MCMOFDIRTY | MCMOFNODISC)) + +/* get object's memory pointer - object must be locked for valid result */ +/* uchar *mcmobjptr(mcmcxdef *ctx, mcmon objn); */ +#define mcmobjptr(ctx, objn) (mcmobje(ctx, objn)->mcmoptr) + +/* + * Free an object. The memory occupied by the object is discarded, and + * the object may no longer be referenced. + */ +void mcmfre(mcmcxdef *ctx, mcmon obj); + +/* + * "Revert" an object - convert it back to original state. This + * routine just invokes a client callback to do the actual reversion + * work. The callback is called immediately if the object is already + * present in memory, but is deferred until the object is loaded/swapped + * in if the object is not in memory. + */ +/* void mcmrevert(mcmcxdef *ctx, mcmon objn); */ +#define mcmrevert(ctx, objn) \ + ((mcmobje(ctx, objn)->mcmoflg & MCMOFPRES) ? \ + ((*(ctx)->mcmcxrvf)((ctx)->mcmcxrvc, objn), DISCARD 0) \ + : DISCARD (mcmobje(ctx, objn)->mcmoflg |= MCMOFREVRT)) + +/* get current size of object cache */ +ulong mcmcsiz(mcmcxdef *ctx); + +/* change an object's swap handle (used by swapper) */ +/* void mcmcsw(mcmcx1def *ctx, ushort objn, mcsseg swapn, mcsseg oldswn); */ +#define mcmcsw(ctx, objn, swapn, oldswapn) \ + ((*(ctx)->mcmcxcsw)(ctx, objn, swapn, oldswapn)) + +/* ------------------------------- PRIVATE ------------------------------- */ + +/* Unlock an object by its global handle */ +#ifdef MCM_NO_MACRO +void mcmgunlck(mcmcx1def *ctx, mcmon objnum); +#else /* MCM_NO_MACRO */ + +/* void mcmgunlck(mcmcx1def *ctx, mcmon objnum); */ +#define mcmgunlck(ctx,obj) \ + ((mcmgobje(ctx,obj)->mcmoflg & MCMOFLOCK) ? \ + (--(mcmgobje(ctx,obj)->mcmolcnt) ? (void)0 : \ + ((mcmgobje(ctx,obj)->mcmoflg&=(~MCMOFLOCK)), mcmuse(ctx,obj))) : \ + (void)0) + +#endif /* MCM_NO_MACRO */ + +/* real memory allocator; clients use cover macros */ +uchar *mcmalo0(mcmcxdef *ctx, ushort siz, mcmon *nump, mcmon clinum, + int noclitrans); + +/* free an object by global object number */ +void mcmgfre(mcmcx1def *ctx, mcmon obj); + +/* "use" an object (move to most-recent position in LRU chain) */ +void mcmuse(mcmcx1def *ctx, mcmon n); + +/* + * Load or swap in a cache object which is currently unloaded, locking + * it before returning. Returns a pointer to the memory containing + * the object, or a null pointer in case of error. The object + * remains fixed in memory at the returned location until unlocked. + */ +uchar *mcmload(mcmcxdef *ctx, mcmon objnum); + +/* + * Size of each chunk of memory we'll request from the heap manager. + * To cut down on wasted memory from the heap manager, we'll always make + * our requests in this large size, then sub-allocate out of these + * chunks. + */ +#define MCMCHUNK 32768 + +/* + * number of cache entries in a page - make this a power of 2 to keep + * the arithmetic to find a cache object entry simple + */ +#define MCMPAGECNT 256 + +/* + * size of a page, in bytes + */ +#define MCMPAGESIZE (MCMPAGECNT * sizeof(mcmodef)) + +/* + * When allocating memory, and we find a free block satisfying the + * request, we will split the free block if doing so would result in + * enough space in the second block. MCMSPLIT specifies the minimum + * size left over that will allow a split to occur. + */ +#define MCMSPLIT 64 + +/* get an object cache entyr given a GLOBAL object number */ +#define mcmgobje(ctx,num) (&((ctx)->mcmcxtab[(num)>>8][(num)&255])) + +/* get an object cache entry given a CLIENT object number */ +/* mcmodef *mcmobje(mcmcxdef *ctx, mcmon objnum) */ +#define mcmobje(ctx,num) mcmgobje((ctx)->mcmcxgl,mcmc2g(ctx,num)) + +/* allocate a block that will be locked for its entire lifetime */ +void *mcmptralo(mcmcxdef *ctx, ushort siz); + +/* free a block allocated with mcmptralo */ +void mcmptrfre(mcmcxdef *ctx, void *block); + +/* change an object's swap handle */ +void mcmcswf(mcmcx1def *ctx, mcmon objn, mcsseg swapn, mcsseg oldswapn); + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/memory_cache_heap.cpp b/engines/glk/tads/tads2/memory_cache_heap.cpp new file mode 100644 index 0000000000..fae3c5e080 --- /dev/null +++ b/engines/glk/tads/tads2/memory_cache_heap.cpp @@ -0,0 +1,51 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/tads2/memory_cache_heap.h" +#include "glk/tads/tads2/error.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* global to keep track of all allocations */ +IF_DEBUG(ulong mchtotmem;) + +uchar *mchalo(errcxdef *ctx, size_t siz, char *comment) { + uchar *ret; + + VARUSED(comment); + IF_DEBUG(mchtotmem += siz;) + + ret = (uchar *)osmalloc(siz); + if (ret) + return(ret); + else { + errsig(ctx, ERR_NOMEM); + NOTREACHEDV(uchar *); + return 0; + } +} + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/tads2/memory_cache_heap.h b/engines/glk/tads/tads2/memory_cache_heap.h new file mode 100644 index 0000000000..269cdec3ad --- /dev/null +++ b/engines/glk/tads/tads2/memory_cache_heap.h @@ -0,0 +1,60 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * Memory cache heap manager + * + * This is the low-level heap manager, which maintains a list of non-relocatable, + * non-swappable blocks of memory. The cache manager uses the heap manager for + * its basic storage needs. + */ + +#ifndef GLK_TADS_TADS2_MEMORY_CACHE_HEAP +#define GLK_TADS_TADS2_MEMORY_CACHE_HEAP + +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/error_handling.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/** + * Allocate a block of memory; returns pointer to the block. + * An out-of-memory error is signalled if insufficient memory + * is available. The comment is for debugging purposes only. + */ +uchar *mchalo(errcxdef *ctx, size_t siz, char *comment); + +/* allocate a structure */ +#define MCHNEW(errctx, typ, comment) \ + ((typ *)mchalo(errctx, sizeof(typ), comment)) + +/* free a block of memory */ +/* void mchfre(uchar *ptr); */ +#define mchfre(ptr) (osfree(ptr)) + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/memory_cache_loader.cpp b/engines/glk/tads/tads2/memory_cache_loader.cpp new file mode 100644 index 0000000000..f403299167 --- /dev/null +++ b/engines/glk/tads/tads2/memory_cache_loader.cpp @@ -0,0 +1,31 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/tads2/memory_cache_loader.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/tads2/memory_cache_loader.h b/engines/glk/tads/tads2/memory_cache_loader.h new file mode 100644 index 0000000000..c7ec22db57 --- /dev/null +++ b/engines/glk/tads/tads2/memory_cache_loader.h @@ -0,0 +1,57 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * Memory cache swap manager + * + * The cache swap manager provides swap file services to the memory + * cache manager. The cache manager calls the swap manager to write + * objects to the swap file and read in previously swapped-out objects. + */ + +#ifndef GLK_TADS_TADS2_MEMORY_CACHE_LOADER +#define GLK_TADS_TADS2_MEMORY_CACHE_LOADER + +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/error_handling.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/** + * Loader context + */ +struct mclcxdef { + errcxdef *mclcxerr; /* error handling context */ +}; + +/** + * Loader handle + */ +typedef ulong mclhd; /* essentially a seek address */ + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/memory_cache_swap.cpp b/engines/glk/tads/tads2/memory_cache_swap.cpp new file mode 100644 index 0000000000..114a7258e9 --- /dev/null +++ b/engines/glk/tads/tads2/memory_cache_swap.cpp @@ -0,0 +1,31 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/tads2/memory_cache_swap.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/tads2/memory_cache_swap.h b/engines/glk/tads/tads2/memory_cache_swap.h new file mode 100644 index 0000000000..f1b9849aec --- /dev/null +++ b/engines/glk/tads/tads2/memory_cache_swap.h @@ -0,0 +1,122 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * Memory cache swap manager + * + * The cache swap manager provides swap file services to the memory + * cache manager. The cache manager calls the swap manager to write + * objects to the swap file and read in previously swapped-out objects. + */ + +#ifndef GLK_TADS_TADS2_MEMORY_CACHE_SWAP +#define GLK_TADS_TADS2_MEMORY_CACHE_SWAP + +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/error_handling.h" +#include "glk/tads/osfrobtads.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* Forward declarations */ +struct mcmcx1def; + +/** + * swap segment descriptor + */ +struct mcsdsdef { + ulong mcsdsptr; /* seek pointer in swap file */ + ushort mcsdssiz; /* size of this swap segment */ + ushort mcsdsosz; /* size of object written to segment */ + uint mcsdsobj; /* client object ID */ + ushort mcsdsflg; /* flags */ +#define MCSDSFINUSE 0x01 /* segment is in use */ +}; + +/** + * mcsseg - swap segment handle. All swap-file segments are addressed + * through this handle type. + */ +typedef ushort mcsseg; + +/** + * Swap manager context + */ +struct mcscxdef { + osfildef *mcscxfp; /* swap file handle */ + char *mcscxfname; /* name of swap file */ + errcxdef *mcscxerr; /* error handling context */ + ulong mcscxtop; /* top of swap file allocated so far */ + ulong mcscxmax; /* maximum size of swap file we're allowed */ + mcsdsdef **mcscxtab; /* swap descriptor page table */ + mcsseg mcscxmsg; /* maximum segment allocated so far */ + mcmcx1def *mcscxmem; /* memory manager context */ +}; + +#define MCSSEGINV ((mcsseg)~0) /* invalid segment ID - error indicator */ + +/* initialize swapper - returns 0 for success, other for error */ +void mcsini(struct mcscxdef *ctx, struct mcmcx1def *gmemctx, ulong maxsiz, + osfildef *fp, char *swapfilename, struct errcxdef *errctx); + +/* close swapper (release memory areas) */ +void mcsclose(struct mcscxdef *ctx); + +/** + * Swap an object out. The caller specifies the location and size of + * the object, as well as a unique handle (arbitrary, up to the caller; + * the only requirement is that it be unique among all caller objects + * and always the same for a particular caller's object) and the + * previous swap handle if the object ever had one. If the object is + * not dirty (it hasn't been written since being swapped in), and the + * swap manager hasn't reused the swap slot, the swap manager doesn't + * need to write the memory, since it already has a copy on disk; + * instead, it can just mark the slot as back in use. If the caller + * doesn't wish to take advantage of this optimization, always pass in + * dirty == TRUE, which will force a write regardless of the object ID. + */ +mcsseg mcsout(struct mcscxdef *ctx, uint objid, uchar *objptr, + ushort objsize, mcsseg oldswapseg, int dirty); + +/* Swap an object in */ +void mcsin(struct mcscxdef *ctx, mcsseg swapseg, uchar *objptr, ushort size); + + +/* number of page pointers in page table (max number of pages) */ +#define MCSPAGETAB 256 + +/* number of swap descriptors in a page */ +#define MCSPAGECNT 256 + +/* find swap descriptor corresponding to swap segment number */ +#define mcsdsc(ctx,seg) (&(ctx)->mcscxtab[(seg)>>8][(seg)&255]) + +/* write out a swap segment */ +void mcswrt(mcscxdef *ctx, mcsdsdef *desc, uchar *buf, ushort bufl); + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/object.cpp b/engines/glk/tads/tads2/object.cpp new file mode 100644 index 0000000000..2ba1161d86 --- /dev/null +++ b/engines/glk/tads/tads2/object.cpp @@ -0,0 +1,35 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/tads2/object.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + + + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/object.h b/engines/glk/tads/tads2/object.h new file mode 100644 index 0000000000..5e2499ccc2 --- /dev/null +++ b/engines/glk/tads/tads2/object.h @@ -0,0 +1,396 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_TADS_TADS2_OBJECT +#define GLK_TADS_TADS2_OBJECT + +#include "glk/tads/tads.h" +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/memory_cache.h" +#include "glk/tads/tads2/property.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/** + * object number + */ +typedef ushort objnum; + +/** + * For non-class objects, we'll leave some space free in the object so + * that a few properties can be added without having to resize the + * object. Class objects will probably never have anything added, so + * there's no need for extra space. + */ +#define OBJEXTRA 64 + +# define OBJFCLS 0x01 /* object is a class */ + +/** + * The object structure is actually laid out portably, using unaligned + * 2-byte arrays, stored least significant byte first, for each ushort + * (including the objnum array for the superclasses). The actual + * entries are at these offsets on all machines: + * + * objws 0 + * objflg 2 + * objnsc 4 + * objnprop 6 + * objfree 8 + * objrst 10 + * objstat 12 + * objsc[0] 14 + * objsc[1] 16 + * etc + * + * If the OBJFINDEX flag is set, the object has a property index. + * The index occurs after the last superclass (so it's where the + * property data would go if there were no index), and the property + * data follows. Each index entry consists of a pair of two-byte + * entries: the first is the property number, and the second is + * its offset within the object. For performance reasons, an index + * is only built on a class object -- whenever a property is changed + * within an object, the entire index must be rebuilt, because the + * locations of many properties within the object can be changed by + * a single property change in the object. The index is ordered by + * property number, so it can be searched using a binary search. + * Furthermore, "ignored" properties are excluded from the index; + * only the active instance of a particular property is stored. + * The index must be maintained by all routines that can change + * property information: setp, delp, revert, etc. + * + * Preceding the index table is a two-byte entry that gives the + * offset of the properties. Since the properties immediately + * follow the index, this can be used to deduce how large a space + * is available for the index itself. + */ +typedef uchar objdef; +#define OBJDEFSIZ 14 /* "sizeof(objdef)" - size of object header w/o sc's */ + +/* object flags */ +#define OBJFCLASS 1 /* object is a class */ +#define OBJFINDEX 2 /* object has a property index */ +#define OBJFMOD 4 /* object has been modified by a newer definition */ + +/* undo context */ +struct objucxdef { + mcmcxdef *objucxmem; /* cache manager context */ + errcxdef *objucxerr; /* error context */ + ushort objucxsiz; /* size of the undo buffer */ + ushort objucxhead; /* head (position of next write) */ + ushort objucxtail; /* tail (position of oldest record) */ + ushort objucxprv; /* previous head pointer */ + ushort objucxtop; /* highest head value written */ + void (*objucxcun)(void *ctx, uchar *data); + /* apply a client undo record */ + ushort (*objucxcsz)(void *ctx, uchar *data); + /* get size of a client undo record */ + void *objucxccx; /* client undo context */ + uchar objucxbuf[1]; /* undo buffer */ +}; + +/* + * Undo records are kept in a circular buffer allocated as part of an + * undo context. Offsets within the buffer are kept for the head, tail, + * and previous head records. The head always points to the byte at + * which the next undo record will be written. The previous head points + * to the most recently written undo record; it contains a back link to + * the undo record before that, and so forth back through the entire + * chain. (These reverse links are necessary because undo records vary + * in size depending on the data contained within.) The tail points to + * the oldest undo record that's still in the buffer. Conceptually, the + * head is always "above" the tail in the buffer; since the buffer is + * circular, the tail may have a higher address, but this just means + * that the buffer wraps around at the top. When the head bumps into + * the tail (i.e., the head address is physically below or equal to the + * tail address, and the head is then advanced so that its address + * becomes higher than the tail's), the tail is advanced by discarding + * as many of the least recent undo records as necessary to make room + * for the new head position. When the head and the previous head point + * to the same place, we have no undo records in the buffer. + */ +/** + * The first byte of an undo record specifies what action is to be + * undone. If a property was added, it is undone merely by deleting the + * property. If a property was changed, it is undone by setting the + * property back to its old value. An additional special flag indicates + * a "savepoint." Normally, all changes back to a savepoint will be + * undone. + */ +#define OBJUADD 1 /* a property was added (undo by deleting) */ +#define OBJUCHG 2 /* a property was changed (change back to old value) */ +#define OBJUSAV 3 /* savepoint marker (no property information) */ +#define OBJUOVR 4 /* override original property (set orig to IGNORE) */ +#define OBJUCLI 5 /* client undo record (any client data) */ + +/* + * After the control byte (OBJUxxx), the object number, property + * number, datatype, and data value will follow; some or all of these + * may be omitted, depending on the control byte. + */ + +/* get object flags */ +#define objflg(o) ((ushort)osrp2(((char *)(o)) + 2)) + +/* get object flags */ +#define objsflg(o, val) oswp2(((char *)(o)) + 2, val) + +/* given an object pointer, get a pointer to the first prpdef */ +/* prpdef *objprp(objdef *objptr); */ +#define objprp(o) ((prpdef *)(objsc(o) + 2*objnsc(o))) + +/* given an object pointer, get number of properties in the prpdef */ +/* int objnprop(objdef *objptr); */ +#define objnprop(o) ((ushort)osrp2(((char *)(o)) + 6)) + +/* set number of properties */ +/* void objsnp(objdef *objptr, int newnum); */ +#define objsnp(o,n) oswp2(((char *)(o)) + 6, n) + +/* given an object pointer, get offset of free space */ +/* int objfree(objdef *objptr); */ +#define objfree(o) ((ushort)osrp2(((char *)(o)) + 8)) + +/* set free space pointer */ +/* void objsfree(objdef *objptr, int newfree); */ +#define objsfree(o,n) oswp2(((char *)(o)) + 8, n) + +/* get number of static properties */ +/* ushort objstat(objdef *objptr); */ +#define objstat(o) ((ushort)osrp2(((char *)(o)) + 10)) + +/* set number of static properties */ +/* void objsetst(objdef *objptr, int newstat); */ +#define objsetst(o,n) oswp2(((char *)(o)) + 10, n) + +/* get reset size (size of static properties) */ +/* ushort objrst(objdef *objptr); */ +#define objrst(o) ((ushort)osrp2(((char *)(o)) + 12)) + +/* set reset size */ +/* void objsetrst(objdef *objptr, uint newrst); */ +#define objsetrst(o,n) oswp2(((char *)(o)) + 12, n) + +/* given an object pointer, get first superclass pointer */ +/* uchar *objsc(objdef *objptr); */ +#define objsc(o) (((uchar *)(o)) + OBJDEFSIZ) + +/* given an object pointer, get number of superclasses */ +/* int objnsc(objdef *objptr); */ +#define objnsc(o) ((ushort)osrp2(((char *)(o)) + 4)) + +/* set number of superclasses */ +/* void objsnsc(objdef *objptr, int num); */ +#define objsnsc(o,n) oswp2(((char *)(o)) + 4, n) + +/* given a prpdef, get the next prpdef */ +/* prpdef *objpnxt(prpdef *p); */ +#define objpnxt(p) \ + ((prpdef *)(((uchar *)(p)) + PRPHDRSIZ + prpsize(p))) + +/* get pointer to free prpdef */ +/* prpdef *objpfre(objdef *objptr); */ +#define objpfre(o) ((prpdef *)(((uchar *)(o)) + objfree(o))) + +/* given a prpdef and an object pointer, compute the prpdef offset */ +/* uint objpofs(objdef *objptr, prpdef *propptr); */ +#define objpofs(o,p) ((uint)((p) ? (((uchar *)(p)) - ((uchar *)(o))) : 0)) + +/* given an object pointer and a property offset, get prpdef pointer */ +/* prpdef *objofsp(objdef *objptr, uint propofs); */ +#define objofsp(o,ofs) ((prpdef *)((ofs) ? (((uchar *)(o)) + (ofs)) : 0)) + +/* + * Get the first superclass of an object. If it doesn't have any + * superclasses, return invalid. + */ +objnum objget1sc(mcmcxdef *ctx, objnum objn); + +/* + * Get an object's property WITHOUT INHERITANCE. If the object has the + * indicated property set, the byte OFFSET of the prpdef within the + * object is returned. The offset will remain valid until any type of + * operation that sets a property in the object (such as objdelp, + * objsetp, or an undo operation). An offset of zero means that the + * property was not set in the object. + */ +uint objgetp(mcmcxdef *ctx, objnum objn, prpnum prop, + dattyp *typptr); + +/* + * Get the *ending* offset of the given property's value, without any + * inheritance. Returns the byte offset one past the end of the + * property's data. + */ +uint objgetp_end(mcmcxdef *ctx, objnum objn, prpnum prop); + +/* + * Get a property of an object, either from the object or from a + * superclass (inherited). If the inh flag is TRUE, we do not look + * at all in the object itself, but restrict our search to inherited + * properties only. We return the byte OFFSET of the prpdef within + * the object in which the prpdef is found; the superclass object + * itself is NOT locked upon return, but we will NOT unlock the + * object passed in. If the offset is zero, the property was not + * found. The offset returned is valid until any operation that + * sets a property in the object (such as objdelp, objsetp, or an + * undo operation). + */ +uint objgetap(mcmcxdef *ctx, noreg objnum objn, prpnum prop, + objnum *orn, int inh); + +/* + * expand an object by a requested amount, returning a pointer to the + * object's new location if it must be moved. The object will be + * unlocked and relocked by this call. On return, the actual amount + * of space ADDED to the object will be returned. + */ +objdef *objexp(mcmcxdef *ctx, objnum obj, ushort *siz); + +/* + * Set an object's property, deleting the original value of the + * property if it existed. If an undo context is provided, write an + * undo record for the change; if the undo context pointer is null, no + * undo information is retained. + */ +void objsetp(mcmcxdef *ctx, objnum obj, prpnum prop, + dattyp typ, void *val, objucxdef *undoctx); + +/* + * Delete a property. If mark_only is true, we'll only mark the + * property as deleted without actually reclaiming its space; this is + * necessary when removing a code property (type DAT_CODE) any time + * other code properties may follow, because p-code is not entirely + * self-relative and thus can't always be relocated within an object. + */ +void objdelp(mcmcxdef *mctx, objnum objn, prpnum prop, int mark_only); + +/* + * Set up for emitting code into an object. Writes a property header + * of type 'code', and returns the offset of the next free byte in the + * object. Call objendemt when done. The datatype argument is + * provided so that list generation can be done through the same + * mechanism, since parser lists must be converted to run-time + * lists via the code generator. + */ +uint objemt(mcmcxdef *ctx, objnum objn, prpnum prop, dattyp typ); + +/* done emitting code into property, finish setting object info */ +void objendemt(mcmcxdef *ctx, objnum objn, prpnum prop, uint endofs); + +/* + * Determine if undo records should be kept. Undo records should be + * kept only if a savepoint is present in the undo log. If no savepoint + * is present, adding undo records would be useless, since it will not + * be possible to apply the undo information. + */ +int objuok(objucxdef *undoctx); + +/* + * Reserve space in an undo buffer, deleting old records as needed. + * Returns a pointer to the reserved space. + */ +uchar *objures(objucxdef *undoctx, uchar cmd, ushort siz); + +/* advance the tail pointer in an undo buffer over the record it points to */ +void objutadv(objucxdef *undoctx); + +/* apply one undo record, and remove it from undo list */ +void obj1undo(mcmcxdef *mctx, objucxdef *undoctx); + +/* + * Undo back to the most recent savepoint. If there is no savepoint in + * the undo list, NOTHING will be undone. This prevents reaching an + * inconsistent state in which some, but not all, of the operations + * between two savepoints are undone: either all operations between two + * savepoints will be undone, or none will. + */ +void objundo(mcmcxdef *mctx, objucxdef *undoctx); + +/* set an undo savepoint */ +void objusav(objucxdef *undoctx); + +/* initialize undo context */ +objucxdef *objuini(mcmcxdef *memctx, ushort undosiz, + void (*undocb)(void *ctx, uchar *data), + ushort (*sizecb)(void *ctx, uchar *data), + void *callctx); + +/* free the undo context - releases memory allocated by objuini() */ +void objuterm(objucxdef *undoctx); + +/* discard all undo context (for times such as restarting) */ +void objulose(objucxdef *undoctx); + +/* + * Allocate and initialize a new object. The caller specifies the + * number of superclasses to be allocated, and the amount of space (in + * bytes) for the object's property data. The caller must fill in the + * superclass array. Upon return, the object is allocated and locked, + * and is initialized with no properties. A pointer to the object's + * memory is returned, and *objnptr receives the object number. + */ +objdef *objnew(mcmcxdef *mctx, int sccnt, ushort propspace, + objnum *objnptr, int classflg); + +/* initialize an already allocated object */ +void objini(mcmcxdef *mctx, int sccnt, objnum objn, int classflg); + +/* + * Add space for additional superclasses to an object. The object can + * already have some properties set (if it doesn't, it can just be + * reinitialized). + */ +void objaddsc(mcmcxdef *mctx, int sccnt, objnum objn); + +/* + * Delete an object's properties and superclasses. The 'mindel' + * parameter specifies the minimum property number to be deleted. + * Properties below this are considered "system" properties that are not + * to be deleted. This could be used by a development environment to + * store the source for an object as a special system property in the + * object; when the object is recompiled, all of the object's properties + * and superclasses must be deleted except the source property, which is + * retained even after recompilation. + */ +void objclr(mcmcxdef *mctx, objnum objn, prpnum mindel); + +/* Build or rebuild an object's property index */ +void objindx(mcmcxdef *mctx, objnum objn); + +/* set up just-compiled object: mark static part and original properties */ +void objcomp(mcmcxdef *mctx, objnum objn, int for_debug); + +/* revert an object to original post-compilation state */ +void objrevert(void *mctx, mcmon objn); + +/* reset 'ignore' flags for a newly reconstructed object */ +void objsetign(mcmcxdef *mctx, objnum objn); + + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/opcode_defs.h b/engines/glk/tads/tads2/opcode_defs.h new file mode 100644 index 0000000000..32519ae8f7 --- /dev/null +++ b/engines/glk/tads/tads2/opcode_defs.h @@ -0,0 +1,218 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_TADS_TADS2_OPCODE_DEFS +#define GLK_TADS_TADS2_OPCODE_DEFS + +/* + * Opcode definitions + * + * Lifted largely from the old TADS, since the basic run - time interpreter's + * operation is essentially the same. + */ + +#include "glk/tads/tads.h" +#include "glk/tads/tads2/data.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +#define OPCPUSHNUM 1 /* push a constant numeric value */ +#define OPCPUSHOBJ 2 /* push an object */ +#define OPCNEG 3 /* unary negation */ +#define OPCNOT 4 /* logical negation */ +#define OPCADD 5 /* addition/list concatenation */ +#define OPCSUB 6 /* subtraction/list difference */ +#define OPCMUL 7 /* multiplication */ +#define OPCDIV 8 /* division */ +#define OPCAND 9 /* logical AND */ +#define OPCOR 10 /* logical OR */ +#define OPCEQ 11 /* equality */ +#define OPCNE 12 /* inequality */ +#define OPCGT 13 /* greater than */ +#define OPCGE 14 /* greater or equal */ +#define OPCLT 15 /* less than */ +#define OPCLE 16 /* less or equal */ +#define OPCCALL 17 /* call a function */ +#define OPCGETP 18 /* get property */ +#define OPCGETPDATA 19 /* get a property, allowing only data values */ +#define OPCGETLCL 20 /* get a local variable's value */ +#define OPCPTRGETPDATA 21 /* get property via pointer; only allow data */ +#define OPCRETURN 22 /* return without a value */ +#define OPCRETVAL 23 /* return a value */ +#define OPCENTER 24 /* enter a function */ +#define OPCDISCARD 25 /* discard top of stack */ +#define OPCJMP 26 /* unconditional jump */ +#define OPCJF 27 /* jump if false */ +#define OPCPUSHSELF 28 /* push current object */ +#define OPCSAY 29 /* implicit printout for doublequote strings */ +#define OPCBUILTIN 30 /* call a built-in function */ +#define OPCPUSHSTR 31 /* push a string */ +#define OPCPUSHLST 32 /* push a list */ +#define OPCPUSHNIL 33 /* push the NIL value */ +#define OPCPUSHTRUE 34 /* push the TRUE value */ +#define OPCPUSHFN 35 /* push the address of a function */ +#define OPCGETPSELFDATA 36 /* push property of self; only allow data */ + +#define OPCPTRCALL 38 /* call function pointed to by top of stack */ +#define OPCPTRINH 39 /* inherit pointer to property (stack=prop) */ +#define OPCPTRGETP 40 /* get property by pointer (stack=obj,prop) */ + +#define OPCPASS 41 /* pass to inherited handler */ +#define OPCEXIT 42 /* exit turn, but continue with fuses/daemons */ +#define OPCABORT 43 /* abort turn, skipping fuses/daemons */ +#define OPCASKDO 44 /* ask for a direct object */ +#define OPCASKIO 45 /* ask for indirect object and set preposition */ + +/* explicit superclass inheritance opcodes */ +#define OPCEXPINH 46 /* "inherited ." */ +#define OPCEXPINHPTR 47 /* "inherited ." */ + +/* + * Special opcodes for peephole optimization. These are essentially + * pairs of operations that occur frequently so have been collapsed into + * a single instruction. + */ +#define OPCCALLD 48 /* call function and discard value */ +#define OPCGETPD 49 /* evaluate property and discard any value */ +#define OPCBUILTIND 50 /* call built-in function and discard value */ + +#define OPCJE 51 /* jump if equal */ +#define OPCJNE 52 /* jump if not equal */ +#define OPCJGT 53 /* jump if greater than */ +#define OPCJGE 54 /* jump if greater or equal */ +#define OPCJLT 55 /* jump if less than */ +#define OPCJLE 56 /* jump if less or equal */ +#define OPCJNAND 57 /* jump if not AND */ +#define OPCJNOR 58 /* jump if not OR */ +#define OPCJT 59 /* jump if true */ + +#define OPCGETPSELF 60 /* get property of the 'self' object */ +#define OPCGETPSLFD 61 /* get property of 'self' and discard result */ +#define OPCGETPOBJ 62 /* get property of a given object */ + /* note: differs from GETP in that object is */ + /* encoded into the instruction */ +#define OPCGETPOBJD 63 /* get property of an object and discard result */ +#define OPCINDEX 64 /* get an indexed entry from a list */ + +#define OPCPUSHPN 67 /* push a property number */ + +#define OPCJST 68 /* jump and save top-of-stack if true */ +#define OPCJSF 69 /* jump and save top-of-stack if false */ +#define OPCJMPD 70 /* discard stack and then jump unconditionally */ + +#define OPCINHERIT 71 /* inherit a property from superclass */ +#define OPCCALLEXT 72 /* call external function */ +#define OPCDBGRET 73 /* return to debugger (no stack frame leaving) */ + +#define OPCCONS 74 /* construct list from top two stack elements */ +#define OPCSWITCH 75 /* switch statement */ + +#define OPCARGC 76 /* get argument count */ +#define OPCCHKARGC 77 /* check actual arguments against formal count */ + +#define OPCLINE 78 /* line record */ +#define OPCFRAME 79 /* local variable frame record */ +#define OPCBP 80 /* breakpoint - replaces an OPCLINE instruction */ +#define OPCGETDBLCL 81 /* get debugger local */ +#define OPCGETPPTRSELF 82 /* get property pointer from self */ +#define OPCMOD 83 /* modulo */ +#define OPCBAND 84 /* binary AND */ +#define OPCBOR 85 /* binary OR */ +#define OPCXOR 86 /* binary XOR */ +#define OPCBNOT 87 /* binary negation */ +#define OPCSHL 88 /* bit shift left */ +#define OPCSHR 89 /* bit shift right */ + +#define OPCNEW 90 /* create new object */ +#define OPCDELETE 91 /* delete object */ + + +/* ----- opcodes 192 and above are reserved for assignment operations ----- */ + +/* +ASSIGNMENT OPERATIONS + When (opcode & 0xc0 == 0xc0), we have an assignment operation. + (Note that this means that opcodes from 0xc0 up are all reserved + for assignment operations.) The low six bits of the opcode + specify exactly what kind of operation is to be performed: + + bits 0-1: specifies destination type: + 00 2-byte operand is local number + 01 2-byte operand is property to set in obj at tos + 10 tos is index, [sp-1] is list to be indexed and set + 11 tos is property pointer, [sp-1] is object + + bits 2-4: specifies assignment operation: + 000 := (direct assignment) + 001 += (add tos to destination) + 010 -= (subtract tos from destination) + 011 *= (multiply destination by tos) + 100 /= (divide destination by tos) + 101 ++ (increment tos) + 110 -- (decrement tos) + 111 *reserved* + + bit 5: specifies what to do with value computed by assignment + 0 leave on stack (implies pre increment/decrement) + 1 discard (implies post increment/decrement) +*/ +#define OPCASI_MASK 0xc0 /* assignment instruction */ + +#define OPCASIDEST_MASK 0x03 /* mask to get destination field */ +#define OPCASILCL 0x00 /* assign to a local */ +#define OPCASIPRP 0x01 /* assign to an object.property */ +#define OPCASIIND 0x02 /* assign to an element of a list */ +#define OPCASIPRPPTR 0x03 /* assign property via pointer */ + +#define OPCASITYP_MASK 0x1c /* mask to get assignment type field */ +#define OPCASIDIR 0x00 /* direct assignment */ +#define OPCASIADD 0x04 /* assign and add */ +#define OPCASISUB 0x08 /* assign and subtract */ +#define OPCASIMUL 0x0c /* assign and multiply */ +#define OPCASIDIV 0x10 /* assign and divide */ +#define OPCASIINC 0x14 /* increment */ +#define OPCASIDEC 0x18 /* decrement */ +#define OPCASIEXT 0x1c /* other - extension flag */ + +/* extended assignment flags - next byte when OPCASIEXT is used */ +#define OPCASIMOD 1 /* modulo and assign */ +#define OPCASIBAND 2 /* binary AND and assign */ +#define OPCASIBOR 3 /* binary OR and assign */ +#define OPCASIXOR 4 /* binary XOR and assign */ +#define OPCASISHL 5 /* shift left and assign */ +#define OPCASISHR 6 /* shift right and assign */ + + +#define OPCASIPRE_MASK 0x20 /* mask for pre/post field */ +#define OPCASIPOST 0x00 /* increment after push */ +#define OPCASIPRE 0x20 /* increment before push */ + +/* some composite opcodes for convenience */ +#define OPCSETLCL (OPCASI_MASK | OPCASILCL | OPCASIDIR) + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/os.cpp b/engines/glk/tads/tads2/os.cpp index 15a291a87b..e51a4d938e 100644 --- a/engines/glk/tads/tads2/os.cpp +++ b/engines/glk/tads/tads2/os.cpp @@ -26,6 +26,7 @@ namespace Glk { namespace TADS { namespace TADS2 { +#if 0 OS::OS(OSystem *syst, const GlkGameDescription &gameDesc) : TADS(syst, gameDesc), status_mode(0) { Common::fill(&status_left[0], &status_left[OSS_STATUS_STRING_LEN], '\0'); @@ -345,6 +346,7 @@ int OS::oss_getc_from_window(winid_t win) { buffered_char = oss_convert_keystroke_to_tads(ev.val1); return 0; } +#endif } // End of namespace TADS2 } // End of namespace TADS diff --git a/engines/glk/tads/tads2/os.h b/engines/glk/tads/tads2/os.h index 45ab804f95..5dcc83c935 100644 --- a/engines/glk/tads/tads2/os.h +++ b/engines/glk/tads/tads2/os.h @@ -20,219 +20,5137 @@ * */ +/* Portable interfaces to OS-specific functions + * + * This file defines interfaces to certain functions that must be called + * from portable code, but which must have system-specific implementations. + */ + #ifndef GLK_TADS_TADS2_OS #define GLK_TADS_TADS2_OS -#include "glk/tads/tads.h" -#include "glk/tads/tads2/types.h" +#include "common/system.h" +#include "glk/tads/osfrobtads.h" +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/appctx.h" namespace Glk { namespace TADS { namespace TADS2 { -/** - * Operating system compatibility layer - */ -class OS : public TADS { -protected: - char status_left[OSS_STATUS_STRING_LEN]; - char status_right[OSS_STATUS_STRING_LEN]; - int status_mode; -protected: - /** - * Constructor - */ - OS(OSystem *syst, const GlkGameDescription &gameDesc); - - /** - * Terminates the game - */ - void os_terminate(int rc); - - /** - * \defgroup Type Conversions - * @{ - */ - - /** - * Change a TADS prompt type (OS_AFP_*) into a Glk prompt type. - */ - uint oss_convert_prompt_type(int type); - - /** - * Change a TADS file type (OSFT*) into a Glk file type. - */ - uint oss_convert_file_type(int type); - - /** - * Change a fileref ID (frefid_t) to a special string and put it in the - * buffer which is passed to it. The string is given by - * OSS_FILEREF_STRING_PREFIX + 'nnnnn' + OSS_FILEREF_STRING_SUFFIX - * where 'nnnnn' is the frefid_t pointer converted into a string of decimal - * numbers. This is only really practical for 32-bit pointers; if we use - * 64-bit pointers I'll have to start using a hash table or use hex - * numbers. - */ - uint oss_convert_fileref_to_string(frefid_t file_to_convert, char *buffer, int buf_len); - - /** - * Turn a filename or a special fileref string into an actual fileref. - * Notice that, since Glk doesn't know paths, we take this opportunity to - * call oss_check_path, which should do the OS-dependent path changing - * in the event that the filename contains path information - */ - frefid_t oss_convert_string_to_fileref(char *buffer, uint usage); - - /** - * Tell us if the passed string is a hashed fileref or not - */ - bool oss_is_string_a_fileref(char *buffer); - - /** - * Change a Glk key into a TADS one, using the CMD_xxx codes - */ - unsigned char oss_convert_keystroke_to_tads(uint key); - - /**@}*/ - - /** - * \defgroup Directory/File methods - * @{ - */ - - /** - * If a filename contains path information, change dirs to that path. - * Returns true if the path was fiddled with - */ - bool oss_check_path(char *filename); - - /** - * In case we changed directories in oss_check_path, change back to the - * original executable directory - */ - void oss_revert_path(); - - /** - * Open a stream, given a string, usage, and a filemode. tadsusage is the - * TADS filemode (OSFT*); tbusage is either fileusage_TextMode or - * fileusage_BinaryMode (from Glk). - */ - osfildef *oss_open_stream(char *buffer, uint tadsusage, uint tbusage, - uint fmode, uint rock); - - /** - * Get a pointer to the root name portion of a filename. This is the part - * of the filename after any path or directory prefix. For example, on - * Unix, given the string "/home/mjr/deep.gam", this function should return - * a pointer to the 'd' in "deep.gam". If the filename doesn't appear to - * have a path prefix, it should simply return the argument unchanged. - */ - const char *os_get_root_name(const char *buf) const { return buf; } - - /** - * Open a file for access - */ - osfildef *osfoprb(const char *fname, uint typ = 0); - - /** - * Receive notification that a character mapping file has been loaded. We - * don't need to do anything with this information, since we we're relying - * on the Glk layer and ScummVM backend to handle all that - */ - void os_advise_load_charmap(const char *id, const char *ldesc, const char *sysinfo) { - // No implementation needed - } - - /** - * Generate a filename for a character mapping table. On Windows, the - * filename is always simply "win" plus the internal ID plus ".tcp". - */ - void os_gen_charmap_filename(char *filename, const char *internal_id, - const char *argv0); - - /**@}*/ - - /** - * \defgroup The main text area print routines - * @{ - */ - - /** - * Process hilighting codes while printing a string - */ - void oss_put_string_with_hilite(winid_t win, const char *str, size_t len); - - /** - * Status line handling - */ - void oss_draw_status_line(); - - void oss_change_status_string(char *dest, const char *src, size_t len); - void oss_change_status_left(const char *str, size_t len); - void oss_change_status_right(const char *str); - int os_get_status() const { return status_mode; } - - /** - * Flush the output - */ - void os_flush(); - - /** - * Print a null terminated string - */ - void os_printz(const char *str) { os_print(str, strlen(str)); } - - /** - * Print a string - */ - void os_print(const char *str, size_t len); - - /**@}*/ - - /** - * \defgroup Memory routines - * @{ - */ - - /** - * Compare two strings - */ - int memicmp(const char *s1, const char *s2, int len); - - /**@}*/ - - /** - * \defgroup Input routines - * @{ - */ - - /** - * Wait for a key to be hit - */ - void os_waitc(void) { os_getc(); } - - /** - * Get a character from the keyboard. For extended characters, return 0, - * then return the extended key at the next call to this function - */ - int os_getc() { - return oss_getc_from_window(story_win); - } - - /** - * Accept a keystroke in the passed window - */ - int oss_getc_from_window(winid_t win); - - - /** - * Print a message (with os_print) and wait for a key - */ - void os_expause(); - - /**@}*/ + +/* ------------------------------------------------------------------------ */ +/* + * A note on character sets: + * + * Except where noted, all character strings passed to and from the + * osxxx functions defined herein use the local operating system + * representation. On a Windows machine localized to Eastern Europe, + * for example, the character strings passed to and from the osxxx + * functions would use single-byte characters in the Windows code page + * 1250 representation. + * + * Callers that use multiple character sets must implement mappings to + * and from the local character set when calling the osxxx functions. + * The osxxx implementations are thus free to ignore any issues related + * to character set conversion or mapping. + * + * The osxxx implementations are specifically not permitted to use + * double-byte Unicode as the native character set, nor any other + * character set where a null byte could appear as part of a non-null + * character. In particular, callers may assume that null-terminated + * strings passed to and from the osxxx functions contain no embedded + * null bytes. Multi-byte character sets (i.e., character sets with + * mixed single-byte and double-byte characters) may be used as long as + * a null byte is never part of any multi-byte character, since this + * would guarantee that a null byte could always be taken as a null + * character without knowledge of the encoding or context. + */ + +/* ------------------------------------------------------------------------ */ +/* + * "Far" Pointers. Most platforms can ignore this. For platforms with + * mixed-mode addressing models, where pointers of different sizes can + * be used within a single program and hence some pointers require + * qualification to indicate that they use a non-default addressing + * model, the keyword OSFAR should be defined to the appropriate + * compiler-specific extension keyword. + * + * If you don't know what I'm talking about here, you should just ignore + * it, because your platform probably doesn't have anything this + * sinister. As of this writing, this applies only to MS-DOS, and then + * only to 16-bit implementations that must interact with other 16-bit + * programs via dynamic linking or other mechanisms. + */ + + +/* ------------------------------------------------------------------------ */ +/* + * ANSI C99 exact-size integer types. + * + * C99 defines a set of integer types with exact bit sizes, named intXX_t + * for a signed integer with XX bits, and uintXX_t for unsigned. XX can be + * 8, 16, 32, or 64. TADS uses the 16- and 32-bit sizes, so each platform + * is responsible for defining the following types: + * + *. int16_t - a signed integer type storing EXACTLY 16 bits + *. uint16_t - an unsigned integer type storing EXACTLY 16 bits + *. int32_t - a signed integer type storing EXACTLY 32 bits + *. uint32_t - an unsigned integer type storing EXACTLY 32 bits + * + * Many modern compilers provide definitions for these types via the + * standard header stdint.h. Where stdint.h is provided, the platform code + * can merely #include . + * + * For compilers where stdint.h isn't available, you must provide suitable + * typedefs. Note that the types must be defined with the exact bit sizes + * specified; it's not sufficient to use a bigger type, because we depend + * in some cases on overflow and sign extension behavior at the specific + * bit size. + */ + + +/* ------------------------------------------------------------------------ */ +/* + * Thread-local storage (TLS). + * + * When TADS is compiled with threading support, it requires some variables + * to be "thread-local". This means that the variables have global scope + * (so they're not stored in "auto" variables on the stack), but each + * thread has a private copy of each such variable. + * + * Nearly all systems that support threads also support thread-local + * storage. Like threading support itself, though, TLS support is at + * present implemented only in non-portable OS APIs rather than standard C + * language features. TLS is a requirement if TADS is compiled with + * threading, but it's not needed for non-threaded builds. TADS only + * requires threading at present (version 3.1) for its network features; + * since these features are optional, systems that don't have threading and + * TLS support will simply need to disable the network features, which will + * allow all of the threading and TLS definitions in osifc to be omitted. + * + * There appear to be two common styles of TLS programming models. The + * first provides non-standard compiler syntax for declarative creation of + * thread-local variables. The Microsoft (on Windows) and Gnu compilers + * (on Linux and Unix) do this: they provide custom storage class modifiers + * for declaring thread locals (__declspec(thread) for MSVC, __thread for + * gcc). Compilers that support declarative thread locals handle the + * implementation details through code generation, so the program merely + * needs to add the special TLS storage class qualifier to an otherwise + * ordinary global variable declaration, and then can access the thread + * local as though it were an ordinary global. + * + * The second programming model is via explicit OS API calls to create, + * initialize, and access thread locals. pthreads provides such an API, as + * does Win32. In fact, when you use the declarative syntax with MSVC or + * gcc, the compiler generates the appropriate API calls, but the details + * are transparent to the program; in contrast, when using pthreads + * directly, the program must actively call the relevant APIs. + * + * It's probably the case that every system that has compiler-level support + * for declarative thread local creation also has procedural APIs, so the + * simplest way to abstract the platform differences would be to do + * everything in terms of APIs. However, it seems likely that compilers + * with declarative syntax might be able to generate more efficient code, + * since optimizers always benefit from declarative information. So we'd + * like to use declarative syntax whenever it's available, but fall back on + * explicit API calls when it's not. So our programming model is a union + * of the two styles: + * + * 1. For each thread local, declare the thread local: + *. OS_DECL_TLS(char *, my_local); + * + * 2. At main program startup (for the main thread only), initialize each + * thread local: + *. os_tls_create(my_local); + * + * 3. Never get or set the value of a thread local directly; instead, use + * the get/set functions: + *. char *x = os_tls_get(char *, my_local); + *. os_tls_set(my_local, "hello"); + * + * One key feature of this implementation is that each thread local is + * stored as a (void *) value. We do it this way to allow a simple direct + * mapping to the pthreads APIs, since that's going to be the most common + * non-declarative implementation. This means that a thread local variable + * can contain any pointer type, but *only* a pointer type. The standard + * pattern for dealing with anything more ocmplex is the same as in + * pthreads: gather up the data into a structure, malloc() an instance of + * that structure at entry to each thread (including the main thread), and + * os_tls_set() the variable to contain a pointer to that structure. From + * then on, use os_tls_set(my_struct *, my_local)->member to access the + * member variables in the structure. And finally, each thread must delete + * the structure at thread exit. + */ + +/* + * + * Declare a thread local. + * + * - For compilers that support declarative TLS variables, the local OS + * headers should use the compiler support by #defining OS_DECL_TLS to the + * appropriate local declarative keyword. + * + * - For systems without declarative TLS support but with TLS APIs, the + * global declared by this macro actually stores the slot ID (what pthreads + * calls the "key") for the variable. This macro should therefore expand + * to a declaration of the appropriate API type for a slot ID; for example, + * on pthreads, #define OS_DECL_TLS(t, v) pthread_key_t v. + * + * - For builds with no thread support, simply #define this to declare the + * variable as an ordinary global: #define OS_DECL_TLS(t, v) t v. + */ +/* #define OS_DECL_TLS(typ, varname) __thread typ varname */ + +/* + * For API-based systems without declarative support in the compiler, the + * main program startup code must explicitly create a slot for each thread- + * local variable by calling os_tls_create(). The API returns a slot ID, + * which is shared among threads and therefore can be stored in an ordinary + * global variable. OS_DECL_TLS will have declared the global variable + * name in this case as an ordinary global of the slot ID type. The + * os_tls_create() macro should therefore expand to a call to the slot + * creation API, storing the new slot ID in the global. + * + * Correspondingly, before the main thread exits, it should delete each + * slot it created, b calling os_tls_delete(). + * + * For declarative systems, there's no action required here, so these + * macros can be defined to empty. + */ +/* #define os_tls_create(varname) pthread_key_create(&varname, NULL) */ +/* #define os_tls_delete(varname) pthread_key_delete(varname) */ + + +/* + * On API-based systems, each access to get or set the thread local + * requires an API call, using the slot ID stored in the actual global to + * get the per-thread instance of the variable's storage. + *. #define os_tls_get(typ, varname) ((typ)pthread_getspecific(varname)) + *. #define os_tls_set(varname, val) pthread_setspecific(varname, val) + * + * On declarative systems, the global variable itself is the thread local, + * so get/set can be implemented as direct access to the variable. + *. #define os_tls_get(typ, varname) varname + *. #define os_tls_set(varname, val) varname = (val) + */ + +/* + * Common TLS definitions - declarative thread locals + * + * For systems with declarative TLS support in the compiler, the OS header + * can #define OS_DECLARATIVE_TLS to pick up suitable definitions for the + * os_tls_xxx() macros. The OS header must separately define OS_DECL_TLS + * as appropriate for the local system. + */ +#ifdef OS_DECLARATIVE_TLS +#define os_tls_create(varname) +#define os_tls_delete(varname) +#define os_tls_get(typ, varname) varname +#define os_tls_set(varname, val) varname = (val) +#endif + +/* + * Common TLS definitions - pthreads + * + * For pthreads systems without declarative TLS support in the compiler, + * the OS header can simply #define OS_PTHREAD_TLS to pick up the standard + * definitions below. + */ +#ifdef OS_PTHREAD_TLS +#include +#define OS_DECL_TLS(typ, varname) pthread_key_t varname +#define os_tls_create(varname) pthread_key_create(&varname, NULL) +#define os_tls_delete(varname) pthread_key_delete(varname) +#define os_tls_get(typ, varname) ((typ)pthread_getspecific(varname)) +#define os_tls_set(varname, val) pthread_setspecific(varname, val) +#endif + + +/* ------------------------------------------------------------------------ */ +/* + * definitions. + * + * os_time() should act like Unix time(), returning the number of seconds + * elapsed since January 1, 1970 at midnight UTC. + * + * The original Unix 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 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 attribute - by default, map this to bold-face */ +#ifndef OS_ATTR_EM +# define OS_ATTR_EM OS_ATTR_BOLD +#endif + +/* HTML 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 tag, for example). If it's + * convenient to do so, the OS layer can use this string to set a window + * caption, or whatever else makes sense on each system. Most + * character-mode implementations will provide an empty implementation, + * since there's not usually any standard way to show the current + * application title on a character-mode display. + */ +void os_set_title(const char *title); + +/* + * Show the system-specific MORE prompt, and wait for the user to respond. + * Before returning, remove the MORE prompt from the screen. + * + * This routine is only used and only needs to be implemented when the OS + * layer takes responsibility for pagination; this will be the case on + * most systems that use proportionally-spaced (variable-pitch) fonts or + * variable-sized windows, since on such platforms the OS layer must do + * most of the formatting work, leaving the standard output layer unable + * to guess where pagination should occur. + * + * If the portable output formatter handles the MORE prompt, which is the + * usual case for character-mode or terminal-style implementations, this + * routine is not used and you don't need to provide an implementation. + * Note that HTML TADS provides an implementation of this routine, because + * the HTML renderer handles line breaking and thus must handle + * pagination. + */ +void os_more_prompt(); + +/* + * Interpreter Class Configuration. + * + * If this is a TEXT-ONLY interpreter: DO NOT define USE_HTML. + * + * If this is a MULTIMEDIA (HTML TADS) intepreter: #define USE_HTML + * + * (This really should be called something like OS_USE_HTML - the USE_ name + * is for historical reasons. This purpose of this macro is to configure + * the tads 2 VM-level output formatter's line breaking and MORE mode + * behavior. In HTML mode, the VM-level formatter knows that it's feeding + * its output to a page layout engine, so the VM-level output is just a + * text stream. In plain-text mode, the VM formatter *is* the page layout + * engine, so it needs to do all of the word wrapping and MORE prompting + * itself. The tads 3 output layer does NOT use this macro for its + * equivalent configuration, but instead has different .cpp files for the + * different modes, and you simply link in the one for the configuration + * you want.) + */ +/* #define USE_HTML */ + + +/* + * Enter HTML mode. This is only used when the run-time is compiled + * with the USE_HTML flag defined. This call instructs the renderer + * that HTML sequences should be parsed; until this call is made, the + * renderer should not interpret output as HTML. Non-HTML + * implementations do not need to define this routine, since the + * run-time will not call it if USE_HTML is not defined. + */ +void os_start_html(void); + +/* exit HTML mode */ +void os_end_html(void); + +/* + * Global variables with the height and width (in character cells - rows + * and columns) of the main text display area into which os_printf + * displays. The height and width are given in text lines and character + * columns, respectively. The portable code can use these values to + * format text for display via os_printf(); for example, the caller can + * use the width to determine where to put line breaks. + * + * These values are only needed for systems where os_printf() doesn't + * perform its own word-wrap formatting. On systems such as the Mac, + * where os_printf() performs word wrapping, these sizes aren't really + * important because the portable code doesn't need to perform any real + * formatting. + * + * These variables reflect the size of the "main text area," which is the + * area of the screen excluding the status line and any "banner" windows + * (as created with the os_banner_xxx() interfaces). + * + * The OS code must initialize these variables during start-up, and must + * adjust them whenever the display size is changed by user action or + * other external events (for example, if we're running inside a terminal + * window, and the user resizes the window, the OS code must recalculate + * the layout and adjust these accordingly). + */ +extern int G_os_pagelength; +extern int G_os_linewidth; + +/* + * Global flag that tells the output formatter whether to count lines + * that it's displaying against the total on the screen so far. If this + * variable is true, lines are counted, and the screen is paused with a + * [More] message when it's full. When not in MORE mode, lines aren't + * counted. This variable should be set to false when displaying text + * that doesn't count against the current page, such as status line + * information. + * + * This flag should not be modified by OS code. Instead, the output + * formatter will set this flag according to its current state; the OS + * code can use this flag to determine whether or not to display a MORE + * prompt during os_printf()-type operations. Note that this flag is + * normally interesting to the OS code only when the OS code itself is + * handling the MORE prompt. + */ +extern int G_os_moremode; + +/* + * Global buffer containing the name of the byte-code file (the "game + * file") loaded into the VM. This is used only where applicable, which + * generally means in TADS Interpreter builds. In other application + * builds, this is simply left empty. The application is responsible for + * setting this during start-up (or wherever else the byte-code filename + * becomes known or changes). + */ +extern char G_os_gamename[OSFNMAX]; + +/* + * Set non-stop mode. This tells the OS layer that it should disable any + * MORE prompting it would normally do. + * + * This routine is needed only when the OS layer handles MORE prompting; on + * character-mode platforms, where the prompting is handled in the portable + * console layer, this can be a dummy implementation. + */ +void os_nonstop_mode(int flag); + +/* + * Update progress display with current info, if appropriate. This can + * be used to provide a status display during compilation. Most + * command-line implementations will just ignore this notification; this + * can be used for GUI compiler implementations to provide regular + * display updates during compilation to show the progress so far. + */ +/* void os_progress(const char *fname, unsigned long linenum); */ + +/* + * Set busy cursor. If 'flag' is true, provide a visual representation + * that the system or application is busy doing work. If 'flag' is + * false, remove any visual "busy" indication and show normal status. + * + * We provide a prototype here if your osxxx.h header file does not + * #define a macro for os_csr_busy. On many systems, this function has + * no effect at all, so the osxxx.h header file simply #define's it to + * do an empty macro. + */ +#ifndef os_csr_busy +void os_csr_busy(int flag); +#endif + + +/* ------------------------------------------------------------------------ */ +/* + * User Input Routines + */ + +/* + * Ask the user for a filename, using a system-dependent dialog or other + * mechanism. Returns one of the OS_AFE_xxx status codes (see below). + * + * prompt_type is the type of prompt to provide -- this is one of the + * OS_AFP_xxx codes (see below). The OS implementation doesn't need to + * pay any attention to this parameter, but it can be used if desired to + * determine the type of dialog to present if the system provides + * different types of dialogs for different types of operations. + * + * file_type is one of the OSFTxxx codes for system file type. The OS + * implementation is free to ignore this information, but can use it to + * filter the list of files displayed if desired; this can also be used + * to apply a default suffix on systems that use suffixes to indicate + * file type. If OSFTUNK is specified, it means that no filtering + * should be performed, and no default suffix should be applied. + */ +int os_askfile(const char *prompt, char *fname_buf, int fname_buf_len, + int prompt_type, os_filetype_t file_type); + +/* + * os_askfile status codes + */ + +/* success */ +#define OS_AFE_SUCCESS 0 + +/* + * Generic failure - this is largely provided for compatibility with + * past versions, in which only zero and non-zero error codes were + * meaningful; since TRUE is defined as 1 on most platforms, we assume + * that 1 is probably the generic non-zero error code that most OS + * implementations have traditionally used. In addition, this can be + * used to indicate any other error for which there is no more specific + * error code. + */ +#define OS_AFE_FAILURE 1 + +/* user cancelled */ +#define OS_AFE_CANCEL 2 + +/* + * os_askfile prompt types + * + * Important note: do not change these values when porting TADS. These + * values can be used by games, so they must be the same on all + * platforms. + */ +#define OS_AFP_OPEN 1 /* choose an existing file to open for reading */ +#define OS_AFP_SAVE 2 /* choose a filename for saving to a file */ + + +/* + * Read a string of input. Fills in the buffer with a null-terminated + * string containing a line of text read from the standard input. The + * returned string should NOT contain a trailing newline sequence. On + * success, returns 'buf'; on failure, including end of file, returns a + * null pointer. + */ +unsigned char *os_gets(unsigned char *buf, size_t bufl); + +/* + * Read a string of input with an optional timeout. This behaves like + * os_gets(), in that it allows the user to edit a line of text (ideally + * using the same editing keys that os_gets() does), showing the line of + * text under construction during editing. This routine differs from + * os_gets() in that it returns if the given timeout interval expires + * before the user presses Return (or the local equivalent). + * + * If the user presses Return before the timeout expires, we store the + * command line in the given buffer, just as os_gets() would, and we return + * OS_EVT_LINE. We also update the display in the same manner that + * os_gets() would, by moving the cursor to a new line and scrolling the + * displayed text as needed. + * + * If a timeout occurs before the user presses Return, we store the command + * line so far in the given buffer, statically store the cursor position, + * insert mode, buffer text, and anything else relevant to the editing + * state, and we return OS_EVT_TIMEOUT. + * + * If the implementation does not support the timeout operation, this + * routine should simply return OS_EVT_NOTIMEOUT immediately when called; + * the routine should not allow the user to perform any editing if the + * timeout is not supported. Callers must use the ordinary os_gets() + * routine, which has no timeout capabilities, if the timeout is not + * supported. + * + * When we return OS_EVT_TIMEOUT, the caller is responsible for doing one + * of two things. + * + * The first possibility is that the caller performs some work that doesn't + * require any display operations (in other words, the caller doesn't + * invoke os_printf, os_getc, or anything else that would update the + * display), and then calls os_gets_timeout() again. In this case, we will + * use the editing state that we statically stored before we returned + * OS_EVT_TIMEOUT to continue editing where we left off. This allows the + * caller to perform some computation in the middle of user command editing + * without interrupting the user - the extra computation is transparent to + * the user, because we act as though we were still in the midst of the + * original editing. + * + * The second possibility is that the caller wants to update the display. + * In this case, the caller must call os_gets_cancel() BEFORE making any + * display changes. Then, the caller must do any post-input work of its + * own, such as updating the display mode (for example, closing HTML font + * tags that were opened at the start of the input). The caller is now + * free to do any display work it wants. + * + * If we have information stored from a previous call that was interrupted + * by a timeout, and os_gets_cancel(TRUE) was never called, we will resume + * editing where we left off when the cancelled call returned; this means + * that we'll restore the cursor position, insertion state, and anything + * else relevant. Note that if os_gets_cancel(FALSE) was called, we must + * re-display the command line under construction, but if os_gets_cancel() + * was never called, we will not have to make any changes to the display at + * all. + * + * Note that when resuming an interrupted editing session (interrupted via + * os_gets_cancel()), the caller must re-display the prompt prior to + * invoking this routine. + * + * Note that we can return OS_EVT_EOF in addition to the other codes + * mentioned above. OS_EVT_EOF indicates that an error occurred reading, + * which usually indicates that the application is being terminated or that + * some hardware error occurred reading the keyboard. + * + * If 'use_timeout' is false, the timeout should be ignored. Without a + * timeout, the function behaves the same as os_gets(), except that it will + * resume editing of a previously-interrupted command line if appropriate. + * (This difference is why the timeout is optional: a caller might not need + * a timeout, but might still want to resume a previous input that did time + * out, in which case the caller would invoke this routine with + * use_timeout==FALSE. The regular os_gets() would not satisfy this need, + * because it cannot resume an interrupted input.) + * + * Note that a zero timeout has the same meaning as for os_get_event(): if + * input is available IMMEDIATELY, return the input, otherwise return + * immediately with the OS_EVT_TIMEOUT result code. + */ +int os_gets_timeout(unsigned char *buf, size_t bufl, + unsigned long timeout_in_milliseconds, int use_timeout); + +/* + * Cancel an interrupted editing session. This MUST be called if any + * output is to be displayed after a call to os_gets_timeout() returns + * OS_EVT_TIMEOUT. + * + * 'reset' indicates whether or not we will forget the input state saved + * by os_gets_timeout() when it last returned. If 'reset' is true, we'll + * clear the input state, so that the next call to os_gets_timeout() will + * start with an empty input buffer. If 'reset' is false, we will retain + * the previous input state, if any; this means that the next call to + * os_gets_timeout() will re-display the same input buffer that was under + * construction when it last returned. + * + * This routine need not be called if os_gets_timeout() is to be called + * again with no other output operations between the previous + * os_gets_timeout() call and the next one. + * + * Note that this routine needs only a trivial implementation when + * os_gets_timeout() is not supported (i.e., the function always returns + * OS_EVT_NOTIMEOUT). + */ +void os_gets_cancel(int reset); + +/* + * Read a character from the keyboard. For extended keystrokes, this + * function returns zero, and then returns the CMD_xxx code for the + * extended keystroke on the next call. For example, if the user presses + * the up-arrow key, the first call to os_getc() should return 0, and the + * next call should return CMD_UP. Refer to the CMD_xxx codes below. + * + * os_getc() should return a high-level, translated command code for + * command editing. This means that, where a functional interpretation of + * a key and the raw key-cap interpretation both exist as CMD_xxx codes, + * the functional interpretation should be returned. For example, on Unix, + * Ctrl-E is conventionally used in command editing to move to the end of + * the line, following Emacs key bindings. Hence, os_getc() should return + * CMD_END for this keystroke, rather than a single 0x05 character (ASCII + * Ctrl-E), because CMD_END is the high-level command code for the + * operation. + * + * The translation ability of this function allows for system-dependent key + * mappings to functional meanings. + */ +int os_getc(void); + +/* + * Read a character from the keyboard, following the same protocol as + * os_getc() for CMD_xxx codes (i.e., when an extended keystroke is + * encountered, os_getc_raw() returns zero, then returns the CMD_xxx code + * on the subsequent call). + * + * This function differs from os_getc() in that this function returns the + * low-level, untranslated key code whenever possible. This means that, + * when a functional interpretation of a key and the raw key-cap + * interpretation both exist as CMD_xxx codes, this function returns the + * key-cap interpretation. For the Unix Ctrl-E example in the comments + * describing os_getc() above, this function should return 5 (the ASCII + * code for Ctrl-E), because the ASCII control character interpretation is + * the low-level key code. + * + * This function should return all control keys using their ASCII control + * codes, whenever possible. Similarly, this function should return ASCII + * 27 for the Escape key, if possible. + * + * For keys for which there is no portable ASCII representation, this + * should return the CMD_xxx sequence. So, this function acts exactly the + * same as os_getc() for arrow keys, function keys, and other special keys + * that have no ASCII representation. This function returns a + * non-translated version ONLY when an ASCII representation exists - in + * practice, this means that this function and os_getc() vary only for CTRL + * keys and Escape. + */ +int os_getc_raw(void); + + +/* wait for a character to become available from the keyboard */ +void os_waitc(void); + +/* + * Constants for os_getc() when returning commands. When used for + * command line editing, special keys (arrows, END, etc.) should cause + * os_getc() to return 0, and return the appropriate CMD_ value on the + * NEXT call. Hence, os_getc() must keep the appropriate information + * around statically for the next call when a command key is issued. + * + * The comments indicate which CMD_xxx codes are "translated" codes and + * which are "raw"; the difference is that, when a particular keystroke + * could be interpreted as two different CMD_xxx codes, one translated + * and the other raw, os_getc() should always return the translated + * version of the key, and os_getc_raw() should return the raw version. + */ +#define CMD_UP 1 /* move up/up arrow (translated) */ +#define CMD_DOWN 2 /* move down/down arrow (translated) */ +#define CMD_RIGHT 3 /* move right/right arrow (translated) */ +#define CMD_LEFT 4 /* move left/left arrow (translated) */ +#define CMD_END 5 /* move cursor to end of line (translated) */ +#define CMD_HOME 6 /* move cursor to start of line (translated) */ +#define CMD_DEOL 7 /* delete to end of line (translated) */ +#define CMD_KILL 8 /* delete entire line (translated) */ +#define CMD_DEL 9 /* delete current character (translated) */ +#define CMD_SCR 10 /* toggle scrollback mode (translated) */ +#define CMD_PGUP 11 /* page up (translated) */ +#define CMD_PGDN 12 /* page down (translated) */ +#define CMD_TOP 13 /* top of file (translated) */ +#define CMD_BOT 14 /* bottom of file (translated) */ +#define CMD_F1 15 /* function key F1 (raw) */ +#define CMD_F2 16 /* function key F2 (raw) */ +#define CMD_F3 17 /* function key F3 (raw) */ +#define CMD_F4 18 /* function key F4 (raw) */ +#define CMD_F5 19 /* function key F5 (raw) */ +#define CMD_F6 20 /* function key F6 (raw) */ +#define CMD_F7 21 /* function key F7 (raw) */ +#define CMD_F8 22 /* function key F8 (raw) */ +#define CMD_F9 23 /* function key F9 (raw) */ +#define CMD_F10 24 /* function key F10 (raw) */ +#define CMD_CHOME 25 /* control-home (raw) */ +#define CMD_TAB 26 /* tab (translated) */ +#define CMD_SF2 27 /* shift-F2 (raw) */ +/* not used (obsolete) - 28 */ +#define CMD_WORD_LEFT 29 /* word left (ctrl-left on dos) (translated) */ +#define CMD_WORD_RIGHT 30 /* word right (ctrl-right on dos) (translated) */ +#define CMD_WORDKILL 31 /* delete word right (translated) */ +#define CMD_EOF 32 /* end-of-file (raw) */ +#define CMD_BREAK 33 /* break (Ctrl-C or local equivalent) (translated) */ +#define CMD_INS 34 /* insert key (raw) */ + + +/* + * ALT-keys - add alphabetical code to CMD_ALT: ALT-A == CMD_ALT + 0, + * ALT-B == CMD_ALT + 1, ALT-C == CMD_ALT + 2, etc + * + * These keys are all raw (untranslated). + */ +#define CMD_ALT 128 /* start of ALT keys */ + + +/* ------------------------------------------------------------------------ */ +/* + * Event information structure for os_get_event. The appropriate union + * member should be filled in, depending on the type of event that + * occurs. + */ +union os_event_info_t +{ + /* + * OS_EVT_KEY - this returns the one or two characters of the + * keystroke. If the key is an extended key, so that os_getc() would + * return a two-character sequence for the keystroke, the first + * character should be zero and the second the extended key code. + * Otherwise, the first character should simply be the ASCII key code. + * + * The key code here is the "raw" keycode, equivalent to the codes + * returned by os_getc_raw(). Note in particular that this means that + * CTRL and Escape keys are presented as one-byte ASCII control + * characters, not as two-byte CMD_xxx sequences. + * + * For multi-byte character sets (Shift-JIS, for example), note that + * os_get_event() must NOT return a complete two-byte character here. + * The two bytes here are exclusively used to represent special + * CMD_xxx keys (such as arrow keys and function keys). For a + * keystroke that is represented in a multi-byte character set using + * more than one byte, os_get_event() must return a series of events. + * Return an ordinary OS_EVT_KEY for the first byte of the sequence, + * putting the byte in key[0]; then, return the second byte as a + * separate OS_EVT_KEY as the next event; and so on for any additional + * bytes. This will allow callers that are not multibyte-aware to + * treat multi-byte characters as though they were sequences of + * one-byte characters. + */ + int key[2]; + + /* + * OS_EVT_HREF - this returns the text of the HREF as a + * null-terminated string. + */ + char href[256]; + + /* command ID (for OS_EVT_COMMAND) */ + int cmd_id; +}; +typedef union os_event_info_t os_event_info_t; + +/* + * Event types for os_get_event + */ + +/* invalid/no event */ +#define OS_EVT_NONE 0x0000 + +/* OS_EVT_KEY - user typed a key on the keyboard */ +#define OS_EVT_KEY 0x0001 + +/* OS_EVT_TIMEOUT - no event occurred before the timeout elapsed */ +#define OS_EVT_TIMEOUT 0x0002 + +/* + * OS_EVT_HREF - user clicked on a <A HREF> link. This only applies to + * the HTML-enabled run-time. + */ +#define OS_EVT_HREF 0x0003 + +/* + * OS_EVT_NOTIMEOUT - caller requested a timeout, but timeout is not + * supported by this version of the run-time + */ +#define OS_EVT_NOTIMEOUT 0x0004 + +/* + * OS_EVT_EOF - an error occurred reading the event. This generally + * means that the application is quitting or we can no longer read from + * the keyboard or terminal. + */ +#define OS_EVT_EOF 0x0005 + +/* + * OS_EVT_LINE - user entered a line of text on the keyboard. This event + * is not returned from os_get_event(), but rather from os_gets_timeout(). + */ +#define OS_EVT_LINE 0x0006 + + +/* + * Get an input event. The event types are shown above. If use_timeout is + * false, this routine should simply wait until one of the events it + * recognizes occurs, then return the appropriate information on the event. + * If use_timeout is true, this routine should return OS_EVT_TIMEOUT after + * the given number of milliseconds elapses if no event occurs first. + * + * This function is not obligated to obey the timeout. If a timeout is + * specified and it is not possible to obey the timeout, the function + * should simply return OS_EVT_NOTIMEOUT. The trivial implementation thus + * checks for a timeout, returns an error if specified, and otherwise + * simply waits for the user to press a key. + * + * A timeout value of 0 does *not* mean that there's no timeout (i.e., it + * doesn't mean we should wait indefinitely) - that's specified by passing + * FALSE for use_timeout. A zero timeout also doesn't meant that the + * function should unconditionally return OS_EVT_TIMEOUT. Instead, a zero + * timeout specifically means that IF an event is available IMMEDIATELY, + * without blocking the thread, we should return that event; otherwise we + * should immediately return a timeout event. + */ +int os_get_event(unsigned long timeout_in_milliseconds, int use_timeout, + os_event_info_t *info); + + +/* ------------------------------------------------------------------------ */ +/* + * Extended os_get_event() codes. + * + * THESE ARE NOT USED in the basic osifc implementation - these are only + * used if the interpreter supports the "extended" interface defined in + * osifcext.h. + */ + +/* + * System menu command event. Some interpreters (such as HTML TADS for + * Windows) provide menu commands for common system-level game verbs - + * SAVE, RESTORE, UNDO, QUIT. By default, these commands are returned as + * literal command strings ("save", "restore", etc) via os_gets(), as + * though the user had typed the commands at the keyboard. The extended OS + * interface allows the program to receive these commands via + * os_get_event(). When a command is enabled for os_get_event() via the + * extended OS interface, and the player selects the command via a menu (or + * toolbar button, etc) during a call to os_get_event(), os_get_event() + * returns this event code, with the menu ID stored in the cmd_id field of + * the event structure. + */ +#define OS_EVT_COMMAND 0x0100 + +/* command IDs for OS_EVT_COMMAND */ +#define OS_CMD_NONE 0x0000 /* invalid command ID, for internal use */ +#define OS_CMD_SAVE 0x0001 /* save game */ +#define OS_CMD_RESTORE 0x0002 /* restore game */ +#define OS_CMD_UNDO 0x0003 /* undo last turn */ +#define OS_CMD_QUIT 0x0004 /* quit game */ +#define OS_CMD_CLOSE 0x0005 /* close the game window */ +#define OS_CMD_HELP 0x0006 /* show game help */ + +/* highest command ID used in this version of the interface */ +#define OS_CMD_LAST 0x0006 + + +/* ------------------------------------------------------------------------ */ +/* + * Ask for input through a dialog. + * + * 'prompt' is a text string to display as a prompting message. For + * graphical systems, this message should be displayed in the dialog; + * for text systems, this should be displayed on the terminal after a + * newline. + * + * 'standard_button_set' is one of the OS_INDLG_xxx values defined + * below, or zero. If this value is zero, no standard button set is to + * be used; the custom set of buttons defined in 'buttons' is to be used + * instead. If this value is non-zero, the appropriate set of standard + * buttons, with labels translated to the local language if possible, is + * to be used. + * + * 'buttons' is an array of strings to use as button labels. + * 'button_count' gives the number of entries in the 'buttons' array. + * 'buttons' and 'button_count' are ignored if 'standard_button_set' is + * non-zero, since a standard set of buttons is used instead. If + * 'buttons' and 'button_count' are to be used, each entry contains the + * label of a button to show. + */ +/* + * An ampersand ('&') character in a label string indicates that the + * next character after the '&' is to be used as the short-cut key for + * the button, if supported. The '&' should NOT be displayed in the + * string; instead, the character should be highlighted according to + * local system conventions. On Windows, for example, the short-cut + * character should be shown underlined; on a text display, the response + * might be shown with the short-cut character enclosed in parentheses. + * If there is no local convention for displaying a short-cut character, + * then the '&' should simply be removed from the displayed text. + * + * The short-cut key specified by each '&' character should be used in + * processing responses. If the user presses the key corresponding to a + * button's short-cut, the effect should be the same as if the user + * clicked the button with the mouse. If local system conventions don't + * allow for short-cut keys, any short-cut keys can be ignored. + * + * 'default_index' is the 1-based index of the button to use as the + * default. If this value is zero, there is no default response. If + * the user performs the appropriate system-specific action to select + * the default response for the dialog, this is the response that is to + * be selected. On Windows, for example, pressing the "Return" key + * should select this item. + * + * 'cancel_index' is the 1-based index of the button to use as the + * cancel response. If this value is zero, there is no cancel response. + * This is the response to be used if the user cancels the dialog using + * the appropriate system-specific action. On Windows, for example, + * pressing the "Escape" key should select this item. + */ +/* + * icon_id is one of the OS_INDLG_ICON_xxx values defined below. If + * possible, an appropriate icon should be displayed in the dialog. + * This can be ignored in text mode, and also in GUI mode if there is no + * appropriate system icon. + * + * The return value is the 1-based index of the response selected. If + * an error occurs, return 0. + */ +int os_input_dialog(int icon_id, const char *prompt, int standard_button_set, + const char **buttons, int button_count, + int default_index, int cancel_index); + +/* + * Standard button set ID's + */ + +/* OK */ +#define OS_INDLG_OK 1 + +/* OK, Cancel */ +#define OS_INDLG_OKCANCEL 2 + +/* Yes, No */ +#define OS_INDLG_YESNO 3 + +/* Yes, No, Cancel */ +#define OS_INDLG_YESNOCANCEL 4 + +/* + * Dialog icons + */ + +/* no icon */ +#define OS_INDLG_ICON_NONE 0 + +/* warning */ +#define OS_INDLG_ICON_WARNING 1 + +/* information */ +#define OS_INDLG_ICON_INFO 2 + +/* question */ +#define OS_INDLG_ICON_QUESTION 3 + +/* error */ +#define OS_INDLG_ICON_ERROR 4 + + + +/* ------------------------------------------------------------------------ */ +/* + * OS main entrypoint + */ +int os0main(int oargc, char **oargv, + int (*mainfn)(int, char **, char *), + const char *before, const char *config); + +/* + * new-style OS main entrypoint - takes an application container context + */ +int os0main2(int oargc, char **oargv, + int (*mainfn)(int, char **, struct appctxdef *, char *), + const char *before, const char *config, + struct appctxdef *appctx); + +/* + * OBSOLETE - Get filename from startup parameter, if possible; returns + * true and fills in the buffer with the parameter filename on success, + * false if no parameter file could be found. + * + * (This was used until TADS 2.2.5 for the benefit of the Mac interpreter, + * and interpreters on systems with similar desktop shells, to allow the + * user to launch the terp by double-clicking on a saved game file. The + * terp would read the launch parameters, discover that a saved game had + * been used to invoke it, and would then stash away the saved game info + * for later retrieval from this function. This functionality was replaced + * in 2.2.5 with a command-line parameter: the terp now uses the desktop + * launch data to synthesize a suitable argv[] vectro to pass to os0main() + * or os0main2(). This function should now simply be stubbed out - it + * should simply return FALSE.) + */ +int os_paramfile(char *buf); + +/* + * Initialize. This should be called during program startup to + * initialize the OS layer and check OS-specific command-line arguments. + * + * If 'prompt' and 'buf' are non-null, and there are no arguments on the + * given command line, the OS code can use the prompt to ask the user to + * supply a filename, then store the filename in 'buf' and set up + * argc/argv to give a one-argument command string. (This mechanism for + * prompting for a filename is obsolescent, and is retained for + * compatibility with a small number of existing implementations only; + * new implementations should ignore this mechanism and leave the + * argc/argv values unchanged.) + */ +int os_init(int *argc, char *argv[], const char *prompt, + char *buf, int bufsiz); + +/* + * Termination functions. There are three main termination functions, + * described individually below; here's a brief overview of the + * relationship among the functions. The important thing to realize is + * that these functions have completely independent purposes; they should + * never call one another, and they should never do any of the work that's + * intended for the others. + * + * os_uninit() is meant to undo the effects of os_init(). On many + * systems, os_init() has some global effect, such as setting the terminal + * to some special input or output mode. os_uninit's purpose is to undo + * these global effects, returning the terminal mode (and whatever else) + * to the conditions they were in at program startup. Portable callers + * are meant to call this routine at some point before terminating if they + * ever called os_init(). Note that this routine DOES NOT terminate the + * program - it should simply undo anything that os_init() did and return, + * to let the caller do any additional termination work of its own. + * + * os_expause() optionally pauses before termination, to allow the user to + * acknowledge any text the program displays just before exiting. This + * doesn't have to do anything at all, but it's useful on systems where + * program termination will do something drastic like close the window: + * without a pause, the user wouldn't have a chance to read any text the + * program displayed after the last interactive input, because the window + * would abruptly disappear moments after the text was displayed. For + * systems where termination doesn't have such drastic effects, there's no + * need to do anything in this routine. Because it's up to this routine + * whether or not to pause, this routine must display a prompt if it's + * going to pause for user input - the portable caller obviously can't do + * so, because the caller doesn't know if the routine is going to pause or + * not (so if the caller displayed a prompt assuming the routine would + * pause, the prompt would show up even on systems where there actually is + * no pause, which would be confusing). This routine DOES NOT terminate + * the program; it simply pauses if necessary to allow the user to + * acknowledge the last bit of text the program displayed, then returns to + * allow the caller to carry on with its own termination work. + * + * os_term() is meant to perform the same function as the C standard + * library routine exit(): this actually terminates the program, exiting + * to the operating system. This routine is not meant to return to its + * caller, because it's supposed to exit the program directly. For many + * systems, this routine can simply call exit(); the portable code calls + * this routine instead of calling exit() directly, because some systems + * have their own OS-specific way of terminating rather than using exit(). + * This routine MUST NOT undo the effects of os_init(), because callers + * might not have ever called os_init(); callers are required to call + * os_uninit() if they ever called os_init(), before calling os_term(), so + * this routine can simply assume that any global modes set by os_init() + * have already been undone by the time this is called. + */ + +/* + * Uninitialize. This is called prior to progam termination to reverse + * the effect of any changes made in os_init(). For example, if + * os_init() put the terminal in raw mode, this should restore the + * previous terminal mode. This routine should not terminate the + * program (so don't call exit() here) - the caller might have more + * processing to perform after this routine returns. + */ +void os_uninit(void); + +/* + * Pause prior to exit, if desired. This is meant to be called by + * portable code just before the program is to be terminated; it can be + * implemented to show a prompt and wait for user acknowledgment before + * proceeding. This is useful for implementations that are using + * something like a character-mode terminal window running on a graphical + * operating system: this gives the implementation a chance to pause + * before exiting, so that the window doesn't just disappear + * unceremoniously. + * + * This is allowed to do nothing at all. For regular character-mode + * systems, this routine usually doesn't do anything, because when the + * program exits, the terminal will simply return to the OS command + * prompt; none of the text displayed just before the program exited will + * be lost, so there's no need for any interactive pause. Likewise, for + * graphical systems where the window will remain open, even after the + * program exits, until the user explicitly closes the window, there's no + * need to do anything here. + * + * If this is implemented to pause, then this routine MUST show some kind + * of prompt to let the user know we're waiting. In the simple case of a + * text-mode terminal window on a graphical OS, this should simply print + * out some prompt text ("Press a key to exit...") and then wait for the + * user to acknowledge the prompt (by pressing a key, for example). For + * graphical systems, the prompt could be placed in the window's title + * bar, or status-bar, or wherever is appropriate for the OS. + */ +void os_expause(void); + +/* + * Terminate. This should exit the program with the given exit status. + * In general, this should be equivalent to the standard C library + * exit() function, but we define this interface to allow the OS code to + * do any necessary pre-termination cleanup. + */ +void os_term(int status); + +/* + * Install/uninstall the break handler. If possible, the OS code should + * set (if 'install' is true) or clear (if 'install' is false) a signal + * handler for keyboard break signals (control-C, etc, depending on + * local convention). The OS code should set its own handler routine, + * which should note that a break occurred with an internal flag; the + * portable code uses os_break() from time to time to poll this flag. + */ +void os_instbrk(int install); + +/* + * Check for user break ("control-C", etc) - returns true if a break is + * pending, false if not. If this returns true, it should "consume" the + * pending break (probably by simply clearing the OS code's internal + * break-pending flag). + */ +int os_break(void); + +/* + * Sleep for a given interval. This should simply pause for the given + * number of milliseconds, then return. On multi-tasking systems, this + * should use a system API to suspend the current process for the desired + * delay; on single-tasking systems, this can simply sit in a wait loop + * until the desired interval has elapsed. + */ +void os_sleep_ms(long delay_in_milliseconds); + +/* + * Yield CPU; returns TRUE if user requested an interrupt (a "control-C" + * type of operation to abort the entire program), FALSE to continue. + * Portable code should call this routine from time to time during lengthy + * computations that don't involve any UI operations; if practical, this + * routine should be invoked roughly every 10 to 100 milliseconds. + * + * The purpose of this routine is to support "cooperative multitasking" + * systems, such as pre-X MacOS, where it's necessary for each running + * program to call the operating system explicitly in order to yield the + * CPU from time to time to other concurrently running programs. On + * cooperative multitasking systems, a program can only lose control of + * the CPU by making specific system calls, usually related to GUI events; + * a program can never lose control of the CPU asynchronously. So, a + * program that performs lengthy computations without any UI interaction + * can cause the rest of the system to freeze up until the computations + * are finished; but if a compute-intensive program explicitly yields the + * CPU from time to time, it allows other programs to remain responsive. + * Yielding the CPU at least every 100 milliseconds or so will generally + * allow the UI to remain responsive; yielding more frequently than every + * 10 ms or so will probably start adding noticeable overhead. + * + * On single-tasking systems (such as MS-DOS), there's only one program + * running at a time, so there's no need to yield the CPU; on virtually + * every modern system, the OS automatically schedules CPU time without + * the running programs having any say in the matter, so again there's no + * need for a program to yield the CPU. For systems where this routine + * isn't needed, the system header should simply #define os_yield to + * something like "((void)0)" - this will allow the compiler to completely + * ignore calls to this routine for systems where they aren't needed. + * + * Note that this routine is NOT meant to provide scheduling hinting to + * modern systems with true multitasking, so a trivial implementation is + * fine for any modern system. + */ +#ifndef os_yield +int os_yield(void); +#endif + +/* + * Set the default saved-game extension. This routine will NOT be called + * when we're using the standard saved game extension; this routine will be + * invoked only if we're running as a stand-alone game, and the game author + * specified a non-standard saved-game extension when creating the + * stand-alone game. + * + * This routine is not required if the system does not use the standard, + * semi-portable os0.c implementation. Even if the system uses the + * standard os0.c implementation, it can provide an empty routine here if + * the system code doesn't need to do anything special with this + * information. + * + * The extension is specified as a null-terminated string. The extension + * does NOT include the leading period. + */ +void os_set_save_ext(const char *ext); + +/* + * Get the saved game extension previously set with os_set_save_ext(). + * Returns null if no custom extension has been set. + */ +const char *os_get_save_ext(); + + +/* ------------------------------------------------------------------------*/ +/* + * Translate a character from the HTML 4 Unicode character set to the + * current character set used for display. Takes an HTML 4 character + * code and returns the appropriate local character code. + * + * The result buffer should be filled in with a null-terminated string + * that should be used to represent the character. Multi-character + * results are possible, which may be useful for certain approximations + * (such as using "(c)" for the copyright symbol). + * + * Note that we only define this prototype if this symbol isn't already + * defined as a macro, which may be the case on some platforms. + * Alternatively, if the function is already defined (for example, as an + * inline function), the defining code can define OS_XLAT_HTML4_DEFINED, + * in which case we'll also omit this prototype. + * + * Important: this routine provides the *default* mapping that is used + * when no external character mapping file is present, and for any named + * entities not defined in the mapping file. Any entities in the + * mapping file, if used, will override this routine. + * + * A trivial implementation of this routine (that simply returns a + * one-character result consisting of the original input character, + * truncated to eight bits if necessary) can be used if you want to + * require an external mapping file to be used for any game that + * includes HTML character entities. The DOS version implements this + * routine so that games will still look reasonable when played with no + * mapping file present, but other systems are not required to do this. + */ +#ifndef os_xlat_html4 +# ifndef OS_XLAT_HTML4_DEFINED +void os_xlat_html4(unsigned int html4_char, + char *result, size_t result_buf_len); +# endif +#endif + +/* + * Generate a filename for a character-set mapping file. This function + * should determine the current native character set in use, if + * possible, then generate a filename, according to system-specific + * conventions, that we should attempt to load to get a mapping between + * the current native character set and the internal character set + * identified by 'internal_id'. + * + * The internal character set ID is a string of up to 4 characters. + * + * On DOS, the native character set is a DOS code page. DOS code pages + * are identified by 3- or 4-digit identifiers; for example, code page + * 437 is the default US ASCII DOS code page. We generate the + * character-set mapping filename by appending the internal character + * set identifier to the DOS code page number, then appending ".TCP" to + * the result. So, to map between ISO Latin-1 (internal ID = "La1") and + * DOS code page 437, we would generate the filename "437La1.TCP". + * + * Note that this function should do only two things. First, determine + * the current native character set that's in use. Second, generate a + * filename based on the current native code page and the internal ID. + * This function is NOT responsible for figuring out the mapping or + * anything like that -- it's simply where we generate the correct + * filename based on local convention. + * + * 'filename' is a buffer of at least OSFNMAX characters. + * + * 'argv0' is the executable filename from the original command line. + * This parameter is provided so that the system code can look for + * mapping files in the original TADS executables directory, if desired. + */ +void os_gen_charmap_filename(char *filename, char *internal_id, + char *argv0); + +/* + * Receive notification that a character mapping file has been loaded. + * The caller doesn't require this routine to do anything at all; this + * is purely for the system-dependent code's use so that it can take + * care of any initialization that it must do after the caller has + * loaded a charater mapping file. 'id' is the character set ID, and + * 'ldesc' is the display name of the character set. 'sysinfo' is the + * extra system information string that is stored in the mapping file; + * the interpretation of this information is up to this routine. + * + * For reference, the Windows version uses the extra information as a + * code page identifier, and chooses its default font character set to + * match the code page. On DOS, the run-time requires the player to + * activate an appropriate code page using a DOS command (MODE CON CP + * SELECT) prior to starting the run-time, so this routine doesn't do + * anything at all on DOS. + */ +void os_advise_load_charmap(const char *id, const char *ldesc, const char *sysinfo); + +/* + * Generate the name of the character set mapping table for Unicode + * characters to and from the given local character set. Fills in the + * buffer with the implementation-dependent name of the desired + * character set map. See below for the character set ID codes. + * + * For example, on Windows, the implementation would obtain the + * appropriate active code page (which is simply a Windows character set + * identifier number) from the operating system, and build the name of + * the Unicode mapping file for that code page, such as "CP1252". On + * Macintosh, the implementation would look up the current script system + * and return the name of the Unicode mapping for that script system, + * such as "ROMAN" or "CENTEURO". + * + * If it is not possible to determine the specific character set that is + * in use, this function should return "asc7dflt" (ASCII 7-bit default) + * as the character set identifier on an ASCII system, or an appropriate + * base character set name on a non-ASCII system. "asc7dflt" is the + * generic character set mapping for plain ASCII characters. + * + * The given buffer must be at least 32 bytes long; the implementation + * must limit the result it stores to 32 bytes. (We use a fixed-size + * buffer in this interface for simplicity, and because there seems no + * need for greater flexibility in the interface; a character set name + * doesn't carry very much information so shouldn't need to be very + * long. Note that this function doesn't generate a filename, but + * simply a mapping name; in practice, a map name will be used to + * construct a mapping file name.) + * + * Because this function obtains the Unicode mapping name, there is no + * need to specify the internal character set to be used: the internal + * character set is Unicode. + */ +/* + * Implementation note: when porting this routine, the convention that + * you use to name your mapping files is up to you. You should simply + * choose a convention for this implementation, and then use the same + * convention for packaging the mapping files for your OS release. In + * most cases, the best convention is to use the names that the Unicode + * consortium uses in their published cross-mapping listings, since + * these listings can be used as the basis of the mapping files that you + * include with your release. For example, on Windows, the convention + * is to use the code page number to construct the map name, as in + * CP1252 or CP1250. + */ +void os_get_charmap(char *mapname, int charmap_id); + +/* + * Character map for the display (i.e., for the user interface). This + * is the character set which is used for input read from the keyboard, + * and for output displayed on the monitor or terminal. + */ +#define OS_CHARMAP_DISPLAY 1 + +/* + * Character map for mapping filename strings. This should identify the + * character set currently in use on the local system for filenames + * (i.e., for strings identifying objects in the local file system), + * providing a suitable name for use in opening a mapping file. + * + * On many platforms, the entire system uses only one character set at + * the OS level, so the file system character set is the same as the + * display character set. Some systems define a particular character + * set for file system use, though, because different users might be + * running applications on terminals that display different character + * sets. + */ +#define OS_CHARMAP_FILENAME 2 + +/* + * Default character map for file contents. On most systems, this will + * be the same as display. On some systems, it won't be possible to + * know in general what character set files use; in fact, this isn't + * possible anywhere, since a file might have been copied without + * translation from a different type of computer using a different + * character set. So, this isn't meant to provide a reliable choice of + * character set for any arbitrary file; it's simply meant to be a good + * guess that most files on this system are likely to use. + */ +#define OS_CHARMAP_FILECONTENTS 3 + +/* + * Default character map for the command line. This is the maping we use + * to interpret command line arguments passed to our main() or equivalent. + * On most systems, this will be the same as the display character set. + */ +#define OS_CHARMAP_CMDLINE 4 + + +/* ------------------------------------------------------------------------ */ +/* + * External Banner Interface. This interface provides the ability to + * divide the display window into multiple sub-windows, each with its own + * independent contents. + * + * To determine where a new banner is displayed, we look at the banners as + * a tree, rooted at the "main window," the special banner that the system + * automatically creates initially for the main game text. We start by + * allocating the entire display (or the entire application window, if + * we're running on a GUI system) to the main window. We then traverse + * the tree, starting with the root window's children. For each child + * window, we allocate space for the child out of the parent window's + * area, according to the child's alignment and size settings, and deduct + * this space from the parent window's size. We then lay out the children + * of the child. + * + * For each banner window, we take its requested space out of the parent + * window's area by starting at the edge of the parent window rectangle as + * indicated by the banner's alignment, and taking the requested `width + * (for a left/right banner) or height (for a top/bottom banner), limiting + * to the available width/height in the parent window's space. Give the + * banner the full extent of the parent's space in its other dimension (so + * a left/right banner gets the full height of the parent space, and a + * top/bottom banner gets the full width). + * + * Note that the layout proceeds exclusively down the tree (i.e., from the + * root to children to grandchildren, and so on). It *appears* that a + * child affects its parent, because of the deduction step: a child + * acquires screen space by carving out a chunk of its parent. The right + * way to think about this, though, is that the parent's full area is the + * union of the parent window and all of its children; when viewed this + * way, the parent's full area is fully determined the instant the parent + * is laid out, and never changes as its children are laid out. Note in + * particular that a child can never make a parent larger; the only thing + * a child can do to a parent is carve out a chunk of the parent for + * itself, which doesn't affect the boundaries of the union of the parent + * plus its children. + * + * Note also that if the banner has a border, and the implementation + * actually draws borders, the border must be drawn for the *full* area of + * the banner, as defined above. For example, suppose we have two + * borders: banner A is a child of the main window, is top-aligned, and + * has a border. Banner B is a child of banner A, right-aligned, with no + * border. Obviously, without considering banner B, banner A's space runs + * across the entire width of the main window, so its border (at the + * bottom of its area) runs across the entire width of the main window. + * Banner B carves out some space from A's right side for itself, so + * banner A's actual on-screen area runs from the left edge of the main + * window to banner B's left edge. However, even though banner A itself + * no longer runs the full width of the main window, banner A's *full* + * area - that is, the union of banner A's on-screen area and all of its + * children's full areas - does still run the entire width of the main + * window, hence banner A's border must still run the full width of the + * main window. The simple way of looking at this is that a banner's + * border is always to be drawn exactly the same way, regardless of + * whether or not the banner has children - simply draw the banner as it + * would be drawn if the banner had no children. + * + * Each time a banner is added or removed, we must recalculate the layout + * of the remaining banners and main text area. The os_banner_xxx() + * implementation is responsible for this layout refiguring. + * + * The entire external banner window interface is optional, although the + * functions must at least be defined as dummies to avoid linker errors + * when building. If a platform doesn't implement this feature, + * os_banner_create() should simply return null, and the other routines + * can do nothing. + */ + +/* + * Create a banner window. 'info' gives the desired parameters for the new + * banner. + * + * Note that certain requested parameter settings might or might not be + * respected, depending on the capabilities of the platform and user + * preferences. os_banner_getinfo() can be used after creation to + * determine which parameter settings are actually used in the new banner. + * + * 'parent' gives the parent of this banner; this is the banner handle of + * another banner window, or null. If 'parent' is null, then the new + * banner is a child of the main window, which the system creates + * automatically at startup and which contains the main input/output + * transcript. The new banner's on-screen area is carved out of the + * parent's space, according to the alignment and size settings of the new + * window, so this determines how the window is laid out on the screen. + * + * 'where' is OS_BANNER_FIRST to make the new window the first child of its + * parent; OS_BANNER_LAST to make it the last child of its parent; + * OS_BANNER_BEFORE to insert it immediately before the existing banner + * identified by handle in 'other'; or OS_BANNER_AFTER to insert + * immediately after 'other'. When BEFORE or AFTER is used, 'other' must + * be another child of the same parent; if it is not, the routine should + * act as though 'where' were given as OS_BANNER_LAST. + * + * 'other' is a banner handle for an existing banner window. This is used + * to specify the relative position among children of the new banner's + * parent, if 'where' is either OS_BANNER_BEFORE or OS_BANNER_AFTER. If + * 'where' is OS_BANNER_FIRST or OS_BANNER_LAST, 'other' is ignored. + * + * 'wintype' is the type of the window. This is one of the + * OS_BANNER_TYPE_xxx codes indicating what kind of window is desired. + * + * 'align' is the banner's alignment, given as an OS_BANNER_ALIGN_xxx + * value. Top/bottom banners are horizontal: they run across the full + * width of the existing main text area. Left/right banners are vertical: + * they run down the full height of the existing main text area. + * + * 'siz' is the requested size of the new banner. The meaning of 'siz' + * depends on the value of 'siz_units', which can be OS_BANNER_SIZE_PCT to + * set the size as a percentage of the REMAINING space, or + * OS_BANNER_SIZE_ABS to set an absolute size in the "natural" units of the + * window. The natural units vary by window type: for text and text grid + * windows, this is in rows/columns of '0' characters in the default font + * for the window. Note that when OS_BANNER_SIZE_ABS is used in a text or + * text grid window, the OS implementation MUST add the space needed for + * margins and borders when determining the actual pixel size of the + * window; in other words, the window should be large enough that it can + * actually display the given number or rows or columns. + * + * The size is interpreted as a width or height according to the window's + * orientation. For a TOP or BOTTOM banner, the size is the height; for a + * LEFT or RIGHT banner, the size is the width. A banner has only one + * dimension's size given, since the other dimension's size is determined + * automatically by the layout rules. + * + * Note that the window's size can be changed later using + * banner_size_to_contents() or banner_set_size(). + * + * 'style' is a combination of OS_BANNER_STYLE_xxx flags - see below. The + * style flags give the REQUESTED style for the banner, which might or + * might not be respected, depending on the platform's capabilities, user + * preferences, and other factors. os_banner_getinfo() can be used to + * determine which style flags are actually used. + * + * Returns the "handle" to the new banner window, which is an opaque value + * that is used in subsequent os_banner_xxx calls to operate on the window. + * Returns null if the window cannot be created. An implementation is not + * required to support this functionality at all, and can subset it if it + * does support it (for example, an implementation could support only + * top/bottom-aligned banners, but not left/right-aligned), so callers must + * be prepared for this routine to return null. + */ +void *os_banner_create(void *parent, int where, void *other, int wintype, + int align, int siz, int siz_units, + unsigned long style); + + +/* + * insertion positions + */ +#define OS_BANNER_FIRST 1 +#define OS_BANNER_LAST 2 +#define OS_BANNER_BEFORE 3 +#define OS_BANNER_AFTER 4 + +/* + * banner types + */ + +/* + * Normal text stream window. This is a text stream that behaves + * essentially like the main text window: text is displayed to this + * through os_banner_disp(), always in a stream-like fashion by adding new + * text to the end of any exiting text. + * + * Systems that use proportional fonts should usually simply use the same + * font they use by default in the main text window. However, note that + * the OS_BANNER_STYLE_TAB_ALIGN style flag might imply that a fixed-pitch + * font should be used even when proportional fonts are available, because + * a fixed-pitch font will allow the calling code to rely on using spaces + * to align text within the window. + */ +#define OS_BANNER_TYPE_TEXT 1 + +/* + * "Text grid" window. This type of window is similar to an normal text + * window (OS_BANNER_TYPE_TEXT), but is guaranteed to arrange its text in + * a regular grid of character cells, all of the same size. This means + * that the output position can be moved to an arbitrary point within the + * window at any time, so the calling program can precisely control the + * layout of the text in the window. + * + * Because the output position can be moved to arbitrary positions in the + * window, it is possible to overwrite text previously displayed. When + * this happens, the old text is completely obliterated by the new text, + * leaving no trace of the overwritten text. + * + * In order to guarantee that character cells are all the same size, this + * type of window does not allow any text attributes. The implementation + * should simply ignore any attempts to change text attributes in this + * type of window. However, colors can be used to the same degree they + * can be used in an ordinary text window. + * + * To guarantee the regular spacing of character cells, all + * implementations must use fixed-pitch fonts for these windows. This + * applies even to platforms where proportional fonts are available. + */ +#define OS_BANNER_TYPE_TEXTGRID 2 + + +/* + * banner alignment types + */ +#define OS_BANNER_ALIGN_TOP 0 +#define OS_BANNER_ALIGN_BOTTOM 1 +#define OS_BANNER_ALIGN_LEFT 2 +#define OS_BANNER_ALIGN_RIGHT 3 + +/* + * size units + */ +#define OS_BANNER_SIZE_PCT 1 +#define OS_BANNER_SIZE_ABS 2 + + +/* + * banner style flags + */ + +/* + * The banner has a visible border; this indicates that a line is to be + * drawn to separate the banner from the adjacent window or windows + * "inside" the banner. So, a top-aligned banner will have its border + * drawn along its bottom edge; a left-aligned banner will show a border + * along its right edge; and so forth. + * + * Note that character-mode platforms generally do NOT respect the border + * style, since doing so takes up too much screen space. + */ +#define OS_BANNER_STYLE_BORDER 0x00000001 + +/* + * The banner has a vertical/horizontal scrollbar. Character-mode + * platforms generally do not support scrollbars. + */ +#define OS_BANNER_STYLE_VSCROLL 0x00000002 +#define OS_BANNER_STYLE_HSCROLL 0x00000004 + +/* + * Automatically scroll the banner vertically/horizontally whenever new + * text is displayed in the window. In other words, whenever + * os_banner_disp() is called, scroll the window so that the text that the + * new cursor position after the new text is displayed is visible in the + * window. + * + * Note that this style is independent of the presence of scrollbars. + * Even if there are no scrollbars, we can still scroll the window's + * contents programmatically. + * + * Implementations can, if desired, keep an internal buffer of the + * window's contents, so that the contents can be recalled via the + * scrollbars if the text displayed in the banner exceeds the space + * available in the banner's window on the screen. If the implementation + * does keep such a buffer, we recommend the following method for managing + * this buffer. If the AUTO_VSCROLL flag is not set, then the banner's + * contents should be truncated at the bottom when the contents overflow + * the buffer; that is, once the banner's internal buffer is full, any new + * text that the calling program attempts to add to the banner should + * simply be discarded. If the AUTO_VSCROLL flag is set, then the OLDEST + * text should be discarded instead, so that the most recent text is + * always retained. + */ +#define OS_BANNER_STYLE_AUTO_VSCROLL 0x00000008 +#define OS_BANNER_STYLE_AUTO_HSCROLL 0x00000010 + +/* + * Tab-based alignment is required/supported. On creation, this is a hint + * to the implementation that is sometimes necessary to determine what + * kind of font to use in the new window, for non-HTML platforms. If this + * flag is set on creation, the caller is indicating that it wants to use + * <TAB> tags to align text in the window. + * + * Character-mode implementations that use a single font with fixed pitch + * can simply ignore this. These implementations ALWAYS have a working + * <TAB> capability, because the portable output formatter provides <TAB> + * interpretation for a fixed-pitch window. + * + * Full HTML TADS implementations can also ignore this. HTML TADS + * implementations always have full <TAB> support via the HTML + * parser/renderer. + * + * Text-only implementations on GUI platforms (i.e., implementations that + * are not based on the HTML parser/renderer engine in HTML TADS, but + * which run on GUI platforms with proportionally-spaced text) should use + * this flag to determine the font to display. If this flag is NOT set, + * then the caller doesn't care about <TAB>, and the implementation is + * free to use a proportionally-spaced font in the window if desired. + * + * When retrieving information on an existing banner, this flag indicates + * that <TAB> alignment is actually supported on the window. + */ +#define OS_BANNER_STYLE_TAB_ALIGN 0x00000020 + +/* + * Use "MORE" mode in this window. By default, a banner window should + * happily allow text to overflow the vertical limits of the window; the + * only special thing that should happen on overflow is that the window + * should be srolled down to show the latest text, if the auto-vscroll + * style is set. With this flag, though, a banner window acts just like + * the main text window: when the window fills up vertically, we show a + * MORE prompt (using appropriate system conventions), and wait for the + * user to indicate that they're ready to see more text. On most systems, + * the user acknowledges a MORE prompt by pressing a key or scrolling with + * the mouse, but it's up to the system implementor to decide what's + * appropriate for the system. + * + * Note that MORE mode in ANY banner window should generally override all + * other user input focus. In other words, if the game in the main window + * would like to read a keystroke from the user, but one of the banner + * windows is pausing with a MORE prompt, any keyboard input should be + * directed to the banner paused at the MORE prompt, not to the main + * window; the main window should not receive any key events until the MORE + * prompt has been removed. + * + * This style requires the auto-vscroll style. Implementations should + * assume auto-vscroll when this style is set. This style can be ignored + * with text grid windows. + */ +#define OS_BANNER_STYLE_MOREMODE 0x00000040 + +/* + * This banner is a horizontal/vertical "strut" for sizing purposes. This + * means that the banner's content size is taken into account when figuring + * the content size of its *parent* banner. If the banner has the same + * orientation as the parent, its content size is added to its parent's + * internal content size to determine the parent's overall content size. + * If the banner's orientation is orthogonal to the parent's, then the + * parent's overall content size is the larger of the parent's internal + * content size and this banner's content size. + */ +#define OS_BANNER_STYLE_HSTRUT 0x00000080 +#define OS_BANNER_STYLE_VSTRUT 0x00000100 + + +/* + * Delete a banner. This removes the banner from the display, which + * requires recalculating the entire screen's layout to reallocate this + * banner's space to other windows. When this routine returns, the banner + * handle is invalid and can no longer be used in any os_banner_xxx + * function calls. + * + * If the banner has children, the children will no longer be displayed, + * but will remain valid in memory until deleted. A child window's + * display area always comes out of its parent's space, so once the parent + * is gone, a child has no way to acquire any display space; resizing the + * child won't help, since it simply has no way to obtain any screen space + * once its parent has been deleted. Even though the window's children + * will become invisible, their banner handles will remain valid; the + * caller is responsible for explicitly deleting the children even after + * deleting their parent. + */ +void os_banner_delete(void *banner_handle); + +/* + * "Orphan" a banner. This tells the osifc implementation that the caller + * wishes to sever all of its ties with the banner (as part of program + * termination, for example), but that the calling program does not + * actually require that the banner's on-screen display be immediately + * removed. + * + * The osifc implementation can do one of two things: + * + * 1. Simply call os_banner_delete(). If the osifc implementation + * doesn't want to do anything extra with the banner, it can simply delete + * the banner, since the caller has no more use for it. + * + * 2. Take ownership of the banner. If the osifc implementation wishes + * to continue displaying the final screen configuration after a program + * has terminated, it can simply take over the banner and leave it on the + * screen. The osifc subsystem must eventually delete the banner itself + * if it takes this routine; for example, if the osifc subsystem allows + * another client program to be loaded into the same window after a + * previous program has terminated, it would want to delete any orphaned + * banners from the previous program when loading a new program. + */ +void os_banner_orphan(void *banner_handle); + +/* + * Banner information structure. This is filled in by the system-specific + * implementation in os_banner_getinfo(). + */ +struct os_banner_info_t +{ + /* alignment */ + int align; + + /* style flags - these indicate the style flags actually in use */ + unsigned long style; + + /* + * Actual on-screen size of the banner, in rows and columns. If the + * banner is displayed in a proportional font or can display multiple + * fonts of different sizes, this is approximated by the number of "0" + * characters in the window's default font that will fit in the + * window's display area. + */ + int rows; + int columns; + + /* + * Actual on-screen size of the banner in pixels. This is meaningful + * only for full HTML interpreter; for text-only interpreters, these + * are always set to zero. + * + * Note that even if we're running on a GUI operating system, these + * aren't meaningful unless this is a full HTML interpreter. Text-only + * interpreters should always set these to zero, even on GUI OS's. + */ + int pix_width; + int pix_height; + + /* + * OS line wrapping flag. If this is set, the window uses OS-level + * line wrapping because the window uses a proportional font, so the + * caller does not need to (and should not) perform line breaking in + * text displayed in the window. + * + * Note that OS line wrapping is a PERMANENT feature of the window. + * Callers can note this information once and expect it to remain + * fixed through the window's lifetime. + */ + int os_line_wrap; +}; +typedef struct os_banner_info_t os_banner_info_t; + +/* + * Get information on the banner - fills in the information structure with + * the banner's current settings. Note that this should indicate the + * ACTUAL properties of the banner, not the requested properties; this + * allows callers to determine how the banner is actually displayed, which + * depends upon the platform's capabilities and user preferences. + * + * Returns true if the information was successfully obtained, false if + * not. This can return false if the underlying OS window has already + * been closed by a user action, for example. + */ +int os_banner_getinfo(void *banner_handle, os_banner_info_t *info); + +/* + * Get the character width/height of the banner, for layout purposes. This + * gives the size of the banner in character cells. + * + * These are not meaningful when the underlying window uses a proportional + * font or varying fonts of different sizes. When the size of text varies + * in the window, the OS layer is responsible for word-wrapping and other + * layout, in which case these simply return zero. + * + * Note that these routines might appear to be redundant with the 'rows' + * and 'columns' information returned from os_banner_getinfo(), but these + * have two important distinctions. First, these routines return only the + * width and height information, so they can be implemented with less + * overhead than os_banner_getinfo(); this is important because formatters + * might need to call these routines frequently while formatting text. + * Second, these routines are not required to return an approximation for + * windows using proportional fonts, as os_banner_getinfo() does; these can + * simply return zero when a proportional font is in use. + */ +int os_banner_get_charwidth(void *banner_handle); +int os_banner_get_charheight(void *banner_handle); + +/* clear the contents of a banner */ +void os_banner_clear(void *banner_handle); + +/* + * Display output on a banner. Writes the output to the window on the + * display at the current output position. + * + * The following special characters should be recognized and handled: + * + * '\n' - newline; move output position to the start of the next line. + * + * '\r' - move output position to start of current line; subsequent text + * overwrites any text previously displayed on the current line. It is + * permissible to delete the old text immediately on seeing the '\r', + * rather than waiting for additional text to actually overwrite it. + * + * All other characters should simply be displayed as ordinary printing + * text characters. Note that tab characters should not be passed to this + * routine, but if they are, they can simply be treated as ordinary spaces + * if desired. Other control characters (backspace, escape, etc) should + * never be passed to this routine; the implementation is free to ignore + * any control characters not listed above. + * + * If any text displayed here overflows the current boundaries of the + * window on the screen, the text MUST be "clipped" to the current window + * boundaries; in other words, anything this routine tries to display + * outside of the window's on-screen rectangle must not actually be shown + * on the screen. + * + * Text overflowing the display boundaries MUST also be retained in an + * internal buffer. This internal buffer can be limited to the actual + * maximum display size of the terminal screen or application window, if + * desired. It is necessary to retain clipped text, because this allows a + * window to be expanded to the size of its contents AFTER the contents + * have already been displayed. + * + * If the banner does its own line wrapping, it must indicate this via the + * os_line_wrap flag in the os_banner_getinfo() return data. If the + * banner doesn't indicate this flag, then it must not do any line + * wrapping at all, even if the caller attempts to write text beyond the + * right edge of the window - any text overflowing the width of the window + * must simply be clipped. + * + * Text grid banners must ALWAYS clip - these banners should never perform + * any line wrapping. + */ +void os_banner_disp(void *banner_handle, const char *txt, size_t len); + +/* + * Set the text attributes in a banner, for subsequent text displays. + * 'attr' is a (bitwise-OR'd) combination of OS_ATTR_xxx values. + */ +void os_banner_set_attr(void *banner_handle, int attr); + +/* + * Set the text color in a banner, for subsequent text displays. The 'fg' + * and 'bg' colors are given as RGB or parameterized colors; see the + * definition of os_color_t for details. + * + * If the underlying renderer is HTML-enabled, then this should not be + * used; the appropriate HTML code should simply be displayed to the + * banner instead. + */ +void os_banner_set_color(void *banner_handle, os_color_t fg, os_color_t bg); + +/* + * Set the screen color in the banner - this is analogous to the screen + * color in the main text area. + * + * If the underlying renderer is HTML-enabled, then this should not be + * used; the HTML <BODY> tag should be used instead. + */ +void os_banner_set_screen_color(void *banner_handle, os_color_t color); + +/* flush output on a banner */ +void os_banner_flush(void *banner_handle); + +/* + * Set the banner's size. The size has the same meaning as in + * os_banner_create(). + * + * 'is_advisory' indicates whether the sizing is required or advisory only. + * If this flag is false, then the size should be set as requested. If + * this flag is true, it means that the caller intends to call + * os_banner_size_to_contents() at some point, and that the size being set + * now is for advisory purposes only. Platforms that support + * size-to-contents may simply ignore advisory sizing requests, although + * they might want to ensure that they have sufficient off-screen buffer + * space to keep track of the requested size of display, so that the + * information the caller displays in preparation for calling + * size-to-contents will be retained. Platforms that do not support + * size-to-contents should set the requested size even when 'is_advisory' + * is true. + */ +void os_banner_set_size(void *banner_handle, int siz, int siz_units, + int is_advisory); + +/* + * Set the banner to the size of its current contents. This can be used + * to set the banner's size after some text (or other material) has been + * displayed to the banner, so that the size can be set according to the + * banner's actual space requirements. + * + * This changes the banner's "requested size" to match the current size. + * Subsequent calls to os_banner_getinfo() will thus indicate a requested + * size according to the size set here. + */ +void os_banner_size_to_contents(void *banner_handle); + +/* + * Turn HTML mode on/off in the banner window. If the underlying renderer + * doesn't support HTML, these have no effect. + */ +void os_banner_start_html(void *banner_handle); +void os_banner_end_html(void *banner_handle); + +/* + * Set the output coordinates in a text grid window. The grid window is + * arranged into character cells numbered from row zero, column zero for + * the upper left cell. This function can only be used if the window was + * created with type OS_BANNER_TYPE_TEXTGRID; the request should simply be + * ignored by other window types. + * + * Moving the output position has no immediate effect on the display, and + * does not itself affect the "content size" for the purposes of + * os_banner_size_to_contents(). This simply sets the coordinates where + * any subsequent text is displayed. + */ +void os_banner_goto(void *banner_handle, int row, int col); + + +/* ------------------------------------------------------------------------ */ +/* + * Get system information. 'code' is a SYSINFO_xxx code, which + * specifies what type of information to get. The 'param' argument's + * meaning depends on which code is selected. 'result' is a pointer to + * an integer that is to be filled in with the result value. If the + * code is not known, this function should return FALSE. If the code is + * known, the function should fill in *result and return TRUE. + */ +int os_get_sysinfo(int code, void *param, long *result); + +/* determine if systemInfo is supported - os_get_sysinfo never gets this */ +#define SYSINFO_SYSINFO 1 + +/* get interpreter version number - os_get_sysinfo never gets this */ +#define SYSINFO_VERSION 2 + +/* get operating system name - os_get_sysinfo never gets this */ +#define SYSINFO_OS_NAME 3 + +/* + * Can the system process HTML directives? returns 1 if so, 0 if not. + * Note that if this returns false, then all of the codes below from + * JPEG to LINKS are implicitly false as well, since TADS can only use + * images, sounds, and links through HTML. + */ +#define SYSINFO_HTML 4 + +/* can the system display JPEG's? 1 if yes, 0 if no */ +#define SYSINFO_JPEG 5 + +/* can the system display PNG's? 1 if yes, 0 if no */ +#define SYSINFO_PNG 6 + +/* can the system play WAV's? 1 if yes, 0 if no */ +#define SYSINFO_WAV 7 + +/* can the system play MIDI's? 1 if yes, 0 if no */ +#define SYSINFO_MIDI 8 + +/* can the system play MIDI and WAV's simultaneously? yes=1, no=0 */ +#define SYSINFO_WAV_MIDI_OVL 9 + +/* can the system play multiple WAV's simultaneously? yes=1, no=0 */ +#define SYSINFO_WAV_OVL 10 + +/* + * GENERAL NOTES ON PREFERENCE SETTINGS: + * + * Several of the selectors below refer to the preference settings. We're + * talking about user-settable options to control various aspects of the + * interpreter. The conventional GUI for this kind of thing is a dialog + * box reachable through a menu command named something like "Options" or + * "Preferences". A couple of general notes about these: + * + * 1. The entire existence of a preferences mechanism is optional - + * interpreter writers aren't required to implement anything along these + * lines. In some cases the local platforms might not have any suitable + * conventions for a preferences UI (e.g., character-mode console + * applications), and in other cases the terp developer might just want to + * omit a prefs mechanism because of the work involved to implement it, or + * to keep the UI simpler. + * + * 2. If a given SYSINFO_PREF_xxx selector refers to a preference item + * that's not implemented in the local interpreter, the terp should simply + * return a suitable default result. For example, if the interpreter + * doesn't have a preference item to allow the user to turn sounds off, the + * selector SYSINFO_PREF_SOUNDS should return 1 to indicate that the user + * has not in fact turned off sounds (because there's no way to do so). + * + * 3. The various SYSINFO_PREF_xxx selectors are purely queries - they're + * NOT a mechanism for enforcing the preferences. For example, if the + * interpreter provides a "Sounds On/Off" option, it's up to the terp to + * enforce it the Off setting by ignoring any sound playback requests. The + * game isn't under any obligation to query any of the preferences or to + * alter its behavior based on their settings - you should expect that the + * game will go on trying to play sounds even when "Sounds Off" is selected + * in the preferences. The purpose of these SYSINFO selectors is to let + * the game determine the current presentation status, but *only if it + * cares*. For example, the game might determine whether or not sounds are + * actually being heard just before playing a sound effect that's important + * to the progress of the game, so that it can provide a visual alternative + * if the effect won't be heard. + */ + +/* + * Get image preference setting - 1 = images can be displayed, 0 = images + * are not being displayed because the user turned off images in the + * preferences. This is, of course, irrelevant if images can't be + * displayed at all. + * + * See the general notes on preferences queries above. + */ +#define SYSINFO_PREF_IMAGES 11 + +/* + * Get digitized sound effect (WAV) preference setting - 1 = sounds can be + * played, 0 = sounds are not being played because the user turned off + * sounds in the preferences. + * + * See the general notes on preferences queries above. + */ +#define SYSINFO_PREF_SOUNDS 12 + +/* + * Get music (MIDI) preference setting - 1 = music can be played, 0 = music + * is not being played because the user turned off music in the + * preferences. + * + * See the general notes on preferences queries above. + */ +#define SYSINFO_PREF_MUSIC 13 + +/* + * Get link display preference setting - 0 = links are not being displayed + * because the user set a preference item that suppresses all links (which + * doesn't actually hide them, but merely displays them and otherwise + * treats them as ordinary text). 1 = links are to be displayed normally. + * 2 = links can be displayed temporarily by the user by pressing a key or + * some similar action, but aren't being displayed at all times. + * + * See the general note on preferences queries above. + */ +#define SYSINFO_PREF_LINKS 14 + +/* can the system play MPEG sounds of any kind? */ +#define SYSINFO_MPEG 15 + +/* can the system play MPEG audio 2.0 layer I/II/III sounds? */ +#define SYSINFO_MPEG1 16 +#define SYSINFO_MPEG2 17 +#define SYSINFO_MPEG3 18 + +/* + * is the system *currently* in HTML mode? os_get_sysinfo never gets + * this code, since the portable output layer keeps track of this + */ +#define SYSINFO_HTML_MODE 19 + +/* + * Does the system allow following external URL links of the various + * types? These return true if the system is capable of following these + * types of hypertext links, false if not. Note that, if the system is + * capable of following these links, these should return true regardless + * of any current mode settings; in particular, these should not be + * sensitive to the current HTML mode or the current link display mode, + * since the question is not whether a link now displayed can be + * followed by the user, but rather whether the system has the + * capability to follow these types of links at all. + */ +#define SYSINFO_LINKS_HTTP 20 +#define SYSINFO_LINKS_FTP 21 +#define SYSINFO_LINKS_NEWS 22 +#define SYSINFO_LINKS_MAILTO 23 +#define SYSINFO_LINKS_TELNET 24 + +/* is PNG transparency supported? */ +#define SYSINFO_PNG_TRANS 25 + +/* is PNG alpha blending supported? */ +#define SYSINFO_PNG_ALPHA 26 + +/* is the Ogg Vorbis audio format supported? */ +#define SYSINFO_OGG 27 + +/* can the system display MNG's? */ +#define SYSINFO_MNG 28 + +/* can the system display MNG's with transparency? */ +#define SYSINFO_MNG_TRANS 29 + +/* can the system display MNG's with alpha blending? */ +#define SYSINFO_MNG_ALPHA 30 + +/* can we display highlighted text in its own appearance? */ +#define SYSINFO_TEXT_HILITE 31 + +/* + * Can we display text colors? This returns a SYSINFO_TXC_xxx code + * indicating the level of color support. + * + * The os_xxx interfaces don't presently support anything beyond the ANSI + * colors; however, HTML-enabled interpreters generally support full RGB + * colors, so we call this out as a separate level. + */ +#define SYSINFO_TEXT_COLORS 32 + +/* no text color support */ +#define SYSINFO_TXC_NONE 0 + +/* parameterized color names only (OS_COLOR_P_TEXT, etc) */ +#define SYSINFO_TXC_PARAM 1 + +/* + * we support only the basic ANSI colors, foreground control only (white, + * black, blue, red, green, yellow, cyan, magenta) + */ +#define SYSINFO_TXC_ANSI_FG 2 + +/* ANSI colors, foreground and background */ +#define SYSINFO_TXC_ANSI_FGBG 3 + +/* full RGB support */ +#define SYSINFO_TXC_RGB 4 + +/* are the os_banner_xxx() interfaces supported? */ +#define SYSINFO_BANNERS 33 + +/* Interpreter Class - this returns one of the SYSINFO_ICLASS_xxx codes */ +#define SYSINFO_INTERP_CLASS 34 + +/* + * Interpreter class: Character-mode Text-Only. Interpreters of this class + * use a single, fixed-pitch font to display all text, and use the + * text-only HTML subset, and cannot display graphics. + */ +#define SYSINFO_ICLASS_TEXT 1 + +/* + * Interpreter class: Text-Only GUI. Interpreters of this class are + * traditional text-only interpreters running on graphical operating + * systems. These interpreters might use multiple fonts (for example, they + * might display highlighted text in boldface), and might use + * proportionally-spaced text for some windows. These interpreters support + * the text-only HTML subset, and cannot display graphics. + * + * Text-only GUI interpreters act essentially the same as character-mode + * text-only interpreters, from the perspective of the client program. + */ +#define SYSINFO_ICLASS_TEXTGUI 2 + +/* + * Interpreter class: HTML. Interpreters of this class can display + * graphics and sounds, can display multiple fonts and font sizes, can use + * proportional fonts, and support the full HTML TADS markup language for + * formatting. + */ +#define SYSINFO_ICLASS_HTML 3 + +/* + * Audio fade information. + * + * SYSINFO_AUDIO_FADE: basic fade-in and fade-out support. Interpreters + * that don't support audio fade at all should return 0. Interpreters that + * support fades should return a bitwise OR'd combination of + * SYSINFO_AUDIOFADE_xxx flags below indicating which formats can be used + * with fades. + * + * SYSINFO_AUDIO_CROSSFADE: cross-fades are supported (i.e., simultaneous + * play of overlapping tracks, one fading out while the other fades in). + * If cross-fades aren't supported, return 0. If they're supported, return + * a combination of SYSINFO_AUDIOFADE_xxx flags indicating which formats + * can be used with cross-fades. + */ +#define SYSINFO_AUDIO_FADE 35 +#define SYSINFO_AUDIO_CROSSFADE 36 + +/* + * Specific audio fading features. These are bit flags that can be + * combined to indicate the fading capabilities of the interpreter. + */ +#define SYSINFO_AUDIOFADE_MPEG 0x0001 /* supported for MPEG audio */ +#define SYSINFO_AUDIOFADE_OGG 0x0002 /* supported for Ogg Vorbis */ +#define SYSINFO_AUDIOFADE_WAV 0x0004 /* supported for WAV */ +#define SYSINFO_AUDIOFADE_MIDI 0x0008 /* supported for MIDI */ + + +/* ------------------------------------------------------------------------ */ +/* + * Integer division operators. For any compiler that follows ANSI C + * rules, no definitions are required for these routine, since the + * standard definitions below will work properly. However, if your + * compiler does not follow ANSI standards with respect to integer + * division of negative numbers, you must provide implementations of + * these routines that produce the correct results. + * + * Division must "truncate towards zero," which means that any + * fractional part is dropped from the result. If the result is + * positive, the result must be the largest integer less than the + * algebraic result: 11/3 yields 3. If the result is negative, the + * result must be the smallest integer less than the result: (-11)/3 + * yields -3. + * + * The remainder must obey the relationship (a/b)*b + a%b == a, for any + * integers a and b (b != 0). + * + * If your compiler does not obey the ANSI rules for the division + * operators, make the following changes in your osxxx.h file + * + * - define the symbol OS_NON_ANSI_DIVIDE in the osxxx.h file + * + * - either define your own macros for os_divide_long() and + * os_remainder_long(), or put actual prototypes for these functions + * into your osxxx.h file and write appropriate implementations of these + * functions in one of your osxxx.c or .cpp files. + */ +/* long os_divide_long(long a, long b); // returns (a/b) with ANSI rules */ +/* long os_remainder_long(long a, long b); // returns (a%b) with ANSI rules */ + +/* standard definitions for any ANSI compiler */ +#ifndef OS_NON_ANSI_DIVIDE +#define os_divide_long(a, b) ((a) / (b)) +#define os_remainder_long(a, b) ((a) % (b)) +#endif + +/* ------------------------------------------------------------------------ */ +/* + * Special "switch" statement optimization flags. These definitions are + * OPTIONAL - if a platform doesn't provide these definitions, suitable + * code that's fully portable will be used. + * + * On some compilers, the performance of a "switch" statement can be + * improved by fully populating the switch with all possible "case" + * values. When the compiler can safely assume that every possible "case" + * value is specifically called out in the switch, the compiler can + * generate somewhat faster code by omitting any range check for the + * controlling expression of the switch: a range check is unnecessary + * because the compiler knows that the value can never be outside the + * "case" table. + * + * This type of optimization doesn't apply to all compilers. When a given + * platform's compiler can be coerced to produce faster "switch" + * statements, though, there might be some benefit in defining these + * symbols according to local platform rules. + * + * First, #define OS_FILL_OUT_CASE_TABLES if you want this type of switch + * optimization at all. This symbol is merely a flag, so it doesn't need + * a value - #defining it is enough to activate the special code. If you + * don't define this symbol, then the code that cares about this will + * simply generate ordinary switches, leaving holes in the case table and + * using "default:" to cover them. If the platform's osxxx.h header does + * #define OS_FILL_OUT_CASE_TABLES, then the portable code will know to + * fill out case tables with all possible alternatives where it's possible + * and potentially beneficial to do so. + * + * Second, if you #define OS_FILL_OUT_CASE_TABLES, you MUST ALSO #define + * OS_IMPOSSIBLE_DEFAULT_CASE. The value for this symbol must be some + * code to insert into a "switch" statement at the point where a + * "default:" case would normally go. On some compilers, special + * extensions allow the program to explicitly indicate within a switch + * that all possible cases are covered, so that the compiler doesn't have + * to be relied upon to infer this for itself (which would be impossible + * for it to do in many cases anyway). You can use an empty definition + * for this symbol if your compiler doesn't have any special construct for + * indicating a fully-populated case table. + * + * Note that *most* switch statements in portable code won't care one way + * or the other about these definitions. There's a time/space trade-off + * in fully populating a switch's case table, so only the most + * time-critical code will bother trying. + */ + + + +/* ------------------------------------------------------------------------ */ +/* + * TADS 2 swapping configuration. Define OS_DEFAULT_SWAP_ENABLED to 0 + * if you want swapping turned off, 1 if you want it turned on. If we + * haven't defined a default swapping mode yet, turn swapping on by + * default. + */ +#ifndef OS_DEFAULT_SWAP_ENABLED +# define OS_DEFAULT_SWAP_ENABLED 1 +#endif + +/* + * If the system "long description" (for the banner) isn't defined, make + * it the same as the platform ID string. + */ +#ifndef OS_SYSTEM_LDESC +# define OS_SYSTEM_LDESC OS_SYSTEM_NAME +#endif + +/* + * TADS 2 Usage Lines + * + * If the "usage" lines (i.e., the descriptive lines of text describing + * how to run the various programs) haven't been otherwise defined, + * define defaults for them here. Some platforms use names other than + * tc, tr, and tdb for the tools (for example, on Unix they're usually + * tadsc, tadsr, and tadsdb), so the usage lines should be adjusted + * accordingly; simply define them earlier than this point (in this file + * or in one of the files included by this file, such as osunixt.h) for + * non-default text. + */ +#ifndef OS_TC_USAGE +# define OS_TC_USAGE "usage: tc [options] file" +#endif +#ifndef OS_TR_USAGE +# define OS_TR_USAGE "usage: tr [options] file" +#endif +#ifndef OS_TDB_USAGE +# define OS_TDB_USAGE "usage: tdb [options] file" +#endif + +/* + * Ports can define a special TDB startup message, which is displayed + * after the copyright/version banner. If it's not defined at this + * point, define it to an empty string. + */ +#ifndef OS_TDB_STARTUP_MSG +# define OS_TDB_STARTUP_MSG "" +#endif + +/* + * If a system patch sub-level isn't defined, define it here as zero. + * The patch sub-level is used on some systems where a number of ports + * are derived from a base port (for example, a large number of ports + * are based on the generic Unix port). For platforms like the Mac, + * where the porting work applies only to that one platform, this + * sub-level isn't meaningful. + */ +#ifndef OS_SYSTEM_PATCHSUBLVL +# define OS_SYSTEM_PATCHSUBLVL "0" +#endif + } // End of namespace TADS2 } // End of namespace TADS } // End of namespace Glk diff --git a/engines/glk/tads/tads2/post_compilation.h b/engines/glk/tads/tads2/post_compilation.h new file mode 100644 index 0000000000..1dde838d02 --- /dev/null +++ b/engines/glk/tads/tads2/post_compilation.h @@ -0,0 +1,96 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* definitions for post-compilation setup + * + * Does post-compilation setup, such as setting up contents lists + * faster run-time performance, but run - time errors could be disastrous. + */ + +#ifndef GLK_TADS_TADS2_POST_COMPILATION +#define GLK_TADS_TADS2_POST_COMPILATION + +#include "glk/tads/tads2/run.h" +#include "glk/tads/tads2/tokenizer.h" +#include "glk/tads/tads2/vocabulary.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* setup context */ +struct supcxdef { + errcxdef *supcxerr; + mcmcxdef *supcxmem; /* memory manager client context */ + voccxdef *supcxvoc; /* player command parsing context */ + tokthdef *supcxtab; /* top-level symbol table */ + runcxdef *supcxrun; /* execution context */ + uchar *supcxbuf; /* space for building a list */ + ushort supcxlen; /* size of buffer */ +}; + +/* set up contents list for one object for demand-on-load */ +void supcont(void *ctx, objnum obj, prpnum prp); + +/* set up inherited vocabulary (called before executing game) */ +void supivoc(supcxdef *ctx); + +/* find required objects/functions */ +void supfind(errcxdef *ctx, tokthdef *tab, voccxdef *voc, + objnum *preinit, int warnlevel, int casefold); + +/* set up reserved words */ +void suprsrv(supcxdef *sup, void (*bif[])(struct bifcxdef *, int), + toktdef *tab, int fncntmax, int v1compat, char *new_do, + int casefold); + +/* set up built-in functions without symbol table (for run-time) */ +void supbif(supcxdef *sup, void (*bif[])(struct bifcxdef *, int), + int bifsiz); + +/* log an undefined-object error */ +void sup_log_undefobj(mcmcxdef *mctx, errcxdef *ec, int err, + char *sym_name, int sym_name_len, objnum objn); + +/* set up inherited vocabulary for a particular object */ +void supivoc1(supcxdef *sup, voccxdef *ctx, vocidef *v, objnum target, + int inh_from_obj, int flags); + +/* get name of an object out of symbol table */ +void supgnam(char *buf, tokthdef *tab, objnum objn); + +/* table of built-in functions */ +typedef struct supbidef supbidef; +struct supbidef +{ + char *supbinam; /* name of function */ + void (*supbifn)(struct bifcxdef *, int); /* C routine to call */ +}; + +/* external definition for special token table */ +extern tokldef supsctab[]; + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/property.h b/engines/glk/tads/tads2/property.h new file mode 100644 index 0000000000..86a4b69d67 --- /dev/null +++ b/engines/glk/tads/tads2/property.h @@ -0,0 +1,167 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* property definitions + * + * A property structure must be binary-portable, because properties are + * stored in objects, which must be binary-portable. Hence, the internal + * structure of a property header is not a C structure, but a portable + * sequence of bytes. Multi-byte quantities are stored in Intel format. + * + * property number - 2 bytes + * property datatype - 1 byte + * property size - 2 bytes + * property flags - 1 byte + * + * This header is followed immediately by the property value. For + * convenience, a set of macros is defined to provide access to the + * fields of a property header. + */ + +#ifndef GLK_TADS_TADS2_PROPERTY +#define GLK_TADS_TADS2_PROPERTY + +#include "glk/tads/tads.h" +#include "glk/tads/tads2/data.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/** + * a property number, used to look up all properties */ +typedef ushort prpnum; + +typedef uchar prpdef; /* prpdef is just an array of bytes */ +#define PRPHDRSIZ 6 /* "sizeof(prpdef)" - size of property header */ + +/* Macros to provide access to property header entries */ +#define prpprop(p) osrp2((uchar *)(p)) +#define prptype(p) (*(((uchar *)(p)) + 2)) +#define prpsize(p) osrp2((((uchar *)(p)) + 3)) +#define prpflg(p) (*(((uchar *)(p)) + 5)) +#define prpvalp(p) (((uchar *)(p)) + 6) + +#define prpsetprop(p,n) oswp2((uchar *)(p), n) +#define prpsetsize(p,s) oswp2((((uchar *)(p)) + 3), s) + +/* property flag values */ +#define PRPFORG 0x01 /* property is original startup value */ +#define PRPFIGN 0x02 /* ignore this prop (has been changed) */ +#define PRPFDEL 0x04 /* property has been permanently deleted */ + +/** + * Invalid property number - this number will never be used as an actual + * property, so it can be used to signify the lack of a valid property + */ +#define PRP_INVALID 0 + +/* certain property types are special, and are reserved here */ +#define PRP_DOACTION 1 /* doAction property */ + +/* vocabulary properties - keep these contiguous, and must start at 2 */ +#define PRP_VERB 2 /* verb vocabulary property */ +#define PRP_NOUN 3 /* noun vocabulary property */ +#define PRP_ADJ 4 /* adjective vocabulary property */ +#define PRP_PREP 5 /* preposition vocabulary property */ +#define PRP_ARTICLE 6 /* article vocabulary property */ +#define PRP_PLURAL 7 /* plural vocabulary property */ + +/* determine if a property is a vocab property */ +/* int prpisvoc(prpnum p); */ +#define prpisvoc(p) ((p) >= PRP_VERB && (p) <= PRP_PLURAL) + +/* more properties... */ +#define PRP_SDESC 8 +#define PRP_THEDESC 9 +#define PRP_DODEFAULT 10 +#define PRP_IODEFAULT 11 +#define PRP_IOACTION 12 +#define PRP_LOCATION 13 +#define PRP_VALUE 14 +#define PRP_ROOMACTION 15 +#define PRP_ACTORACTION 16 +#define PRP_CONTENTS 17 +#define PRP_TPL 18 /* special built-in TEMPLATE structure */ +#define PRP_PREPDEFAULT 19 +#define PRP_VERACTOR 20 +#define PRP_VALIDDO 21 +#define PRP_VALIDIO 22 +#define PRP_LOOKAROUND 23 +#define PRP_ROOMCHECK 24 +#define PRP_STATUSLINE 25 +#define PRP_LOCOK 26 +#define PRP_ISVIS 27 +#define PRP_NOREACH 28 +#define PRP_ISHIM 29 +#define PRP_ISHER 30 +#define PRP_ACTION 31 /* action method */ +#define PRP_VALDOLIST 32 /* validDoList */ +#define PRP_VALIOLIST 33 /* validIoList */ +#define PRP_IOBJGEN 34 /* iobjGen */ +#define PRP_DOBJGEN 35 /* dobjGen */ +#define PRP_NILPREP 36 /* nilPrep */ +#define PRP_REJECTMDO 37 /* rejectMultiDobj */ +#define PRP_MOVEINTO 38 /* moveInto */ +#define PRP_CONSTRUCT 39 /* construct */ +#define PRP_DESTRUCT 40 /* destruct */ +#define PRP_VALIDACTOR 41 /* validActor */ +#define PRP_PREFACTOR 42 /* preferredActor */ +#define PRP_ISEQUIV 43 /* isEquivalent */ +#define PRP_ADESC 44 +#define PRP_MULTISDESC 45 +#define PRP_TPL2 46 /* new-style built-in TEMPLATE structure */ +#define PRP_ANYVALUE 47 /* anyvalue(n) - value to use for '#' with ANY */ +#define PRP_NEWNUMOBJ 48 /* newnumbered(n) - create new numbered object */ +#define PRP_UNKNOWN 49 /* internal property for unknown words */ +#define PRP_PARSEUNKNOWNDOBJ 50 /* parseUnknownDobj */ +#define PRP_PARSEUNKNOWNIOBJ 51 /* parseUnknownIobj */ +#define PRP_DOBJCHECK 52 /* dobjCheck */ +#define PRP_IOBJCHECK 53 /* iobjCheck */ +#define PRP_VERBACTION 54 /* verbAction */ +#define PRP_DISAMBIGDO 55 /* disambigDobj */ +#define PRP_DISAMBIGIO 56 /* disambigIobj */ +#define PRP_PREFIXDESC 57 /* prefixdesc */ +#define PRP_ISTHEM 58 /* isThem */ + +/* properties used by TADS/Graphic */ +#define PRP_GP_PIC 100 /* gp_picture */ +#define PRP_GP_NAME 101 /* gp_name */ +#define PRP_GP_DEFVERB 102 /* gp_defverb */ +#define PRP_GP_ACTIVE 103 /* gp_active */ +#define PRP_GP_HOTLIST 104 /* gp_hotlist */ +#define PRP_GP_ICON 105 /* gp_icon */ +#define PRP_GP_DEFVERB2 106 /* gp_defverb2 */ +#define PRP_GP_DEFPREP 107 /* gp_defprep */ +#define PRP_GP_HOTID 108 /* gp_hotid */ +#define PRP_GP_OVERLAY 109 /* gp_overlay */ +#define PRP_GP_HOTX 110 /* gp_hotx */ +#define PRP_GP_HOTY 111 /* gp_hoty */ + +/* highest reserved property number - must match last one above */ +#define PRP_LASTRSV 111 + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/regex.cpp b/engines/glk/tads/tads2/regex.cpp index a7f7d99369..5d608dfa4a 100644 --- a/engines/glk/tads/tads2/regex.cpp +++ b/engines/glk/tads/tads2/regex.cpp @@ -68,10 +68,8 @@ Notes */ #include "glk/tads/tads2/regex.h" -#include "glk/tads/tads2/ler.h" -#include "glk/tads/tads2/os.h" -//#include "glk/tads/tads2/std.h" -#//include "engines/glk/tads/tads2/ler.h" +#include "glk/tads/tads2/memory_cache_heap.h" +#include "common/util.h" namespace Glk { namespace TADS { @@ -128,1148 +126,1461 @@ enum { RE_GROUP_MATCH_9 = (RE_GROUP_MATCH_0 + 9) }; -re_context::re_context(errcxdef *errctx) { - // save the error context - _errctx = errctx; - - // clear states - _next_state = RE_STATE_FIRST_VALID; +/* ------------------------------------------------------------------------ */ +/* + * A machine description. Machines are fully described by their initial + * and final state ID's. + */ +struct re_machine { + /* the machine's initial state */ + re_state_id init; - // clear groups - _cur_group = 0; + /* the machine's final state */ + re_state_id final; +}; - // no string buffer yet - _strbuf = 0; +/* ------------------------------------------------------------------------ */ +/* + * Initialize the context. The memory for the context structure itself + * is allocated and maintained by the caller. + */ +void re_init(re_context *ctx, errcxdef *errctx) +{ + /* save the error context */ + ctx->errctx = errctx; + + /* no tuple array yet */ + ctx->tuple_arr = 0; + ctx->tuples_alloc = 0; + + /* clear states */ + ctx->next_state = RE_STATE_FIRST_VALID; + + /* clear groups */ + ctx->cur_group = 0; + + /* no string buffer yet */ + ctx->strbuf = 0; } -re_context::~re_context() { - // reset state - reset(); - - // if we allocated a string buffer, delete it - if (_strbuf != 0) { - mchfre(_strbuf); - _strbuf = nullptr; - } +/* ------------------------------------------------------------------------ */ +/* + * Reset compiler - clears states and tuples + */ +static void re_reset(re_context *ctx) +{ + int i; + + /* delete any range tables we've allocated */ + for (i = 0 ; i < ctx->next_state ; ++i) + { + if (ctx->tuple_arr[i].char_range != 0) + { + mchfre(ctx->tuple_arr[i].char_range); + ctx->tuple_arr[i].char_range = 0; + } + } + + /* clear states */ + ctx->next_state = RE_STATE_FIRST_VALID; + + /* clear groups */ + ctx->cur_group = 0; } -void re_context::reset() { - int i; - - // delete any range tables we've allocated - for (i = 0 ; i < _next_state ; ++i) { - if (_tuple_arr[i].char_range != 0) { - mchfre(_tuple_arr[i].char_range); - _tuple_arr[i].char_range = 0; - } - } - - // clear states - _next_state = RE_STATE_FIRST_VALID; - - // clear groups - _cur_group = 0; +/* ------------------------------------------------------------------------ */ +/* + * Delete the context - frees structures associated with the context. + * Does NOT free the memory used by the context structure itself. + */ +void re_delete(re_context *ctx) +{ + /* reset state */ + re_reset(ctx); + + /* if we've allocated an array, delete it */ + if (ctx->tuple_arr != 0) + { + mchfre(ctx->tuple_arr); + ctx->tuple_arr = 0; + } + + /* if we allocated a string buffer, delete it */ + if (ctx->strbuf != 0) + { + mchfre(ctx->strbuf); + ctx->strbuf = 0; + } } -re_state_id re_context::alloc_state() { - // If we don't have enough room for another state, expand the array - if (_next_state >= (int)_tuple_arr.size()) { - // bump the size by a bit - _tuple_arr.resize(_tuple_arr.size() + 100); - } - - // initialize the next state - _tuple_arr[_next_state].next_state_1 = RE_STATE_INVALID; - _tuple_arr[_next_state].next_state_2 = RE_STATE_INVALID; - _tuple_arr[_next_state].ch = RE_EPSILON; - _tuple_arr[_next_state].flags = 0; - _tuple_arr[_next_state].char_range = 0; - - // return the new state's ID - return _next_state++; +/* ------------------------------------------------------------------------ */ +/* + * Allocate a new state ID + */ +static re_state_id re_alloc_state(re_context *ctx) +{ + /* + * If we don't have enough room for another state, expand the array + */ + if (ctx->next_state >= ctx->tuples_alloc) + { + uint new_alloc; + + /* bump the size by a bit */ + new_alloc = ctx->tuples_alloc + 100; + + /* allocate or expand the array */ + if (ctx->tuples_alloc == 0) + { + /* allocate the initial memory block */ + ctx->tuple_arr = + (re_tuple *)mchalo(ctx->errctx, + (new_alloc * sizeof(re_tuple)), + "regex"); + } + else + { + re_tuple *ptr; + + /* allocate a new memory block */ + ptr = (re_tuple *)mchalo(ctx->errctx, + (new_alloc * sizeof(re_tuple)), + "regex"); + + /* copy the old memory to the new memory */ + memcpy(ptr, ctx->tuple_arr, ctx->tuples_alloc * sizeof(re_tuple)); + + /* free the old block */ + mchfre(ctx->tuple_arr); + + /* use the new block */ + ctx->tuple_arr = ptr; + } + + /* remember the new allocation size */ + ctx->tuples_alloc = new_alloc; + } + + /* initialize the next state */ + ctx->tuple_arr[ctx->next_state].next_state_1 = RE_STATE_INVALID; + ctx->tuple_arr[ctx->next_state].next_state_2 = RE_STATE_INVALID; + ctx->tuple_arr[ctx->next_state].ch = RE_EPSILON; + ctx->tuple_arr[ctx->next_state].flags = 0; + ctx->tuple_arr[ctx->next_state].char_range = 0; + + /* return the new state's ID */ + return ctx->next_state++; } -void re_context::set_trans(re_state_id id, re_state_id dest_id, char ch) { - re_tuple *tuple; - - /* - * get the tuple containing the transitions for this state ID - the - * state ID is the index of the state's transition tuple in the array - */ - tuple = &_tuple_arr[id]; - - /* - * If the first state pointer hasn't been set yet, set it to the new - * destination. Otherwise, set the second state pointer. - * - * Only set the character on setting the first state. When setting - * the second state, we must assume that the character for the state - * has already been set, since any given state can have only one - * character setting. - */ - if (tuple->next_state_1 == RE_STATE_INVALID) { - /* - * set the character ID, unless the state has been marked with a - * special flag which indicates that the character value has - * another meaning (in particular, a group marker) - */ - if (!(tuple->flags & (RE_STATE_GROUP_BEGIN | RE_STATE_GROUP_END))) - tuple->ch = ch; - - // set the first transition - tuple->next_state_1 = dest_id; - } else { - // set only the second transition state - don't set the character - tuple->next_state_2 = dest_id; - } + +/* ------------------------------------------------------------------------ */ +/* + * Set a transition from a state to a given destination state. + */ +static void re_set_trans(re_context *ctx, + re_state_id id, re_state_id dest_id, char ch) +{ + re_tuple *tuple; + + /* + * get the tuple containing the transitions for this state ID - the + * state ID is the index of the state's transition tuple in the + * array + */ + tuple = &ctx->tuple_arr[id]; + + /* + * If the first state pointer hasn't been set yet, set it to the new + * destination. Otherwise, set the second state pointer. + * + * Only set the character on setting the first state. When setting + * the second state, we must assume that the character for the state + * has already been set, since any given state can have only one + * character setting. + */ + if (tuple->next_state_1 == RE_STATE_INVALID) + { + /* + * set the character ID, unless the state has been marked with a + * special flag which indicates that the character value has + * another meaning (in particular, a group marker) + */ + if (!(tuple->flags & (RE_STATE_GROUP_BEGIN | RE_STATE_GROUP_END))) + tuple->ch = ch; + + /* set the first transition */ + tuple->next_state_1 = dest_id; + } + else + { + /* set only the second transition state - don't set the character */ + tuple->next_state_2 = dest_id; + } } -void re_context::init_machine(re_machine *machine) { - machine->init = alloc_state(); - machine->final = alloc_state(); +/* ------------------------------------------------------------------------ */ +/* + * Initialize a new machine, giving it an initial and final state + */ +static void re_init_machine(re_context *ctx, re_machine *machine) +{ + machine->init = re_alloc_state(ctx); + machine->final = re_alloc_state(ctx); } -void re_context::build_char(re_machine *machine, char ch) { - // initialize our new machine - init_machine(machine); +/* + * Build a character recognizer + */ +static void re_build_char(re_context *ctx, re_machine *machine, char ch) +{ + /* initialize our new machine */ + re_init_machine(ctx, machine); - // allocate a transition tuple for the new state - set_trans(machine->init, machine->final, ch); + /* allocate a transition tuple for the new state */ + re_set_trans(ctx, machine->init, machine->final, ch); } -void re_context::build_char_range(re_machine *machine, unsigned char *range, int exclusion) { - unsigned char *range_copy; - - // initialize our new machine - init_machine(machine); - - // allocate a transition table for the new state - set_trans(machine->init, machine->final, (char)(exclusion ? RE_RANGE_EXCL : RE_RANGE)); +/* + * Build a character range recognizer. 'range' is a 256-bit (32-byte) + * bit vector. + */ +static void re_build_char_range(re_context *ctx, re_machine *machine, + unsigned char *range, int exclusion) +{ + unsigned char *range_copy; + + /* initialize our new machine */ + re_init_machine(ctx, machine); + + /* allocate a transition table for the new state */ + re_set_trans(ctx, machine->init, machine->final, + (char)(exclusion ? RE_RANGE_EXCL : RE_RANGE)); + + /* allocate a copy of the range bit vector */ + range_copy = (unsigned char *)mchalo(ctx->errctx, 32, "regex range"); + + /* copy the caller's range */ + memcpy(range_copy, range, 32); + + /* store it in the tuple */ + ctx->tuple_arr[machine->init].char_range = range_copy; +} + - // allocate a copy of the range bit vector - range_copy = (unsigned char *)mchalo(_errctx, 32, "regex range"); +/* + * Build a group recognizer. This is almost the same as a character + * recognizer, but matches a previous group rather than a literal + * character. + */ +static void re_build_group_matcher(re_context *ctx, + re_machine *machine, int group_num) +{ + /* initialize our new machine */ + re_init_machine(ctx, machine); + + /* + * Allocate a transition tuple for the new state, using the group ID + * as the character code. Store the special code for a group + * recognizer rather than the normal literal character code. + */ + re_set_trans(ctx, machine->init, machine->final, + (char)(group_num + RE_GROUP_MATCH_0)); +} - // copy the caller's range - memcpy(range_copy, range, 32); - // store it in the tuple - _tuple_arr[machine->init].char_range = range_copy; +/* + * Build a concatenation recognizer + */ +static void re_build_concat(re_context *ctx, re_machine *new_machine, + re_machine *lhs, re_machine *rhs) +{ + /* initialize the new machine */ + re_init_machine(ctx, new_machine); + + /* + * set up an epsilon transition from the new machine's initial state + * to the first submachine's initial state + */ + re_set_trans(ctx, new_machine->init, lhs->init, RE_EPSILON); + + /* + * Set up an epsilon transition from the first submachine's final + * state to the second submachine's initial state + */ + re_set_trans(ctx, lhs->final, rhs->init, RE_EPSILON); + + /* + * Set up an epsilon transition from the second submachine's final + * state to our new machine's final state + */ + re_set_trans(ctx, rhs->final, new_machine->final, RE_EPSILON); } -void re_context::build_group_matcher(re_machine *machine, int group_num) { - // initialize our new machine - init_machine(machine); - - /* - * Allocate a transition tuple for the new state, using the group ID - * as the character code. Store the special code for a group - * recognizer rather than the normal literal character code. - */ - set_trans(machine->init, machine->final, (char)(group_num + RE_GROUP_MATCH_0)); +/* + * Build a group machine. sub_machine contains the machine that + * expresses the group's contents; we'll fill in new_machine with a + * newly-created machine that encloses and marks the group. + */ +static void re_build_group(re_context *ctx, re_machine *new_machine, + re_machine *sub_machine, int group_id) +{ + /* initialize the container machine */ + re_init_machine(ctx, new_machine); + + /* + * set up an epsilon transition from the new machine's initial state + * into the initial state of the group, and another transition from + * the group's final state into the container's final state + */ + re_set_trans(ctx, new_machine->init, sub_machine->init, RE_EPSILON); + re_set_trans(ctx, sub_machine->final, new_machine->final, RE_EPSILON); + + /* + * Mark the initial and final states of the group machine as being + * group markers. + */ + ctx->tuple_arr[new_machine->init].flags |= RE_STATE_GROUP_BEGIN; + ctx->tuple_arr[new_machine->final].flags |= RE_STATE_GROUP_END; + + /* store the group ID in the 'ch' member of the start and end states */ + ctx->tuple_arr[new_machine->init].ch = group_id; + ctx->tuple_arr[new_machine->final].ch = group_id; } -void re_context::build_concat(re_machine *new_machine, re_machine *lhs, re_machine *rhs) { - // initialize the new machine - init_machine(new_machine); - - /* - * Set up an epsilon transition from the new machine's initial state - * to the first submachine's initial state - */ - set_trans(new_machine->init, lhs->init, RE_EPSILON); - - /* - * Set up an epsilon transition from the first submachine's final - * state to the second submachine's initial state - */ - set_trans(lhs->final, rhs->init, RE_EPSILON); - - /* - * Set up an epsilon transition from the second submachine's final - * state to our new machine's final state - */ - set_trans(rhs->final, new_machine->final, RE_EPSILON); +/* + * Build an alternation recognizer + */ +static void re_build_alter(re_context *ctx, re_machine *new_machine, + re_machine *lhs, re_machine *rhs) +{ + /* initialize the new machine */ + re_init_machine(ctx, new_machine); + + /* + * Set up an epsilon transition from our new machine's initial state + * to the initial state of each submachine + */ + re_set_trans(ctx, new_machine->init, lhs->init, RE_EPSILON); + re_set_trans(ctx, new_machine->init, rhs->init, RE_EPSILON); + + /* + * Set up an epsilon transition from the final state of each + * submachine to our final state + */ + re_set_trans(ctx, lhs->final, new_machine->final, RE_EPSILON); + re_set_trans(ctx, rhs->final, new_machine->final, RE_EPSILON); } -void re_context::build_group(re_machine *new_machine, re_machine *sub_machine, int group_id) { - // initialize the container machine - init_machine(new_machine); - - /* - * set up an epsilon transition from the new machine's initial state - * into the initial state of the group, and another transition from - * the group's final state into the container's final state - */ - set_trans(new_machine->init, sub_machine->init, RE_EPSILON); - set_trans(sub_machine->final, new_machine->final, RE_EPSILON); - - // Mark the initial and final states of the group machine as being group markers - _tuple_arr[new_machine->init].flags |= RE_STATE_GROUP_BEGIN; - _tuple_arr[new_machine->final].flags |= RE_STATE_GROUP_END; - - // store the group ID in the 'ch' member of the start and end states - _tuple_arr[new_machine->init].ch = group_id; - _tuple_arr[new_machine->final].ch = group_id; +/* + * Build a closure recognizer + */ +static void re_build_closure(re_context *ctx, + re_machine *new_machine, re_machine *sub, + char specifier) +{ + /* initialize the new machine */ + re_init_machine(ctx, new_machine); + + /* + * set up an epsilon transition from our initial state to the + * submachine's initial state, and from the submachine's final state + * to our final state + */ + re_set_trans(ctx, new_machine->init, sub->init, RE_EPSILON); + re_set_trans(ctx, sub->final, new_machine->final, RE_EPSILON); + + /* + * If this is an unbounded closure ('*' or '+', but not '?'), set up + * the loop transition that takes us from the new machine's final + * state back to its initial state. We don't do this on the + * zero-or-one closure, because we can only match the expression + * once. + */ + if (specifier != '?') + re_set_trans(ctx, sub->final, sub->init, RE_EPSILON); + + /* + * If this is a zero-or-one closure or a zero-or-more closure, set + * up an epsilon transition from our initial state to our final + * state, since we can skip the entire subexpression. We don't do + * this on the one-or-more closure, because we can't skip the + * subexpression in this case. + */ + if (specifier != '+') + re_set_trans(ctx, new_machine->init, new_machine->final, RE_EPSILON); } -void re_context::build_alter(re_machine *new_machine, re_machine *lhs, re_machine *rhs) { - // initialize the new machine - init_machine(new_machine); - - /* - * Set up an epsilon transition from our new machine's initial state - * to the initial state of each submachine - */ - set_trans(new_machine->init, lhs->init, RE_EPSILON); - set_trans(new_machine->init, rhs->init, RE_EPSILON); - - /* - * Set up an epsilon transition from the final state of each - * submachine to our final state - */ - set_trans(lhs->final, new_machine->final, RE_EPSILON); - set_trans(rhs->final, new_machine->final, RE_EPSILON); +/* + * Build a null machine + */ +static void re_build_null_machine(re_context *ctx, re_machine *machine) +{ + machine->init = machine->final = RE_STATE_INVALID; } -void re_context::build_closure(re_machine *new_machine, re_machine *sub, char specifier) { - // initialize the new machine - init_machine(new_machine); - - /* - * Set up an epsilon transition from our initial state to the submachine's initial - * state, and from the submachine's final state to our final state - */ - set_trans(new_machine->init, sub->init, RE_EPSILON); - set_trans(sub->final, new_machine->final, RE_EPSILON); - - /* - * If this is an unbounded closure ('*' or '+', but not '?'), set up - * the loop transition that takes us from the new machine's final - * state back to its initial state. We don't do this on the - * zero-or-one closure, because we can only match the expression - * once. - */ - if (specifier != '?') - set_trans(sub->final, sub->init, RE_EPSILON); - - /* - * If this is a zero-or-one closure or a zero-or-more closure, set - * up an epsilon transition from our initial state to our final - * state, since we can skip the entire subexpression. We don't do - * this on the one-or-more closure, because we can't skip the - * subexpression in this case. - */ - if (specifier != '+') - set_trans(new_machine->init, new_machine->final, RE_EPSILON); +/* ------------------------------------------------------------------------ */ +/* + * Determine if a machine is null + */ +static int re_is_machine_null(re_context *ctx, re_machine *machine) +{ + return (machine->init == RE_STATE_INVALID); } -void re_context::concat_onto(re_machine *dest, re_machine *rhs) { - // check for a null destination machine - if (dest->isNull()) { - /* - * the first machine is null - simply copy the second machine - * onto the first unchanged - */ - *dest = *rhs; - } else { - re_machine new_machine; - - // build the concatenated machine - build_concat(&new_machine, dest, rhs); - - // copy the concatenated machine onto the first machine - *dest = new_machine; - } + +/* ------------------------------------------------------------------------ */ +/* + * Concatenate the second machine onto the first machine, replacing the + * first machine with the resulting machine. If the first machine is a + * null machine (created with re_build_null_machine), we'll simply copy + * the second machine into the first. + */ +static void re_concat_onto(re_context *ctx, + re_machine *dest, re_machine *rhs) +{ + /* check for a null destination machine */ + if (re_is_machine_null(ctx, dest)) + { + /* + * the first machine is null - simply copy the second machine + * onto the first unchanged + */ + *dest = *rhs; + } + else + { + re_machine new_machine; + + /* build the concatenated machine */ + re_build_concat(ctx, &new_machine, dest, rhs); + + /* copy the concatenated machine onto the first machine */ + *dest = new_machine; + } } -void re_context::alternate_onto(re_machine *dest, re_machine *rhs) { - // check to see if the first machine is null - if (dest->isNull()) { - /* - * the first machine is null - simply copy the second machine - * onto the first - */ - *dest = *rhs; - } else { - /* - * if the second machine is null, don't do anything; otherwise, - * build the alternation - */ - if (!rhs->isNull()) { - re_machine new_machine; - - // build the alternation - build_alter(&new_machine, dest, rhs); - - // replace the first machine with the alternation - *dest = new_machine; - } - } +/* + * Alternate the second machine onto the first machine, replacing the + * first machine with the resulting machine. If the first machine is a + * null machine, this simply replaces the first machine with the second + * machine. If the second machine is null, this simply leaves the first + * machine unchanged. + */ +static void re_alternate_onto(re_context *ctx, + re_machine *dest, re_machine *rhs) +{ + /* check to see if the first machine is null */ + if (re_is_machine_null(ctx, dest)) + { + /* + * the first machine is null - simply copy the second machine + * onto the first + */ + *dest = *rhs; + } + else + { + /* + * if the second machine is null, don't do anything; otherwise, + * build the alternation + */ + if (!re_is_machine_null(ctx, rhs)) + { + re_machine new_machine; + + /* build the alternation */ + re_build_alter(ctx, &new_machine, dest, rhs); + + /* replace the first machine with the alternation */ + *dest = new_machine; + } + } } -/** - * Set a bit in a bit vector. +/* ------------------------------------------------------------------------ */ +/* + * Set a bit in a bit vector. */ #define re_set_bit(set, bit) \ - (((unsigned char *)(set))[(bit) >> 3] |= (1 << ((bit) & 7))) + (((unsigned char *)(set))[(bit) >> 3] |= (1 << ((bit) & 7))) -/** - * Test a bit in a bit vector +/* + * Test a bit in a bit vector */ #define re_is_bit_set(set, bit) \ - ((((unsigned char *)(set))[(bit) >> 3] & (1 << ((bit) & 7))) != 0) - -re_status_t re_context::compile(const char *expr, size_t exprlen, re_machine *result_machine) { - re_machine cur_machine; - re_machine alter_machine; - re_machine new_machine; - size_t group_stack_level; - struct { - re_machine old_cur; - re_machine old_alter; - int group_id; - } group_stack[50]; - - // reset everything - reset(); - - // start out with no current machine and no alternate machine - cur_machine.build_null_machine(); - alter_machine.build_null_machine(); - - // nothing on the stack yet - group_stack_level = 0; - - // loop until we run out of expression to parse - for (; exprlen != 0 ; ++expr, --exprlen) { - switch(*expr) { - case '^': - /* - * beginning of line - if we're not at the beginning of the - * current expression (i.e., we already have some - * concatentations accumulated), treat it as an ordinary - * character - */ - if (!cur_machine.isNull()) - goto normal_char; - - // build a new start-of-text recognizer - build_char(&new_machine, RE_TEXT_BEGIN); - - /* - * concatenate it onto the string - note that this can't - * have any postfix operators - */ - concat_onto(&cur_machine, &new_machine); - break; - - case '$': - /* - * End of line specifier - if there's anything left after - * the '$' other than a close parens or alternation - * specifier, great it as a normal character - */ - if (exprlen > 1 - && (*(expr+1) != ')' && *(expr+1) != '|')) - goto normal_char; - - // build a new end-of-text recognizer - build_char(&new_machine, RE_TEXT_END); - - /* - * concatenate it onto the string - note that this can't - * have any postfix operators - */ - concat_onto(&cur_machine, &new_machine); - break; - - case '(': - /* - * Add a nesting level. Push the current machine and - * alternate machines onto the group stack, and clear - * everything out for the new group. - */ - if (group_stack_level > sizeof(group_stack)/sizeof(group_stack[0])) { - /* we cannot proceed - return an error */ - return RE_STATUS_GROUP_NESTING_TOO_DEEP; - } - - // save the current state on the stack - group_stack[group_stack_level].old_cur = cur_machine; - group_stack[group_stack_level].old_alter = alter_machine; - - /* - * Assign the group a group ID - groups are numbered in - * order of their opening (left) parentheses, so we want to - * assign a group number now. We won't actually need to - * know the group number until we get to the matching close - * paren, but we need to assign it now, so store it in the - * group stack. - */ - group_stack[group_stack_level].group_id = _cur_group; - - // consume the group number - _cur_group++; - - // push the level - ++group_stack_level; - - // start the new group with empty machines - cur_machine.build_null_machine(); - alter_machine.build_null_machine(); - break; - - case ')': - // if there's nothing on the stack, ignore this - if (group_stack_level == 0) - break; - - // take a level off the stack - --group_stack_level; - - /* - * Remove a nesting level. If we have a pending alternate - * expression, build the alternation expression. This will - * leave the entire group expression in alter_machine, - * regardless of whether an alternation was in progress or - * not. - */ - alternate_onto(&alter_machine, &cur_machine); - - /* - * Create a group machine that encloses the group and marks - * it with a group number. We assigned the group number - * when we parsed the open paren, so read that group number - * from the stack. - * - * Note that this will leave 'new_machine' with the entire - * group machine. - */ - build_group(&new_machine, &alter_machine, - group_stack[group_stack_level].group_id); - - /* - * Pop the stack - restore the alternation and current - * machines that were in progress before the group started. - */ - cur_machine = group_stack[group_stack_level].old_cur; - alter_machine = group_stack[group_stack_level].old_alter; - - /* - * Check the group expression (in new_machine) for postfix - * expressions - */ - goto apply_postfix; - - case '|': - /* - * Start a new alternation. This ends the current - * alternation; if we have a previous pending alternate, - * build an alternation machine out of the previous - * alternate and the current machine and move that to the - * alternate; otherwise, simply move the current machine to - * the pending alternate. - */ - alternate_onto(&alter_machine, &cur_machine); - - /* - * the alternation starts out with a blank slate, so null - * out the current machine - */ - cur_machine.build_null_machine(); - break; - - case '%': - // quoted character - skip the quote mark and see what we have - ++expr; - --exprlen; - - // check to see if we're at the end of the expression - if (exprlen == 0) { - /* - * end of the string - ignore it, but undo the extra - * increment of the expression index so that we exit the - * enclosing loop properly - */ - --expr; - ++exprlen; - break; - } - - // see what we have - switch(*expr) { - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - // group match - build a new literal group recognizer - build_group_matcher(&new_machine, (int)(*expr - '1')); - - // apply any postfix expression to the group recognizer - goto apply_postfix; - - case '<': - // build a beginning-of-word recognizer - build_char(&new_machine, RE_WORD_BEGIN); - - // it can't be postfixed - just concatenate it - concat_onto(&cur_machine, &new_machine); - break; - - case '>': - // build an end-of-word recognizer */ - build_char(&new_machine, RE_WORD_END); - - // it can't be postfixed - just concatenate it - concat_onto(&cur_machine, &new_machine); - break; - - case 'w': - // word character - build_char(&new_machine, RE_WORD_CHAR); - goto apply_postfix; - - case 'W': - // non-word character - build_char(&new_machine, RE_NON_WORD_CHAR); - goto apply_postfix; - - case 'b': - // word boundary - build_char(&new_machine, RE_WORD_BOUNDARY); - - // it can't be postfixed - concat_onto(&cur_machine, &new_machine); - break; - - case 'B': - // not a word boundary - build_char(&new_machine, RE_NON_WORD_BOUNDARY); - - // it can't be postfixed - concat_onto(&cur_machine, &new_machine); - break; - - default: - // build a new literal character recognizer - build_char(&new_machine, *expr); - - // apply any postfix expression to the character - goto apply_postfix; - } - break; - - case '.': - /* - * wildcard character - build a single character recognizer - * for the special wildcard symbol, then go check it for a - * postfix operator - */ - build_char(&new_machine, RE_WILDCARD); - goto apply_postfix; - break; - - case '[': { - // range expression - int is_exclusive = false; - unsigned char set[32]; - - // clear out the set of characters in the range - memset(set, 0, sizeof(set)); - - // first, skip the open bracket - ++expr; - --exprlen; - - // check to see if starts with the exclusion character - if (exprlen != 0 && *expr == '^') { - // skip the exclusion specifier - ++expr; - --exprlen; - - // note it - is_exclusive = true; - } - - // if the first character is a ']', include it in the range - if (exprlen != 0 && *expr == ']') { - re_set_bit(set, (int)']'); - ++expr; - --exprlen; - } - - // if the next character is a '-', include it in the range - if (exprlen != 0 && *expr == '-') { - re_set_bit(set, (int)'-'); - ++expr; - --exprlen; - } - - // scan the character set - while (exprlen != 0 && *expr != ']') { - int ch; - - // note this character - ch = (int)(unsigned char)*expr; - - // set it - re_set_bit(set, ch); - - // skip this character of the expression - ++expr; - --exprlen; - - // check for a range - if (exprlen != 0 && *expr == '-') { - int ch2; - - // skip the '-' - ++expr; - --exprlen; - if (exprlen != 0) { - // get the other end of the range - ch2 = (int)(unsigned char)*expr; - - // skip the second character - ++expr; - --exprlen; - - // if the range is reversed, swap it - if (ch > ch2) - SWAP(ch, ch2); - - // fill in the range - for ( ; ch <= ch2 ; ++ch) - re_set_bit(set, ch); - } - } - } - - // create a character range machine - build_char_range(&new_machine, set, is_exclusive); - - // apply any postfix operator - goto apply_postfix; - break; - } - - default: - normal_char: - /* - * it's an ordinary character - build a single character - * recognizer machine, and then concatenate it onto any - * existing machine - */ - build_char(&new_machine, *expr); - - apply_postfix: - /* - * Check for a postfix operator, and apply it to the machine - * in 'new_machine' if present. In any case, concatenate - * the 'new_machine' (modified by a postix operator or not) - * to the current machien. - */ - if (exprlen > 1) { - switch(*(expr+1)) { - case '*': - case '+': - case '?': - /* - * We have a postfix closure operator. Build a new - * closure machine out of 'new_machine'. - */ - { - re_machine closure_machine; - - // move onto the closure operator - ++expr; - --exprlen; - - // build the closure machine - build_closure(&closure_machine, &new_machine, *expr); - - // replace the original machine with the closure - new_machine = closure_machine; - - /* - * skip any redundant closure symbols, keeping - * only the first one we saw - */ - while (exprlen > 1 && (*(expr+1) == '?' - || *(expr+1) == '+' - || *(expr+1) == '*')) { - ++expr; - --exprlen; - } - } - break; - - default: - /* no postfix operator */ - break; - } - } - - /* - * Concatenate the new machine onto the current machine - * under construction. - */ - concat_onto(&cur_machine, &new_machine); - break; - } - } - - // complete any pending alternation - alternate_onto(&alter_machine, &cur_machine); - - // store the resulting machine in the caller's machine descriptor - *result_machine = alter_machine; - - // no errors encountered - return RE_STATUS_SUCCESS; + ((((unsigned char *)(set))[(bit) >> 3] & (1 << ((bit) & 7))) != 0) + +/* ------------------------------------------------------------------------ */ +/* + * Compile an expression + */ +static re_status_t re_compile(re_context *ctx, + const char *expr, size_t exprlen, + re_machine *result_machine) +{ + re_machine cur_machine; + re_machine alter_machine; + re_machine new_machine; + size_t group_stack_level; + struct + { + re_machine old_cur; + re_machine old_alter; + int group_id; + } group_stack[50]; + + /* reset everything */ + re_reset(ctx); + + /* start out with no current machine and no alternate machine */ + re_build_null_machine(ctx, &cur_machine); + re_build_null_machine(ctx, &alter_machine); + + /* nothing on the stack yet */ + group_stack_level = 0; + + /* loop until we run out of expression to parse */ + for ( ; exprlen != 0 ; ++expr, --exprlen) + { + switch(*expr) + { + case '^': + /* + * beginning of line - if we're not at the beginning of the + * current expression (i.e., we already have some + * concatentations accumulated), treat it as an ordinary + * character + */ + if (!re_is_machine_null(ctx, &cur_machine)) + goto normal_char; + + /* build a new start-of-text recognizer */ + re_build_char(ctx, &new_machine, RE_TEXT_BEGIN); + + /* + * concatenate it onto the string - note that this can't + * have any postfix operators + */ + re_concat_onto(ctx, &cur_machine, &new_machine); + break; + + case '$': + /* + * End of line specifier - if there's anything left after + * the '$' other than a close parens or alternation + * specifier, great it as a normal character + */ + if (exprlen > 1 + && (*(expr+1) != ')' && *(expr+1) != '|')) + goto normal_char; + + /* build a new end-of-text recognizer */ + re_build_char(ctx, &new_machine, RE_TEXT_END); + + /* + * concatenate it onto the string - note that this can't + * have any postfix operators + */ + re_concat_onto(ctx, &cur_machine, &new_machine); + break; + + case '(': + /* + * Add a nesting level. Push the current machine and + * alternate machines onto the group stack, and clear + * everything out for the new group. + */ + if (group_stack_level + > sizeof(group_stack)/sizeof(group_stack[0])) + { + /* we cannot proceed - return an error */ + return RE_STATUS_GROUP_NESTING_TOO_DEEP; + } + + /* save the current state on the stack */ + group_stack[group_stack_level].old_cur = cur_machine; + group_stack[group_stack_level].old_alter = alter_machine; + + /* + * Assign the group a group ID - groups are numbered in + * order of their opening (left) parentheses, so we want to + * assign a group number now. We won't actually need to + * know the group number until we get to the matching close + * paren, but we need to assign it now, so store it in the + * group stack. + */ + group_stack[group_stack_level].group_id = ctx->cur_group; + + /* consume the group number */ + ctx->cur_group++; + + /* push the level */ + ++group_stack_level; + + /* start the new group with empty machines */ + re_build_null_machine(ctx, &cur_machine); + re_build_null_machine(ctx, &alter_machine); + break; + + case ')': + /* if there's nothing on the stack, ignore this */ + if (group_stack_level == 0) + break; + + /* take a level off the stack */ + --group_stack_level; + + /* + * Remove a nesting level. If we have a pending alternate + * expression, build the alternation expression. This will + * leave the entire group expression in alter_machine, + * regardless of whether an alternation was in progress or + * not. + */ + re_alternate_onto(ctx, &alter_machine, &cur_machine); + + /* + * Create a group machine that encloses the group and marks + * it with a group number. We assigned the group number + * when we parsed the open paren, so read that group number + * from the stack. + * + * Note that this will leave 'new_machine' with the entire + * group machine. + */ + re_build_group(ctx, &new_machine, &alter_machine, + group_stack[group_stack_level].group_id); + + /* + * Pop the stack - restore the alternation and current + * machines that were in progress before the group started. + */ + cur_machine = group_stack[group_stack_level].old_cur; + alter_machine = group_stack[group_stack_level].old_alter; + + /* + * Check the group expression (in new_machine) for postfix + * expressions + */ + goto apply_postfix; + + case '|': + /* + * Start a new alternation. This ends the current + * alternation; if we have a previous pending alternate, + * build an alternation machine out of the previous + * alternate and the current machine and move that to the + * alternate; otherwise, simply move the current machine to + * the pending alternate. + */ + re_alternate_onto(ctx, &alter_machine, &cur_machine); + + /* + * the alternation starts out with a blank slate, so null + * out the current machine + */ + re_build_null_machine(ctx, &cur_machine); + break; + + case '%': + /* + * quoted character - skip the quote mark and see what we + * have + */ + ++expr; + --exprlen; + + /* check to see if we're at the end of the expression */ + if (exprlen == 0) + { + /* + * end of the string - ignore it, but undo the extra + * increment of the expression index so that we exit the + * enclosing loop properly + */ + --expr; + ++exprlen; + break; + } + + /* see what we have */ + switch(*expr) + { + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + /* group match - build a new literal group recognizer */ + re_build_group_matcher(ctx, &new_machine, (int)(*expr - '1')); + + /* apply any postfix expression to the group recognizer */ + goto apply_postfix; + + case '<': + /* build a beginning-of-word recognizer */ + re_build_char(ctx, &new_machine, RE_WORD_BEGIN); + + /* it can't be postfixed - just concatenate it */ + re_concat_onto(ctx, &cur_machine, &new_machine); + break; + + case '>': + /* build an end-of-word recognizer */ + re_build_char(ctx, &new_machine, RE_WORD_END); + + /* it can't be postfixed - just concatenate it */ + re_concat_onto(ctx, &cur_machine, &new_machine); + break; + + case 'w': + /* word character */ + re_build_char(ctx, &new_machine, RE_WORD_CHAR); + goto apply_postfix; + + case 'W': + /* non-word character */ + re_build_char(ctx, &new_machine, RE_NON_WORD_CHAR); + goto apply_postfix; + + case 'b': + /* word boundary */ + re_build_char(ctx, &new_machine, RE_WORD_BOUNDARY); + + /* it can't be postfixed */ + re_concat_onto(ctx, &cur_machine, &new_machine); + break; + + case 'B': + /* not a word boundary */ + re_build_char(ctx, &new_machine, RE_NON_WORD_BOUNDARY); + + /* it can't be postfixed */ + re_concat_onto(ctx, &cur_machine, &new_machine); + break; + + default: + /* build a new literal character recognizer */ + re_build_char(ctx, &new_machine, *expr); + + /* apply any postfix expression to the character */ + goto apply_postfix; + } + break; + + case '.': + /* + * wildcard character - build a single character recognizer + * for the special wildcard symbol, then go check it for a + * postfix operator + */ + re_build_char(ctx, &new_machine, RE_WILDCARD); + goto apply_postfix; + break; + + case '[': + /* range expression */ + { + int is_exclusive = FALSE; + unsigned char set[32]; + + /* clear out the set of characters in the range */ + memset(set, 0, sizeof(set)); + + /* first, skip the open bracket */ + ++expr; + --exprlen; + + /* check to see if starts with the exclusion character */ + if (exprlen != 0 && *expr == '^') + { + /* skip the exclusion specifier */ + ++expr; + --exprlen; + + /* note it */ + is_exclusive = TRUE; + } + + /* + * if the first character is a ']', include it in the + * range + */ + if (exprlen != 0 && *expr == ']') + { + re_set_bit(set, (int)']'); + ++expr; + --exprlen; + } + + /* + * if the next character is a '-', include it in the + * range + */ + if (exprlen != 0 && *expr == '-') + { + re_set_bit(set, (int)'-'); + ++expr; + --exprlen; + } + + /* scan the character set */ + while (exprlen != 0 && *expr != ']') + { + int ch; + + /* note this character */ + ch = (int)(unsigned char)*expr; + + /* set it */ + re_set_bit(set, ch); + + /* skip this character of the expression */ + ++expr; + --exprlen; + + /* check for a range */ + if (exprlen != 0 && *expr == '-') + { + int ch2; + + /* skip the '-' */ + ++expr; + --exprlen; + if (exprlen != 0) + { + /* get the other end of the range */ + ch2 = (int)(unsigned char)*expr; + + /* skip the second character */ + ++expr; + --exprlen; + + /* if the range is reversed, swap it */ + if (ch > ch2) + { + int tmp = ch; + ch = ch2; + ch2 = tmp; + } + + /* fill in the range */ + for ( ; ch <= ch2 ; ++ch) + re_set_bit(set, ch); + } + } + } + + /* create a character range machine */ + re_build_char_range(ctx, &new_machine, set, is_exclusive); + + /* apply any postfix operator */ + goto apply_postfix; + } + break; + + default: + normal_char: + /* + * it's an ordinary character - build a single character + * recognizer machine, and then concatenate it onto any + * existing machine + */ + re_build_char(ctx, &new_machine, *expr); + + apply_postfix: + /* + * Check for a postfix operator, and apply it to the machine + * in 'new_machine' if present. In any case, concatenate + * the 'new_machine' (modified by a postix operator or not) + * to the current machien. + */ + if (exprlen > 1) + { + switch(*(expr+1)) + { + case '*': + case '+': + case '?': + /* + * We have a postfix closure operator. Build a new + * closure machine out of 'new_machine'. + */ + { + re_machine closure_machine; + + /* move onto the closure operator */ + ++expr; + --exprlen; + + /* build the closure machine */ + re_build_closure(ctx, &closure_machine, + &new_machine, *expr); + + /* replace the original machine with the closure */ + new_machine = closure_machine; + + /* + * skip any redundant closure symbols, keeping + * only the first one we saw + */ + while (exprlen > 1 && (*(expr+1) == '?' + || *(expr+1) == '+' + || *(expr+1) == '*')) + { + ++expr; + --exprlen; + } + } + break; + + default: + /* no postfix operator */ + break; + } + } + + /* + * Concatenate the new machine onto the current machine + * under construction. + */ + re_concat_onto(ctx, &cur_machine, &new_machine); + break; + } + } + + /* complete any pending alternation */ + re_alternate_onto(ctx, &alter_machine, &cur_machine); + + /* store the resulting machine in the caller's machine descriptor */ + *result_machine = alter_machine; + + /* no errors encountered */ + return RE_STATUS_SUCCESS; } -void re_context::note_group(re_group_register *regs, re_state_id id, const char *p) { - int group_index; - - /* - * Check to see if this is a valid state and it's a group marker - - * if not, there's nothing to do - */ - if (id == RE_STATE_INVALID - || !(_tuple_arr[id].flags - & (RE_STATE_GROUP_BEGIN | RE_STATE_GROUP_END)) - || (group_index = (int)_tuple_arr[id].ch) >= RE_GROUP_REG_CNT) - return; - - // It's a valid group marker - note the appropriate register value - if ((_tuple_arr[id].flags & RE_STATE_GROUP_BEGIN) != 0) - regs[group_index].start_ofs = p; - else - regs[group_index].end_ofs = p; + +/* ------------------------------------------------------------------------ */ +/* + * Pattern recognizer + */ + +/* + * Note a group position if appropriate + */ +static void re_note_group(re_context *ctx, re_group_register *regs, + re_state_id id, const char *p) +{ + int group_index; + + /* + * Check to see if this is a valid state and it's a group marker - + * if not, there's nothing to do + */ + if (id == RE_STATE_INVALID + || !(ctx->tuple_arr[id].flags + & (RE_STATE_GROUP_BEGIN | RE_STATE_GROUP_END)) + || (group_index = (int)ctx->tuple_arr[id].ch) >= RE_GROUP_REG_CNT) + return; + + /* + * It's a valid group marker - note the appropriate register value + */ + if ((ctx->tuple_arr[id].flags & RE_STATE_GROUP_BEGIN) != 0) + regs[group_index].start_ofs = p; + else + regs[group_index].end_ofs = p; } -bool re_context::is_word_char(char c) const { - return Common::isAlnum(c); +/* + * Determine if a character is part of a word. We consider letters and + * numbers to be word characters. + */ +static int re_is_word_char(char c) { + return Common::isAlnum((unsigned char)c); } -int re_context::match(const char *entire_str, const char *str, size_t origlen, - const re_machine *machine, re_group_register *regs) { - re_state_id cur_state; - const char *p; - size_t curlen; - - // start at the machine's initial state - cur_state = machine->init; - - // start at the beginning of the string - p = str; - curlen = origlen; - - // note any group involved in the initial state - note_group(regs, cur_state, p); - - /* - * if we're starting in the final state, immediately return success - * with a zero-length match - */ - if (cur_state == machine->final) { - // return success with a zero-length match - return 0; - } - - // run the machine - for (;;) { - re_tuple *tuple; - - // get the tuple for this state - tuple = &_tuple_arr[cur_state]; - - // if this is a group state, adjust the group registers - note_group(regs, cur_state, p); - - // see what kind of state we're in - if (!(tuple->flags & (RE_STATE_GROUP_BEGIN | RE_STATE_GROUP_END)) - && tuple->ch != RE_EPSILON) { - /* - * This is a character or group recognizer state. If we - * match the character or group, continue on to the next - * state; otherwise, return failure. - */ - switch(tuple->ch) { - case RE_GROUP_MATCH_0: - case RE_GROUP_MATCH_0 + 1: - case RE_GROUP_MATCH_0 + 2: - case RE_GROUP_MATCH_0 + 3: - case RE_GROUP_MATCH_0 + 4: - case RE_GROUP_MATCH_0 + 5: - case RE_GROUP_MATCH_0 + 6: - case RE_GROUP_MATCH_0 + 7: - case RE_GROUP_MATCH_0 + 8: - case RE_GROUP_MATCH_0 + 9: { - int group_num; - re_group_register *group_reg; - size_t reg_len; - - // it's a group - get the group number - group_num = tuple->ch - RE_GROUP_MATCH_0; - group_reg = ®s[group_num]; - - /* - * if this register isn't defined, there's nothing - * to match, so fail - */ - if (group_reg->start_ofs == 0 || group_reg->end_ofs == 0) - return -1; - - // calculate the length of the register value - reg_len = group_reg->end_ofs - group_reg->start_ofs; - - // if we don't have enough left to match, it fails - if (curlen < reg_len) - return -1; - - // if the string doesn't match exactly, we fail - if (memcmp(p, group_reg->start_ofs, reg_len) != 0) - return -1; - - /* - * It matches exactly - skip the entire length of - * the register in the source string - */ - p += reg_len; - curlen -= reg_len; - break; - } - - case RE_TEXT_BEGIN: - /* - * Match only the exact beginning of the string - if - * we're anywhere else, this isn't a match. If this - * succeeds, we don't skip any characters. - */ - if (p != entire_str) - return -1; - break; - - case RE_TEXT_END: - /* - * Match only the exact end of the string - if we're - * anywhere else, this isn't a match. Don't skip any - * characters on success. - */ - if (curlen != 0) - return -1; - break; - - case RE_WORD_BEGIN: - /* - * if the previous character is a word character, we're - * not at the beginning of a word - */ - if (p != entire_str && is_word_char(*(p - 1))) - return -1; - - /* - * if we're at the end of the string, or the current - * character isn't the start of a word, we're not at the - * beginning of a word - */ - if (curlen == 0 || !is_word_char(*p)) - return -1; - break; - - case RE_WORD_END: - /* - * if the current character is a word character, we're not - * at the end of a word - */ - if (curlen != 0 && is_word_char(*p)) - return -1; - - /* - * if we're at the beginning of the string, or the - * previous character is not a word character, we're not - * at the end of a word - */ - if (p == entire_str || !is_word_char(*(p - 1))) - return -1; - break; - - case RE_WORD_CHAR: - /* if it's not a word character, it's a failure */ - if (curlen == 0 || !is_word_char(*p)) - return -1; - - /* skip this character of input */ - ++p; - --curlen; - break; - - case RE_NON_WORD_CHAR: - /* if it's a word character, it's a failure */ - if (curlen == 0 || is_word_char(*p)) - return -1; - - /* skip the input */ - ++p; - --curlen; - break; - - case RE_WORD_BOUNDARY: - case RE_NON_WORD_BOUNDARY: - { - int prev_is_word; - int next_is_word; - int boundary; - - /* - * Determine if the previous character is a word - * character -- if we're at the beginning of the - * string, it's obviously not, otherwise check its - * classification - */ - prev_is_word = (p != entire_str - && is_word_char(*(p - 1))); - - /* make the same check for the current character */ - next_is_word = (curlen != 0 - && is_word_char(*p)); - - /* - * Determine if this is a boundary - it is if the - * two states are different - */ - boundary = ((prev_is_word != 0) ^ (next_is_word != 0)); - - /* - * make sure it matches what was desired, and return - * failure if not - */ - if ((tuple->ch == RE_WORD_BOUNDARY && !boundary) - || (tuple->ch == RE_NON_WORD_BOUNDARY && boundary)) - return -1; - } - break; - - case RE_WILDCARD: - // make sure we have a character to match - if (curlen == 0) - return -1; - - // skip this character - ++p; - --curlen; - break; - - case RE_RANGE: - case RE_RANGE_EXCL: { - int match_val; - - // make sure we have a character to match - if (curlen == 0) - return -1; - - // see if we match - match_val = re_is_bit_set(tuple->char_range, (int)(unsigned char)*p); - - // make sure we got what we wanted - if ((tuple->ch == RE_RANGE && !match_val) - || (tuple->ch == RE_RANGE_EXCL && match_val)) - return -1; - - // skip this character of the input - ++p; - --curlen; - break; - } - - default: - // make sure we have an exact match - if (curlen == 0 || tuple->ch != *p) - return -1; - - // skip this character of the input - ++p; - --curlen; - break; - } - - /* - * if we got this far, we were successful - move on to the - * next state - */ - cur_state = tuple->next_state_1; - } else if (tuple->next_state_2 == RE_STATE_INVALID) { - /* - * We have only one transition, so this state is entirely - * deterministic. Simply move on to the next state. - */ - cur_state = tuple->next_state_1; - } else { - re_machine sub_machine; - re_group_register regs1[RE_GROUP_REG_CNT]; - re_group_register regs2[RE_GROUP_REG_CNT]; - int ret1; - int ret2; - - /* - * This state has two possible transitions, and we don't - * know which one to take. So, try both, see which one - * works better, and return the result. Try the first - * transition first. Note that each separate attempt must - * use a separate copy of the registers. - */ - memcpy(regs1, regs, sizeof(regs1)); - sub_machine.init = tuple->next_state_1; - sub_machine.final = machine->final; - ret1 = match(entire_str, p, curlen, &sub_machine, regs1); - - /* - * Now try the second transition - */ - memcpy(regs2, regs, sizeof(regs2)); - sub_machine.init = tuple->next_state_2; - sub_machine.final = machine->final; - ret2 = match(entire_str, p, curlen, &sub_machine, regs2); - - /* - * If they both failed, the whole thing failed. Otherwise, - * return the longer of the two, plus the length we - * ourselves matched previously. Note that we return the - * register set from the winning match. - */ - if (ret1 < 0 && ret2 < 0) { - // they both failed - return -1; - } else if (ret1 > ret2) { - // use the first register set and result length - memcpy(regs, regs1, sizeof(regs1)); - return ret1 + (p - str); - } else { - // use the second register set and result length - memcpy(regs, regs2, sizeof(regs2)); - return ret2 + (p - str); - } - } - - // If we're in the final state, return success - if (cur_state == machine->final) { - // finish off any group involved in the final state - note_group(regs, cur_state, p); - - // return the length we matched - return p - str; - } - } +/* + * Match a string to a compiled expression. Returns the length of the + * match if successful, or -1 if no match was found. + */ +static int re_match(re_context *ctx, const char *entire_str, + const char *str, size_t origlen, + const re_machine *machine, re_group_register *regs) +{ + re_state_id cur_state; + const char *p; + size_t curlen; + + /* start at the machine's initial state */ + cur_state = machine->init; + + /* start at the beginning of the string */ + p = str; + curlen = origlen; + + /* note any group involved in the initial state */ + re_note_group(ctx, regs, cur_state, p); + + /* + * if we're starting in the final state, immediately return success + * with a zero-length match + */ + if (cur_state == machine->final) + { + /* return success with a zero-length match */ + return 0; + } + + /* run the machine */ + for (;;) + { + re_tuple *tuple; + + /* get the tuple for this state */ + tuple = &ctx->tuple_arr[cur_state]; + + /* if this is a group state, adjust the group registers */ + re_note_group(ctx, regs, cur_state, p); + + /* see what kind of state we're in */ + if (!(tuple->flags & (RE_STATE_GROUP_BEGIN | RE_STATE_GROUP_END)) + && tuple->ch != RE_EPSILON) + { + /* + * This is a character or group recognizer state. If we + * match the character or group, continue on to the next + * state; otherwise, return failure. + */ + switch(tuple->ch) + { + case RE_GROUP_MATCH_0: + case RE_GROUP_MATCH_0 + 1: + case RE_GROUP_MATCH_0 + 2: + case RE_GROUP_MATCH_0 + 3: + case RE_GROUP_MATCH_0 + 4: + case RE_GROUP_MATCH_0 + 5: + case RE_GROUP_MATCH_0 + 6: + case RE_GROUP_MATCH_0 + 7: + case RE_GROUP_MATCH_0 + 8: + case RE_GROUP_MATCH_0 + 9: + { + int group_num; + re_group_register *group_reg; + size_t reg_len; + + /* it's a group - get the group number */ + group_num = tuple->ch - RE_GROUP_MATCH_0; + group_reg = ®s[group_num]; + + /* + * if this register isn't defined, there's nothing + * to match, so fail + */ + if (group_reg->start_ofs == 0 || group_reg->end_ofs == 0) + return -1; + + /* calculate the length of the register value */ + reg_len = group_reg->end_ofs - group_reg->start_ofs; + + /* if we don't have enough left to match, it fails */ + if (curlen < reg_len) + return -1; + + /* if the string doesn't match exactly, we fail */ + if (memcmp(p, group_reg->start_ofs, reg_len) != 0) + return -1; + + /* + * It matches exactly - skip the entire length of + * the register in the source string + */ + p += reg_len; + curlen -= reg_len; + } + break; + + case RE_TEXT_BEGIN: + /* + * Match only the exact beginning of the string - if + * we're anywhere else, this isn't a match. If this + * succeeds, we don't skip any characters. + */ + if (p != entire_str) + return -1; + break; + + case RE_TEXT_END: + /* + * Match only the exact end of the string - if we're + * anywhere else, this isn't a match. Don't skip any + * characters on success. + */ + if (curlen != 0) + return -1; + break; + + case RE_WORD_BEGIN: + /* + * if the previous character is a word character, we're + * not at the beginning of a word + */ + if (p != entire_str && re_is_word_char(*(p-1))) + return -1; + + /* + * if we're at the end of the string, or the current + * character isn't the start of a word, we're not at the + * beginning of a word + */ + if (curlen == 0 || !re_is_word_char(*p)) + return -1; + break; + + case RE_WORD_END: + /* + * if the current character is a word character, we're not + * at the end of a word + */ + if (curlen != 0 && re_is_word_char(*p)) + return -1; + + /* + * if we're at the beginning of the string, or the + * previous character is not a word character, we're not + * at the end of a word + */ + if (p == entire_str || !re_is_word_char(*(p-1))) + return -1; + break; + + case RE_WORD_CHAR: + /* if it's not a word character, it's a failure */ + if (curlen == 0 || !re_is_word_char(*p)) + return -1; + + /* skip this character of input */ + ++p; + --curlen; + break; + + case RE_NON_WORD_CHAR: + /* if it's a word character, it's a failure */ + if (curlen == 0 || re_is_word_char(*p)) + return -1; + + /* skip the input */ + ++p; + --curlen; + break; + + case RE_WORD_BOUNDARY: + case RE_NON_WORD_BOUNDARY: + { + int prev_is_word; + int next_is_word; + int boundary; + + /* + * Determine if the previous character is a word + * character -- if we're at the beginning of the + * string, it's obviously not, otherwise check its + * classification + */ + prev_is_word = (p != entire_str + && re_is_word_char(*(p-1))); + + /* make the same check for the current character */ + next_is_word = (curlen != 0 + && re_is_word_char(*p)); + + /* + * Determine if this is a boundary - it is if the + * two states are different + */ + boundary = ((prev_is_word != 0) ^ (next_is_word != 0)); + + /* + * make sure it matches what was desired, and return + * failure if not + */ + if ((tuple->ch == RE_WORD_BOUNDARY && !boundary) + || (tuple->ch == RE_NON_WORD_BOUNDARY && boundary)) + return -1; + } + break; + + case RE_WILDCARD: + /* make sure we have a character to match */ + if (curlen == 0) + return -1; + + /* skip this character */ + ++p; + --curlen; + break; + + case RE_RANGE: + case RE_RANGE_EXCL: + { + int match; + + /* make sure we have a character to match */ + if (curlen == 0) + return -1; + + /* see if we match */ + match = re_is_bit_set(tuple->char_range, + (int)(unsigned char)*p); + + /* make sure we got what we wanted */ + if ((tuple->ch == RE_RANGE && !match) + || (tuple->ch == RE_RANGE_EXCL && match)) + return -1; + + /* skip this character of the input */ + ++p; + --curlen; + } + break; + + default: + /* make sure we have an exact match */ + if (curlen == 0 || tuple->ch != *p) + return -1; + + /* skip this character of the input */ + ++p; + --curlen; + break; + } + + /* + * if we got this far, we were successful - move on to the + * next state + */ + cur_state = tuple->next_state_1; + } + else if (tuple->next_state_2 == RE_STATE_INVALID) + { + /* + * We have only one transition, so this state is entirely + * deterministic. Simply move on to the next state. + */ + cur_state = tuple->next_state_1; + } + else + { + re_machine sub_machine; + re_group_register regs1[RE_GROUP_REG_CNT]; + re_group_register regs2[RE_GROUP_REG_CNT]; + int ret1; + int ret2; + + /* + * This state has two possible transitions, and we don't + * know which one to take. So, try both, see which one + * works better, and return the result. Try the first + * transition first. Note that each separate attempt must + * use a separate copy of the registers. + */ + memcpy(regs1, regs, sizeof(regs1)); + sub_machine.init = tuple->next_state_1; + sub_machine.final = machine->final; + ret1 = re_match(ctx, entire_str, p, curlen, &sub_machine, regs1); + + /* + * Now try the second transition + */ + memcpy(regs2, regs, sizeof(regs2)); + sub_machine.init = tuple->next_state_2; + sub_machine.final = machine->final; + ret2 = re_match(ctx, entire_str, p, curlen, &sub_machine, regs2); + + /* + * If they both failed, the whole thing failed. Otherwise, + * return the longer of the two, plus the length we + * ourselves matched previously. Note that we return the + * register set from the winning match. + */ + if (ret1 < 0 && ret2 < 0) + { + /* they both failed */ + return -1; + } + else if (ret1 > ret2) + { + /* use the first register set and result length */ + memcpy(regs, regs1, sizeof(regs1)); + return ret1 + (p - str); + } + else + { + /* use the second register set and result length */ + memcpy(regs, regs2, sizeof(regs2)); + return ret2 + (p - str); + } + } + + /* + * If we're in the final state, return success + */ + if (cur_state == machine->final) + { + /* finish off any group involved in the final state */ + re_note_group(ctx, regs, cur_state, p); + + /* return the length we matched */ + return p - str; + } + } } -int re_context::search(const char *str, size_t len, const re_machine *machine, - re_group_register *regs, int *result_len) { - int ofs; - - /* - * Starting at the first character in the string, search for the - * pattern at each subsequent character until we either find the - * pattern or run out of string to test. - */ - for (ofs = 0 ; ofs < (int)len ; ++ofs) { - int matchlen; - - // check for a match - matchlen = match(str, str + ofs, len - ofs, machine, regs); - if (matchlen >= 0) { - // we found a match here - return the length and offset - *result_len = matchlen; - return ofs; - } - } - - // we didn't find a match - return -1; +/* ------------------------------------------------------------------------ */ +/* + * Search for a regular expression within a string. Returns -1 if the + * string cannot be found, otherwise returns the offset from the start + * of the string to be searched of the start of the first match for the + * pattern. + */ +static int re_search(re_context *ctx, const char *str, size_t len, + const re_machine *machine, re_group_register *regs, + int *result_len) +{ + int ofs; + + /* + * Starting at the first character in the string, search for the + * pattern at each subsequent character until we either find the + * pattern or run out of string to test. + */ + for (ofs = 0 ; ofs < (int)len ; ++ofs) + { + int matchlen; + + /* check for a match */ + matchlen = re_match(ctx, str, str + ofs, len - ofs, + machine, regs); + if (matchlen >= 0) + { + /* we found a match here - return the length and offset */ + *result_len = matchlen; + return ofs; + } + } + + /* we didn't find a match */ + return -1; } -void re_context::save_search_str(const char *str, size_t len) { - // if the string is empty, this is easy - if (len == 0) { - // nothing to store - just save the length and return - _curlen = 0; - return; - } - - // if the current buffer isn't big enough, allocate a new one - if (_strbuf == 0 || _strbufsiz < len) { - /* - * free any previous buffer - its contents are no longer - * important, since we're about to overwrite it with a new - * string - */ - if (_strbuf != 0) - mchfre(_strbuf); - - /* - * allocate a new buffer; round up to the next 256-byte - * increment to make sure we're not constantly reallocating to - * random sizes - */ - _strbufsiz = ((len + 255) & ~255); - - // allocate it - _strbuf = (char *)mchalo(_errctx, _strbufsiz, "regex str"); - } - - // copy the string - memcpy(_strbuf, str, len); - - // save the length - _curlen = len; +/* ------------------------------------------------------------------------ */ +/* + * Make a copy of a search string in our private buffer. + */ +static void re_save_search_str(re_context *ctx, const char *str, size_t len) +{ + /* if the string is empty, this is easy */ + if (len == 0) + { + /* nothing to store - just save the length and return */ + ctx->curlen = 0; + return; + } + + /* if the current buffer isn't big enough, allocate a new one */ + if (ctx->strbuf == 0 || ctx->strbufsiz < len) + { + /* + * free any previous buffer - its contents are no longer + * important, since we're about to overwrite it with a new + * string + */ + if (ctx->strbuf != 0) + mchfre(ctx->strbuf); + + /* + * allocate a new buffer; round up to the next 256-byte + * increment to make sure we're not constantly reallocating to + * random sizes + */ + ctx->strbufsiz = ((len + 255) & ~255); + + /* allocate it */ + ctx->strbuf = (char *)mchalo(ctx->errctx, ctx->strbufsiz, + "regex str"); + } + + /* copy the string */ + memcpy(ctx->strbuf, str, len); + + /* save the length */ + ctx->curlen = len; } -int re_context::compile_and_search(const char *pattern, size_t patlen, - const char *searchstr, size_t searchlen, int *result_len) { - re_machine machine; - - // compile the expression - return failure if we get an error - if (compile(pattern, patlen, &machine) != RE_STATUS_SUCCESS) - return -1; - - // save the search string in our internal buffer - save_search_str(searchstr, searchlen); - - // clear the group registers - memset(_regs, 0, sizeof(_regs)); - - /* - * search for the pattern in our copy of the string - use the copy - * so that the group registers stay valid even if the caller - * deallocates the original string after we return - */ - return search(_strbuf, _curlen, &machine, _regs, result_len); +/* ------------------------------------------------------------------------ */ +/* + * Compile an expression and search for a match within the given string. + * Returns the offset of the match, or -1 if no match was found. + */ +int re_compile_and_search(re_context *ctx, + const char *pattern, size_t patlen, + const char *searchstr, size_t searchlen, + int *result_len) +{ + re_machine machine; + + /* compile the expression - return failure if we get an error */ + if (re_compile(ctx, pattern, patlen, &machine) != RE_STATUS_SUCCESS) + return -1; + + /* save the search string in our internal buffer */ + re_save_search_str(ctx, searchstr, searchlen); + + /* clear the group registers */ + memset(ctx->regs, 0, sizeof(ctx->regs)); + + /* + * search for the pattern in our copy of the string - use the copy + * so that the group registers stay valid even if the caller + * deallocates the original string after we return + */ + return re_search(ctx, ctx->strbuf, ctx->curlen, &machine, + ctx->regs, result_len); } -int re_context::compile_and_match(const char *pattern, size_t patlen, - const char *searchstr, size_t searchlen) { - re_machine machine; +/* ------------------------------------------------------------------------ */ +/* + * Compile an expression and check for a match. Returns the length of + * the match if we found a match, -1 if we found no match. This is not + * a search function; we merely match the leading substring of the given + * string to the given pattern. + */ +int re_compile_and_match(re_context *ctx, + const char *pattern, size_t patlen, + const char *searchstr, size_t searchlen) +{ + re_machine machine; - // compile the expression - return failure if we get an error - if (compile(pattern, patlen, &machine) != RE_STATUS_SUCCESS) - return 0; + /* compile the expression - return failure if we get an error */ + if (re_compile(ctx, pattern, patlen, &machine) != RE_STATUS_SUCCESS) + return FALSE; - // save the search string in our internal buffer - save_search_str(searchstr, searchlen); + /* save the search string in our internal buffer */ + re_save_search_str(ctx, searchstr, searchlen); - // clear the group registers - memset(_regs, 0, sizeof(_regs)); + /* clear the group registers */ + memset(ctx->regs, 0, sizeof(ctx->regs)); - // match the string - return match(_strbuf, _strbuf, _curlen, &machine, _regs); + /* match the string */ + return re_match(ctx, ctx->strbuf, ctx->strbuf, ctx->curlen, + &machine, ctx->regs); } + } // End of namespace TADS2 } // End of namespace TADS } // End of namespace Glk diff --git a/engines/glk/tads/tads2/regex.h b/engines/glk/tads/tads2/regex.h index cd975e066b..48c2dd0ad4 100644 --- a/engines/glk/tads/tads2/regex.h +++ b/engines/glk/tads/tads2/regex.h @@ -24,289 +24,165 @@ #define GLK_TADS_TADS2_REGEX #include "common/array.h" -#include "glk/tads/tads2/ler.h" +#include "glk/tads/tads2/error_handling.h" namespace Glk { namespace TADS { namespace TADS2 { -/** - * state ID - */ + +/* state ID */ typedef int re_state_id; -/** - * invalid state ID - used to mark null machines - */ +/* invalid state ID - used to mark null machines */ #define RE_STATE_INVALID ((re_state_id)-1) -/** - * first valid state ID - */ +/* first valid state ID */ #define RE_STATE_FIRST_VALID ((re_state_id)0) -/** +/* ------------------------------------------------------------------------ */ +/* * Group register structure. Each register keeps track of the starting - * and ending offset of the group's text. + * and ending offset of the group's text. */ -struct re_group_register { - const char *start_ofs; - const char *end_ofs; -}; +typedef struct +{ + const char *start_ofs; + const char *end_ofs; +} re_group_register; -/** - * number of group registers we keep - */ +/* number of group registers we keep */ #define RE_GROUP_REG_CNT 10 -/** - * Denormalized state transition tuple. Each tuple represents the - * complete set of transitions out of a particular state. A particular - * state can have one character transition, or two epsilon transitions. - * Note that we don't need to store the state ID in the tuple, because - * the state ID is the index of the tuple in an array of state tuples. + +/* ------------------------------------------------------------------------ */ +/* + * Denormalized state transition tuple. Each tuple represents the + * complete set of transitions out of a particular state. A particular + * state can have one character transition, or two epsilon transitions. + * Note that we don't need to store the state ID in the tuple, because + * the state ID is the index of the tuple in an array of state tuples. */ -struct re_tuple { - // the character we must match to transition to the target state - char ch; +typedef struct +{ + /* the character we must match to transition to the target state */ + char ch; - // the target states - re_state_id next_state_1; - re_state_id next_state_2; + /* the target states */ + re_state_id next_state_1; + re_state_id next_state_2; - // character range match table, if used - unsigned char *char_range; + /* character range match table, if used */ + unsigned char *char_range; - // flags - byte flags; -}; + /* flags */ + unsigned char flags; +} re_tuple; -/** - * Tuple flags +/* + * Tuple flags */ -enum { - // this state is the start of a group - the 'ch' value is the group ID - RE_STATE_GROUP_BEGIN = 0x02, - // this state is the end of a group - 'ch' is the group ID */ - RE_STATE_GROUP_END = 0x04 -}; +/* this state is the start of a group - the 'ch' value is the group ID */ +#define RE_STATE_GROUP_BEGIN 0x02 + +/* this state is the end of a group - 'ch' is the group ID */ +#define RE_STATE_GROUP_END 0x04 + + +/* ------------------------------------------------------------------------ */ +/* + * Regular expression compilation context structure. This tracks the + * state of the compilation and stores the resources associated with the + * compiled expression. + */ +typedef struct +{ + /* error context */ + errcxdef *errctx; + + /* next available state ID */ + re_state_id next_state; + + /* + * The array of transition tuples. We'll allocate this array and + * expand it as necessary. + */ + re_tuple *tuple_arr; -/** - * Status codes + /* number of transition tuples allocated in the array */ + int tuples_alloc; + + /* current group ID */ + int cur_group; + + /* group registers */ + re_group_register regs[RE_GROUP_REG_CNT]; + + /* + * Buffer for retaining a copy of the last string we scanned. We + * retain our own copy of each string, and point the group registers + * into this copy rather than the caller's original string -- this + * ensures that the group registers remain valid even after the + * caller has deallocated the original string. + */ + char *strbuf; + + /* length of the string currently in the buffer */ + size_t curlen; + + /* size of the buffer allocated to strbuf */ + size_t strbufsiz; +} re_context; + + +/* ------------------------------------------------------------------------ */ +/* + * Status codes */ -typedef enum { - // success - RE_STATUS_SUCCESS = 0, +typedef enum +{ + /* success */ + RE_STATUS_SUCCESS = 0, - // compilation error - group nesting too deep - RE_STATUS_GROUP_NESTING_TOO_DEEP + /* compilation error - group nesting too deep */ + RE_STATUS_GROUP_NESTING_TOO_DEEP } re_status_t; -/** - * Regular expression compilation. This tracks the state of the compilation and - * stores the resources associated with the compiled expression. +/* ------------------------------------------------------------------------ */ +/* + * Initialize the context. The memory for the context structure itself + * must be allocated and maintained by the caller. + */ +void re_init(re_context *ctx, errcxdef *errctx); + +/* + * Delete the context - frees structures associated with the context. + * Does NOT free the memory used by the context structure itself. + */ +void re_delete(re_context *ctx); + +/* + * Compile an expression and search for a match within the given string. + * Returns the offset of the match, or -1 if no match was found. + */ +int re_compile_and_search(re_context *ctx, + const char *pattern, size_t patlen, + const char *searchstr, size_t searchlen, + int *result_len); + +/* + * Compile an expression and check for a match. Returns the length of + * the match if we found a match, -1 if we found no match. This is not + * a search function; we merely match the leading substring of the given + * string to the given pattern. */ -class re_context { - /** - * A machine description. Machines are fully described by their initial - * and final state ID's. - */ - struct re_machine { - re_state_id init; ///< the machine's initial state - re_state_id final; ///< the machine's final state - - re_machine() : init(0), final(0) {} - - /** - * Build a null machine - */ - void build_null_machine() { - init = final = RE_STATE_INVALID; - } - - /** - * Determine if a machine is null - */ - bool isNull() const { - return (init == RE_STATE_INVALID); - } - }; -private: - /** - * Reset compiler - clears states and tuples - */ - void reset(); - - /** - * Set a transition from a state to a given destination state - */ - void set_trans(re_state_id id, re_state_id dest_id, char ch); - - /** - * Initialize a new machine, giving it an initial and final state - */ - void init_machine(re_machine *machine); - - /** - * Build a character recognizer - */ - void build_char(re_machine *machine, char ch); - - /** - * Build a character range recognizer. 'range' is a 256-bit (32-byte) bit vector. - */ - void build_char_range(re_machine *machine, unsigned char *range, int exclusion); - - /** - * Build a group recognizer. This is almost the same as a character - * recognizer, but matches a previous group rather than a literal character. - */ - void build_group_matcher(re_machine *machine, int group_num); - - /** - * Build a concatenation recognizer - */ - void build_concat(re_machine *new_machine, re_machine *lhs, re_machine *rhs); - - /** - * Build a group machine. sub_machine contains the machine that - * expresses the group's contents; we'll fill in new_machine with a - * newly-created machine that encloses and marks the group. - */ - void build_group(re_machine *new_machine, re_machine *sub_machine, int group_id); - - /** - * Build an alternation recognizer - */ - void build_alter(re_machine *new_machine, re_machine *lhs, re_machine *rhs); - - /** - * Build a closure recognizer - */ - void build_closure(re_machine *new_machine, re_machine *sub, char specifier); - - /** - * Concatenate the second machine onto the first machine, replacing the - * first machine with the resulting machine. If the first machine is a - * null machine (created with re_build_null_machine), we'll simply copy - * the second machine into the first. - */ - void concat_onto(re_machine *dest, re_machine *rhs); - - /** - * Alternate the second machine onto the first machine, replacing the - * first machine with the resulting machine. If the first machine is a - * null machine, this simply replaces the first machine with the second - * machine. If the second machine is null, this simply leaves the first - * machine unchanged. - */ - void alternate_onto(re_machine *dest, re_machine *rhs); - - /** - * Compile an expression - */ - re_status_t compile(const char *expr, size_t exprlen, re_machine *result_machine); - - /** - * Note a group position if appropriate - */ - void note_group(re_group_register *regs, re_state_id id, const char *p); - - /** - * Determine if a character is part of a word. We consider letters and - * numbers to be word characters. - */ - bool is_word_char(char c) const; - - /** - * Match a string to a compiled expression. Returns the length of the - * match if successful, or -1 if no match was found. - */ - int match(const char *entire_str, const char *str, size_t origlen, - const re_machine *machine, re_group_register *regs); - - /** - * Search for a regular expression within a string. Returns -1 if the string - * cannot be found, otherwise returns the offset from the start of the string - * to be searched of the start of the first match for the pattern. - */ - int search(const char *str, size_t len, const re_machine *machine, - re_group_register *regs, int *result_len); - - /** - * Make a copy of a search string in our private buffer. - */ - void save_search_str(const char *str, size_t len); -public: - errcxdef *_errctx; ///< error context - re_state_id _next_state; ///< next available state ID - - /** - * The array of transition tuples. We'll allocate this array and - * expand it as necessary. - */ - Common::Array<re_tuple> _tuple_arr; - - // current group ID - int _cur_group; - - // group registers - re_group_register _regs[RE_GROUP_REG_CNT]; - - /** - * Buffer for retaining a copy of the last string we scanned. We - * retain our own copy of each string, and point the group registers - * into this copy rather than the caller's original string -- this - * ensures that the group registers remain valid even after the - * caller has deallocated the original string. - */ - char *_strbuf; - - /** - * length of the string currently in the buffer - */ - size_t _curlen; - - /** - * size of the buffer allocated to strbuf - */ - size_t _strbufsiz; -public: - /** - * Constructor. The memory for the context structure itself - * must be allocated and maintained by the caller. - */ - re_context(errcxdef *errctx); - - /** - * Destructor - */ - ~re_context(); - - /** - * Allocate a new state ID - */ - re_state_id alloc_state(); - - /** - * Compile an expression and search for a match within the given string. - * Returns the offset of the match, or -1 if no match was found. - */ - int compile_and_search(const char *pattern, size_t patlen, - const char *searchstr, size_t searchlen, int *result_len); - - /** - * Compile an expression and check for a match. Returns the length of the match - * if we found a match, -1 if we found no match. This is not a search function; - * we merely match the leading substring of the given string to the given pattern. - */ - int compile_and_match(const char *pattern, size_t patlen, - const char *searchstr, size_t searchlen); -}; +int re_compile_and_match(re_context *ctx, + const char *pattern, size_t patlen, + const char *searchstr, size_t searchlen); } // End of namespace TADS2 } // End of namespace TADS diff --git a/engines/glk/tads/tads2/run.cpp b/engines/glk/tads/tads2/run.cpp new file mode 100644 index 0000000000..5123d650fe --- /dev/null +++ b/engines/glk/tads/tads2/run.cpp @@ -0,0 +1,52 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software{} you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation{} either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY{} without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program{} if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/tads2/run.h" +#include "glk/tads/tads2/data.h" +#include "glk/tads/tads2/vocabulary.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +int runsdef::runsiz() const { + switch (runstyp) { + case DAT_NUMBER: + return 4; + case DAT_SSTRING: + case DAT_LIST: + return osrp2(runsv.runsvstr); + case DAT_PROPNUM: + case DAT_OBJECT: + case DAT_FNADDR: + return 2; + default: + return 0; + } +} + +/*--------------------------------------------------------------------------*/ + + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/tads2/run.h b/engines/glk/tads/tads2/run.h new file mode 100644 index 0000000000..b6694bf523 --- /dev/null +++ b/engines/glk/tads/tads2/run.h @@ -0,0 +1,393 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* Definitions for code execution + * + * The preprocessor symbol RUNFAST can be defined if run - time checking + * of stack overflow, stack underflow, and other unusual but potentially + * dangerous conditions is to be turned off.This will result in somewhat + * faster run-time performance, but run - time errors could be disastrous. + */ + +#ifndef GLK_TADS_TADS2_RUN +#define GLK_TADS_TADS2_RUN + +#include "common/scummsys.h" +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/debug.h" +#include "glk/tads/tads2/object.h" +#include "glk/tads/tads2/memory_cache.h" +#include "glk/tads/tads2/memory_cache_swap.h" +#include "glk/tads/tads2/opcode_defs.h" +#include "glk/tads/tads2/property.h" +#include "glk/tads/tads2/text_io.h" +#include "glk/tads/tads2/tokenizer.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* Forward declarations */ +struct runcxdef; +struct runufdef; +struct voccxdef; + +/** + * Stack element - the stack is an array of these structures + */ +struct runsdef { + uchar runstyp; /* type of element */ + union { + long runsvnum; /* numeric value */ + objnum runsvobj; /* object value */ + prpnum runsvprp; /* property number value */ + uchar *runsvstr; /* string/list value */ + } runsv; + + + /** + * Determine size of a data item + */ + int runsiz() const; +}; + +/** + * External function control structure + */ +struct runxdef { + char runxnam[TOKNAMMAX + 1]; /* name of external function */ + int (*runxptr)(void *); /* pointer to memory containing code */ +}; + +/** + * External function context structure - passed to user exits + */ +struct runuxdef { + runcxdef *runuxctx; /* run-time context */ + runufdef *runuxvec; /* vector of functions */ + int runuxargc; /* count of arguments to function */ +}; + +/** + * External function callback vector + */ +struct runufdef { + int (*runuftyp)(runuxdef *); /* type of top of stack */ + long (*runufnpo)(runuxdef *); /* pop a number */ + uchar *(*runufspo)(runuxdef *); /* pop a string */ + void (*runufdsc)(runuxdef *); /* discard item at top of stack */ + void (*runufnpu)(runuxdef *, long); /* push a number */ + void (*runufspu)(runuxdef *, uchar *); /* push alloc'd string */ + void (*runufcspu)(runuxdef *, char *); /* push a C-string */ + uchar *(*runufsal)(runuxdef *, int); /* allocate a new string */ + void (*runuflpu)(runuxdef *, int);/* push DAT_TRUE or DAT_NIL */ +}; + +/** + * Execution context + */ +struct runcxdef { + errcxdef *runcxerr; /* error management context */ + mcmcxdef *runcxmem; /* cache manager context for object references */ + runsdef *runcxstk; /* base of interpreter stack */ + runsdef *runcxstop; /* top of stack */ + runsdef *runcxsp; /* current stack pointer (stack grows upwards) */ + runsdef *runcxbp; /* base pointer */ + uchar *runcxheap; /* run-time variable-length object heap */ + uchar *runcxhp; /* current heap pointer */ + uchar *runcxhtop; /* top of heap */ + objucxdef *runcxundo; /* undo context */ + tiocxdef *runcxtio; /* text I/O context */ + void *runcxbcx; /* context for built-in callback functions */ + void (**runcxbi)(struct bifcxdef *ctx, int argc); + /* built-in functions */ + dbgcxdef *runcxdbg; /* debugger context */ + voccxdef *runcxvoc; /* player command parser context */ + void (*runcxdmd)(void *ctx, objnum obj, prpnum prp); + /* demand-loader callback function */ + void *runcxdmc; /* demand-loader callback context */ + runxdef *runcxext; /* external function array */ + int runcxexc; /* count of external functions */ + uint runcxlofs; /* offset of last line record encountered */ + char *runcxgamename; /* name of the .GAM file */ + char *runcxgamepath; /* absolute directory path of .GAM file */ + + /** + * Execute a function, given the function object number + */ + void runfn(noreg objnum objn, int argc); + + /** + * Execute p-code given a pointer to the code. p is the actual pointer + * to the first byte of code to be executed. self is the object to be + * used for the special 'self' pseudo-object, and target is the object + * whose data are actually being executed. targprop is the property being + * executed; 0 is used for functions. + */ + void runexe(uchar *p, objnum self, objnum target, + prpnum targprop, int argc); + + /** + * Push a value onto the stack + */ + void runpush(dattyp typ, runsdef *val); + + /** + * Push a value onto the stack that's already in the heap + */ + void runrepush(runsdef *val); + + /** + * Push a number onto the stack + */ + void runpnum(long val); + + /** + * Push an object onto the stack + */ + void runpobj(objnum obj); + + /** + * push nil + */ + void runpnil(runcxdef *ctx); + + /** + * push a value onto the stack from a buffer (propdef, list) + */ + void runpbuf(int typ, void *val); + + /** + * Push a counted-length string onto the stack + */ + void runpstr(char *str, int len, int sav); + + /** + * Push a C-style string onto the stack, converting escape codes. If + * the character contains backslashes, newline, or tab characters, we'll + * convert these characters to their escaped equivalent. + */ + void runpushcstr(char *str, size_t len, int sav); + + /** + * Push a property onto the stack. codepp is a pointer to the caller's + * code pointer, which will be updated if necessary; callobj and + * callofsp are the object and starting offset within the object of the + * code being executed by the caller, which are needed to update + * *codepp. Property 0 is used if a function is being executed. obj + * and prop are the object and property number whose value is to be + * pushed. If 'inh' is TRUE, it means that only a property inherited + * by 'obj' is to be considered; this is used for "pass"/"inherited" + * operations, with the current target object given as 'obj'. + */ + void runpprop(uchar *noreg *codepp, objnum callobj, + prpnum callprop, noreg objnum obj, prpnum prop, int inh, + int argc, objnum self); + + /** + * compare magnitudes of numbers/strings on top of stack; strcmp-like value + */ + int runmcmp(runcxdef *ctx); + + /** + * True if items at top of stack are equal, FALSE otherwise + */ + int runeq(runcxdef *ctx); + + /** + * Garbage collect heap, making sure 'siz' bytes are available afterwards + */ + void runhcmp(uint siz, uint below, + runsdef *val1, runsdef *val2, runsdef *val3); + + /** + * Find a sublist within a list, returning pointer to sublist or null + */ + uchar *runfind(uchar *list, runsdef *item); + + /** + * Restore code pointer from object.property + offset + */ + uchar *runcprst(uint ofs, objnum obj, prpnum prop); + + /** + * Add two runsdef values, returning result in *val + */ + void runadd(runsdef *val2, uint below); + + /** + * Subtract val2 from val, returning result in *val; return TRUE if + * value changed, FALSE otherwise (this is returned when subtracting + * something from a list that isn't in the list) + */ + int runsub(runsdef *val2, uint below); +}; + +/* top level runpprop, when caller is not executing in an object */ +/* void runppr(objnum obj, prpnum prp, int argc); */ +#define runppr(ctx, obj, prp, argc) \ + runpprop(ctx, (uchar **)0, (objnum)0, (prpnum)0, obj, prp, FALSE, argc, obj) + +/* discard top element on stack */ +/* void rundisc(runcxdef *ctx); */ +#define rundisc(ctx) (runstkund(ctx), (--((ctx)->runcxsp))) + +/* pop the top element on the stack */ +/* void runpop(runsdef *val); */ +#define runpop(ctx, v) \ + (runstkund(ctx), memcpy(v, (--((ctx)->runcxsp)), (size_t)sizeof(runsdef))) + +/* pop a numeric value, signalling an error if not a number */ +/* long runpopnum(runcxdef *ctx); */ +#define runpopnum(ctx) \ + (runstkund(ctx), ((--((ctx)->runcxsp))->runstyp!=DAT_NUMBER ? \ + (runsig(ctx,ERR_REQNUM), (long)0) : \ + ((ctx)->runcxsp->runsv.runsvnum))) + +/* pop an object, signalling an error if not an object */ +/* objnum runpopobj(runcxdef *ctx); */ +#define runpopobj(ctx) \ + (runstkund(ctx), ((--(ctx)->runcxsp))->runstyp!=DAT_OBJECT ? \ + (runsig(ctx,ERR_REQVOB), (objnum)0) : \ + ((ctx)->runcxsp->runsv.runsvobj)) + +/* pop an object or nil - returns MCMONINV if the value is nil */ +#define runpopobjnil(ctx) \ + (runstkund(ctx), ((--(ctx)->runcxsp))->runstyp==DAT_OBJECT ? \ + ((ctx)->runcxsp->runsv.runsvobj) : \ + ((ctx)->runcxsp->runstyp==DAT_NIL ? MCMONINV : \ + (runsig(ctx,ERR_REQVOB), (objnum)0))) + +/* pop a list, signalling an error if not a list */ +/* uchar *runpoplst(runcxdef *ctx); */ +#define runpoplst(ctx) \ + (runstkund(ctx), ((--(ctx)->runcxsp))->runstyp!=DAT_LIST ? \ + (runsig(ctx,ERR_REQVLS), (uchar *)0) : \ + (uchar *)((ctx)->runcxsp->runsv.runsvstr)) + +/* pop a property number, signalling an error if not a property number */ +/* prpnum runpopprp(runcxdef *ctx); */ +#define runpopprp(ctx) \ + (runstkund(ctx), ((--(ctx)->runcxsp))->runstyp!=DAT_PROPNUM ? \ + (runsig(ctx,ERR_REQVPR), (prpnum)0) : \ + ((ctx)->runcxsp->runsv.runsvprp)) + + +/* pop function pointer */ +/* objnum runpopfn(runcxdef *ctx); */ +#define runpopfn(ctx) \ + ((objnum)(runstkund(ctx), ((--(ctx)->runcxsp))->runstyp!=DAT_FNADDR ? \ + (runsig(ctx,ERR_REQVFN), (objnum)0) : \ + ((ctx)->runcxsp->runsv.runsvobj))) + +/* pop a string value */ +/* char *runpopstr(runcxdef *ctx); */ +#define runpopstr(ctx) \ + (runstkund(ctx), ((--((ctx)->runcxsp))->runstyp!=DAT_SSTRING ? \ + (runsig(ctx,ERR_REQSTR), (uchar *)0) : \ + ((ctx)->runcxsp->runsv.runsvstr))) + + +/* pop a logical value - TRUE for DAT_TRUE, FALSE for DAT_NIL */ +/* int runpoplog(runcxdef *ctx); */ +#define runpoplog(ctx) \ + ((--((ctx)->runcxsp))->runstyp==DAT_TRUE ? TRUE : \ + (ctx)->runcxsp->runstyp==DAT_NIL ? FALSE : \ + (runsig(ctx, ERR_REQLOG), 0)) + +/* get type of top of stack */ +/* int runtostyp(runcxdef *ctx); */ +#define runtostyp(ctx) (((ctx)->runcxsp - 1)->runstyp) + +/* determine if top of stack is logical value (returns TRUE if so) */ +/* int runtoslog(runcxdef *ctx); */ +#define runtoslog(ctx) \ + (runtostyp(ctx) == DAT_TRUE || runtostyp(ctx) == DAT_NIL) + +/* convert C logical to TADS logical (TRUE->DAT_TRUE, FALSE->DAT_NIL) */ +/* int runclog(int log); */ +#define runclog(l) ((l) ? DAT_TRUE : DAT_NIL) + + +/* + * Check to ensure we have enough arguments to pass to a function or method + * call - this simply ensures we have enough data in the current frame. + * This is important because the called function will be able to write to + * our frame. If we don't have enough arguments, we'll push enough 'nil' + * values to meet the need. + */ +#define runcheckargc(ctx, nargc) \ + while ((ctx)->runcxsp - (ctx)->runcxbp < *(nargc)) \ + runpnil(ctx) + +#ifdef RUNFAST +# define runstkovf(ctx) (DISCARD 0) +# define runstkund(ctx) (DISCARD 0) +#else /* RUNFAST */ +# define runstkovf(ctx) \ + ((ctx)->runcxsp >= (ctx)->runcxstop ? (runsig(ctx, ERR_STKOVF), \ + DISCARD 0) : DISCARD 0) +# define runstkund(ctx) \ + ((ctx)->runcxsp == (ctx)->runcxstk ? runsig(ctx, ERR_STKUND), \ + DISCARD 0 : DISCARD 0) +#endif /* RUNFAST */ + +/* reserve space in heap, collecting garbage if necessary */ +/* void runhres(uint siz, uint below); */ +#define runhres(ctx, siz, below) \ + ((uint)((ctx)->runcxhtop - (ctx)->runcxhp) > (uint)(siz) ? DISCARD 0 : \ + (runhcmp(ctx, siz, below, (runsdef *)0, (runsdef *)0, (runsdef *)0),\ + DISCARD 0)) + +/* reserve space, with various amounts of saving */ +#define runhres1(ctx, siz, below, val1) \ + ((uint)((ctx)->runcxhtop - (ctx)->runcxhp) > (uint)(siz) ? DISCARD 0 : \ + (runhcmp(ctx, siz, below, val1, (runsdef *)0, (runsdef *)0), DISCARD 0)) + +#define runhres2(ctx, siz, below, val1, val2) \ + ((uint)((ctx)->runcxhtop - (ctx)->runcxhp) > (uint)(siz) ? DISCARD 0 : \ + (runhcmp(ctx, siz, below, val1, val2, (runsdef *)0), DISCARD 0)) + +#define runhres3(ctx, siz, below, val1, val2, val3) \ + ((uint)((ctx)->runcxhtop - (ctx)->runcxhp) > (uint)(siz) ? DISCARD 0 : \ + (runhcmp(ctx, siz, below, val1, val2, val3), DISCARD 0)) + + +/* leave a stack frame, removing arguments */ +/* void runleave(uint parms); */ +#define runleave(ctx, parms) \ + (((ctx)->runcxsp = (ctx)->runcxbp), \ + ((ctx)->runcxbp = (runsdef *)((--((ctx)->runcxsp))->runsv.runsvstr)), \ + ((ctx)->runcxsp -= (parms))) + +/* reset run-time: throw away entire stack and heap */ +/* void runrst(runcxdef *ctx); */ +#define runrst(ctx) (((ctx)->runcxsp = (ctx)->runcxstk), \ + ((ctx)->runcxhp = (ctx)->runcxheap), \ + dbgrst(ctx->runcxdbg)) + + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/tads2.cpp b/engines/glk/tads/tads2/tads2.cpp index a772b44aed..b391857613 100644 --- a/engines/glk/tads/tads2/tads2.cpp +++ b/engines/glk/tads/tads2/tads2.cpp @@ -21,13 +21,18 @@ */ #include "glk/tads/tads2/tads2.h" +#include "glk/tads/tads2/built_in.h" +#include "glk/tads/tads2/debug.h" +#include "glk/tads/tads2/file_io.h" +#include "glk/tads/tads2/post_compilation.h" +#include "glk/tads/tads2/run.h" +#include "glk/tads/tads2/vocabulary.h" namespace Glk { namespace TADS { namespace TADS2 { -TADS2::TADS2(OSystem *syst, const GlkGameDescription &gameDesc) : OS(syst, gameDesc) { - cmap_init_default(); +TADS2::TADS2(OSystem *syst, const GlkGameDescription &gameDesc) : TADS(syst, gameDesc) { } void TADS2::runGame() { @@ -35,7 +40,7 @@ void TADS2::runGame() { errctx.errcxlgc = &errctx; errctx.errcxfp = nullptr; errctx.errcxofs = 0; - errctx.errcxappctx = this; + errctx.errcxappctx = nullptr; /* copyright-date-string */ #ifdef T2_COPYRIGHT_NOTICE @@ -48,13 +53,519 @@ void TADS2::runGame() { #endif trdmain1(&errctx); - - // pause before exiting if the OS desires it - os_expause(); } void TADS2::trdmain1(errcxdef *errctx) { + osfildef *swapfp = (osfildef *)0; + runcxdef runctx; + bifcxdef bifctx; + voccxdef vocctx; + void(*bif[100])(bifcxdef *, int); + mcmcxdef *mctx = 0; + mcmcx1def *globalctx = 0; + dbgcxdef dbg; + supcxdef supctx; + char *swapname = 0; + char swapbuf[OSFNMAX]; + char **argp; + char *arg; + char *infile; + char infile_abs[OSFNMAX]; /* fully-qualified input file name */ + char infile_path[OSFNMAX]; /* absolute path to input file */ + char *exefile; /* try with executable file if no infile */ + ulong swapsize = 0xffffffffL; /* allow unlimited swap space */ + int swapena = OS_DEFAULT_SWAP_ENABLED; /* swapping enabled? */ + int i; + int pause = FALSE; /* pause after finishing game */ + fiolcxdef fiolctx; + noreg int loadopen = FALSE; + char inbuf[OSFNMAX]; + ulong cachelimit = 0xffffffff; + ushort undosiz = TRD_UNDOSIZ; /* default undo context size 16k */ + objucxdef *undoptr = 0; + uint flags; /* flags used to write the file we're reading */ + objnum preinit; /* preinit object, if we need to execute it */ + uint heapsiz = TRD_HEAPSIZ; + uint stksiz = TRD_STKSIZ; + runsdef *mystack; + uchar *myheap; + extern osfildef *cmdfile; /* hacky v1 qa interface - command log fp */ + extern osfildef *logfp; /* hacky v1 qa interface - output log fp */ + int preload = FALSE; /* TRUE => preload all objects */ + ulong totsize; + extern voccxdef *main_voc_ctx; + int safety_read, safety_write; /* file I/O safety level */ + char *restore_file = 0; /* .SAV file to restore */ + char *charmap = 0; /* character map file */ + int charmap_none; /* explicitly do not use a character set */ + int doublespace = TRUE; /* formatter double-space setting */ +#ifdef TODO + NOREG((&loadopen)) + + /* initialize the output formatter */ + out_init(); + + /* set safety level to 2 by default - read any/write current dir only */ + safety_read = safety_write = 2; + + /* no -ctab- yet */ + charmap_none = FALSE; + + /* parse arguments */ + for (i = 1, argp = argv + 1; i < argc; ++argp, ++i) + { + arg = *argp; + if (*arg == '-') + { + switch (*(arg + 1)) + { + case 'c': + if (!strcmp(arg + 1, "ctab")) + { + /* get the character mapping table */ + charmap = cmdarg(ec, &argp, &i, argc, 4, trdusage); + } + else if (!strcmp(arg + 1, "ctab-")) + { + /* use the default mapping */ + charmap_none = TRUE; + } + else + trdusage(ec); + break; + + case 'r': + /* restore a game */ + restore_file = cmdarg(ec, &argp, &i, argc, 1, trdusage); + break; + + case 'i': + qasopn(cmdarg(ec, &argp, &i, argc, 1, trdusage), TRUE); + break; + + case 'o': + cmdfile = osfopwt(cmdarg(ec, &argp, &i, argc, 1, trdusage), + OSFTCMD); + break; + + case 'l': + logfp = osfopwt(cmdarg(ec, &argp, &i, argc, 1, trdusage), + OSFTCMD); + break; + + case 'p': + if (!stricmp(arg, "-plain")) + { + os_plain(); + break; + } + pause = cmdtog(ec, pause, arg, 1, trdusage); + break; + + case 'd': + if (!strnicmp(arg, "-double", 7)) + { + /* get the argument value */ + doublespace = cmdtog(ec, doublespace, arg, 6, trdusage); + + /* set the double-space mode in the formatter */ + out_set_doublespace(doublespace); + break; + } + break; + + case 's': + { + char *p; + + /* get the option */ + p = cmdarg(ec, &argp, &i, argc, 1, trdusage); + + /* if they're asking for help, display detailed usage */ + if (*p == '?') + trdusage_s(ec); + + /* get the safety level from the argument */ + safety_read = *p - '0'; + safety_write = (*(p + 1) != '\0' ? *(p + 1) - '0' : + safety_read); + + /* range-check the values */ + if (safety_read < 0 || safety_read > 4 + || safety_write < 0 || safety_write > 4) + trdusage_s(ec); + + /* tell the host system about the setting */ + if (appctx != 0 && appctx->set_io_safety_level != 0) + (*appctx->set_io_safety_level) + (appctx->io_safety_level_ctx, + safety_read, safety_write); + } + break; + + case 'm': + switch (*(arg + 2)) + { + case 's': + stksiz = atoi(cmdarg(ec, &argp, &i, argc, 2, trdusage)); + break; + + case 'h': + heapsiz = atoi(cmdarg(ec, &argp, &i, argc, 2, trdusage)); + break; + + default: + cachelimit = atol(cmdarg(ec, &argp, &i, argc, 1, + trdusage)); + break; + } + break; + + case 't': + /* swap file options: -tf file, -ts size, -t- (no swap) */ + switch (*(arg + 2)) + { + case 'f': + swapname = cmdarg(ec, &argp, &i, argc, 2, trdusage); + break; + + case 's': + swapsize = atol(cmdarg(ec, &argp, &i, argc, 2, trdusage)); + break; + + case 'p': + preload = cmdtog(ec, preload, arg, 2, trdusage); + break; + + default: + swapena = cmdtog(ec, swapena, arg, 1, trdusage); + break; + } + break; + + case 'u': + undosiz = atoi(cmdarg(ec, &argp, &i, argc, 1, trdusage)); + break; + + default: + trdusage(ec); + } + } + else break; + } + + /* presume we won't take the .gam from the application executable */ + exefile = 0; + + /* get input name argument, and make sure it's the last argument */ + if (i == argc) + { + osfildef *fp; + ulong curpos; + ulong endpos; + int use_exe; + + /* + * There's no input name argument, so we need to find the game + * to play some other way. First, check to see if we have a + * game to restore, and if so whether it has the .GAM name + * encoded into it. Next, look to see if there's a game + * attached to the executable file; if so, use it. If not, see + * if the host system wants to provide a name through its + * callback. + */ + + /* presume we won't find a game attached to the executable file */ + infile = 0; + use_exe = FALSE; + + /* + * see if we have a saved game to restore, and it specifies the + * GAM file that saved it + */ + if (restore_file != 0) + { + /* try getting the game name from the restore file */ + if (fiorso_getgame(restore_file, inbuf, sizeof(inbuf))) + { + /* got it - use this file */ + infile = inbuf; + } + } + + /* + * it that didn't work, try to read from os-dependent part of + * program being executed + */ + if (infile == 0) + { + /* try opening the executable file */ + exefile = (argv && argv[0] ? argv[0] : "TRX"); + fp = os_exeseek(exefile, "TGAM"); + if (fp != 0) + { + /* see if there's a game file attached to the executable */ + curpos = osfpos(fp); + osfseek(fp, 0L, OSFSK_END); + endpos = osfpos(fp); + osfcls(fp); + + /* if we found it, use it */ + if (endpos != curpos) + use_exe = TRUE; + } + } + + /* + * if we didn't find a game in the executable, try the host + * system callback + */ + if (infile == 0 && !use_exe) + { + /* + * ask the host system callback what to do - if we don't + * have a host system callback, or the callback + */ + if (appctx != 0 && appctx->get_game_name != 0) + { + /* call the host system callback */ + if ((*appctx->get_game_name)(appctx->get_game_name_ctx, + inbuf, sizeof(inbuf))) + { + /* the host system provided a name - use it */ + infile = inbuf; + } + else + { + /* + * the host didn't provide a name - simply display a + * message indicating that no game file has been + * chosen, and return + */ + trdptf("\n"); + trdptf("(No game has been selected.)\n"); + return; + } + } + else + { + /* + * we've run out of ways to get a filename - give the + * user the usage message and quit + */ + trdusage(ec); + } + } + } + else + { + infile = *argp; + if (i + 1 != argc) + trdusage(ec); +#ifndef OS_HATES_EXTENSIONS + /* + * If original name exists, use it; otherwise, try adding .GAM. + * Note that this code is ifdef'd so that platforms that don't + * use filename extensions in the manner conventional for DOS + * and Unix won't use this code. + */ + if (osfacc(infile)) + { + strcpy(inbuf, infile); + os_defext(inbuf, "gam"); + infile = inbuf; + } +#endif /* !defined(OS_HATES_EXTENSIONS) */ + } + + /* open up the swap file */ + if (swapena && swapsize) + { + swapfp = os_create_tempfile(swapname, swapbuf); + if (swapname == 0) swapname = swapbuf; + if (swapfp == 0) errsig(ec, ERR_OPSWAP); + } + + /* load the character map */ + if (charmap_none) + cmap_override(); + else if (cmap_load(charmap)) + errsig(ec, ERR_INVCMAP); + + ERRBEGIN(ec) + + /* initialize cache manager context */ + globalctx = mcmini(cachelimit, 128, swapsize, swapfp, swapname, ec); + mctx = mcmcini(globalctx, 128, fioldobj, &fiolctx, + objrevert, (void *)0); + mctx->mcmcxrvc = mctx; + + /* set up an undo context */ + if (undosiz) + undoptr = objuini(mctx, undosiz, vocdundo, vocdusz, &vocctx); + else + undoptr = (objucxdef *)0; + + /* set up vocabulary context */ + vocini(&vocctx, ec, mctx, &runctx, undoptr, 100, 100, 200); + + /* + * save a pointer to the voc context globally, so that certain + * external routines (such as Unix-style signal handlers) can reach + * it + */ + main_voc_ctx = &vocctx; + + /* allocate stack and heap */ + totsize = (ulong)stksiz * (ulong)sizeof(runsdef); + if (totsize != (size_t)totsize) + errsig1(ec, ERR_STKSIZE, ERRTINT, (uint)(65535 / sizeof(runsdef))); + mystack = (runsdef *)mchalo(ec, (size_t)totsize, "runtime stack"); + myheap = mchalo(ec, heapsiz, "runtime heap"); + + /* get the absolute path for the input file */ + if (infile != 0) + os_get_abs_filename(infile_abs, sizeof(infile_abs), infile); + else if (exefile != 0) + os_get_abs_filename(infile_abs, sizeof(infile_abs), exefile); + else + infile_abs[0] = '\0'; + os_get_path_name(infile_path, sizeof(infile_path), infile_abs); + + /* set up execution context */ + runctx.runcxerr = ec; + runctx.runcxmem = mctx; + runctx.runcxstk = mystack; + runctx.runcxstop = &mystack[stksiz]; + runctx.runcxsp = mystack; + runctx.runcxbp = mystack; + runctx.runcxheap = myheap; + runctx.runcxhp = myheap; + runctx.runcxhtop = &myheap[heapsiz]; + runctx.runcxundo = undoptr; + runctx.runcxbcx = &bifctx; + runctx.runcxbi = bif; + runctx.runcxtio = (tiocxdef *)0; + runctx.runcxdbg = &dbg; + runctx.runcxvoc = &vocctx; + runctx.runcxdmd = supcont; + runctx.runcxdmc = &supctx; + runctx.runcxext = 0; + runctx.runcxgamename = infile; + runctx.runcxgamepath = infile_path; + + /* set up setup context */ + supctx.supcxerr = ec; + supctx.supcxmem = mctx; + supctx.supcxtab = (tokthdef *)0; + supctx.supcxbuf = (uchar *)0; + supctx.supcxlen = 0; + supctx.supcxvoc = &vocctx; + supctx.supcxrun = &runctx; + + /* set up debug context */ + dbg.dbgcxtio = (tiocxdef *)0; + dbg.dbgcxmem = mctx; + dbg.dbgcxerr = ec; + dbg.dbgcxtab = (tokthdef *)0; + dbg.dbgcxfcn = 0; + dbg.dbgcxdep = 0; + dbg.dbgcxflg = 0; + dbg.dbgcxlin = (lindef *)0; /* no line sources yet */ + + /* set up built-in function context */ + CLRSTRUCT(bifctx); + bifctx.bifcxerr = ec; + bifctx.bifcxrun = &runctx; + bifctx.bifcxtio = (tiocxdef *)0; + bifctx.bifcxrnd = 0; + bifctx.bifcxrndset = FALSE; + bifctx.bifcxappctx = appctx; + bifctx.bifcxsafetyr = safety_read; + bifctx.bifcxsafetyw = safety_write; + bifctx.bifcxsavext = save_ext; + + /* initialize the regular expression parser context */ + re_init(&bifctx.bifcxregex, ec); + + /* add the built-in functions, keywords, etc */ + supbif(&supctx, bif, (int)(sizeof(bif) / sizeof(bif[0]))); + + /* set up status line hack */ + runistat(&vocctx, &runctx, (tiocxdef *)0); + + /* turn on the "busy" cursor before loading */ + os_csr_busy(TRUE); + + /* read the game from the binary file */ + fiord(mctx, &vocctx, (struct tokcxdef *)0, + infile, exefile, &fiolctx, &preinit, &flags, + (struct tokpdef *)0, (uchar **)0, (uint *)0, (uint *)0, + (preload ? 2 : 0), appctx, argv[0]); + loadopen = TRUE; + + /* turn off the "busy" cursor */ + os_csr_busy(FALSE); + + /* play the game */ + plygo(&runctx, &vocctx, (tiocxdef *)0, preinit, restore_file); + + /* close load file */ + fiorcls(&fiolctx); + + if (pause) + { + trdptf("[press a key to exit]"); + os_waitc(); + trdptf("\n"); + } + + /* close and delete swapfile, if one was opened */ + trd_close_swapfile(&runctx); + + /* make sure the script file is closed, if we have one */ + qasclose(); + + ERRCLEAN(ec) + /* close and delete swapfile, if one was opened */ + trd_close_swapfile(&runctx); + + /* close the load file if one was opened */ + if (loadopen) + fiorcls(&fiolctx); + + /* vocctx is going out of scope - forget the global reference to it */ + main_voc_ctx = 0; + + /* delete the voc context */ + vocterm(&vocctx); + + /* delete the undo context */ + if (undoptr != 0) + objuterm(undoptr); + + /* release the object cache structures */ + if (mctx != 0) + mcmcterm(mctx); + if (globalctx != 0) + mcmterm(globalctx); + ERRENDCLN(ec) + + /* vocctx is going out of scope - forget the global reference to it */ + main_voc_ctx = 0; + + /* delete the voc contxt */ + vocterm(&vocctx); + + /* delete the undo context */ + if (undoptr != 0) + objuterm(undoptr); + + /* release the object cache structures */ + mcmcterm(mctx); + mcmterm(globalctx); +#endif } void TADS2::trdptf(const char *fmt, ...) { diff --git a/engines/glk/tads/tads2/tads2.h b/engines/glk/tads/tads2/tads2.h index 2472929840..0ea1662b00 100644 --- a/engines/glk/tads/tads2/tads2.h +++ b/engines/glk/tads/tads2/tads2.h @@ -23,78 +23,40 @@ #ifndef GLK_TADS_TADS2 #define GLK_TADS_TADS2 -#include "glk/tads/tads2/os.h" -#include "glk/tads/tads2/ler.h" +#include "glk/tads/tads.h" +#include "glk/tads/tads2/error_handling.h" +#include "glk/tads/tads2/appctx.h" namespace Glk { namespace TADS { namespace TADS2 { -/** - * map a native character (read externally) into an internal character +/* + * Run-time version number */ -#define cmap_n2i(c) (G_cmap_input[(unsigned char)(c)]) +#define TADS_RUNTIME_VERSION "2.5.17" -/** - * map an internal character into a native character (for display) - */ -#define cmap_i2n(c) (G_cmap_output[(unsigned char)(c)]) - -/** - * the full name (for display purposes) of the loaded character set - */ -#define CMAP_LDESC_MAX_LEN 40 - -/** - * Maximum expansion for an HTML entity mapping - */ -#define CMAP_MAX_ENTITY_EXPANSION 50 +# define TRD_HEAPSIZ 4096 +# define TRD_STKSIZ 200 +# define TRD_UNDOSIZ (16 * 1024) +# define TDD_HEAPSIZ 4096 +# define TDD_STKSIZ 200 +# define TDD_UNDOSIZ (16 * 1024) +# define TDD_POOLSIZ (2 * 1024) +# define TDD_LCLSIZ 0 +# define ERR_TRUS_OS_FIRST 100 +# define ERR_TRUS_OS_LAST 99 /** * TADS 2 game interpreter */ -class TADS2 : public OS { +class TADS2 : public TADS { private: // STUBS void os_printz(const Common::String &s) {} void tio_set_html_expansion(unsigned int html_char_val, const char *expansion, size_t expansion_len) {} -private: - /** - * \defgroup cmap - * @{ - */ - - /** - * flag: true -> a character set has been explicitly loaded, so we - * should ignore any game character set setting - */ - bool S_cmap_loaded; - - /** - * input-mapping table - for native character 'n', cmap_input[n] yields - * the internal character code - */ - unsigned char G_cmap_input[256]; - - /** - * output-mapping table - for internal character 'n', cmap_output[n] - * yields the output character code - */ - unsigned char G_cmap_output[256]; - - /** - * the ID of the loaded character set - */ - char G_cmap_id[5]; - - /** - * the full name (for display purposes) of the loaded character set - */ - char G_cmap_ldesc[CMAP_LDESC_MAX_LEN + 1]; - - /**@}*/ private: /** * \defgroup trd @@ -109,55 +71,6 @@ private: void trdptf(const char *fmt, ...); /**@}*/ - - /** - * \defgroup cmap - * @{ - */ - - /** - * Initialize the default character mappings. If no mapping file is to - * be read, this function will establish identify mappings that leave - * characters untranslated. - */ - void cmap_init_default(); - - /** - * Load a character map file. Returns zero on success, non-zero on - * failure. If filename is null, we'll use the default mapping. - */ - int cmap_load(const char *filename); - - /** - * Turn off character translation. This overrides any game character - * set that we find and simply uses the default translation. - */ - void cmap_override(void); - - /** - * Set the game's internal character set. This should be called when a - * game is loaded, and the game specifies an internal character set. If - * there is no character map file explicitly loaded, we will attempt to - * load a character mapping file that maps this character set to the - * current native character set. Signals an error on failure. This - * routine will succeed (without doing anything) if a character set has - * already been explicitly loaded, since an explicitly-loaded character - * set overrides the automatic character set selection that we attempt - * when loading a game. - * - * argv0 must be provided so that we know where to look for our mapping - * file on systems where mapping files are stored in the same directory - * as the TADS executables. - */ - void cmap_set_game_charset(errcxdef *errctx, const char *internal_id, - const char *internal_ldesc, const char *argv0); - - /** - * Internal routine to load a character map from a file - */ - int cmap_load_internal(const char *filename); - - /**@}*/ public: /** * Constructor @@ -175,7 +88,7 @@ public: virtual InterpreterType getInterpreterType() const override { return INTERPRETER_TADS2; } }; -typedef TADS2 appctxdef; +//typedef TADS2 appctxdef; } // End of namespace TADS2 } // End of namespace TADS diff --git a/engines/glk/tads/tads2/tads2_cmap.cpp b/engines/glk/tads/tads2/tads2_cmap.cpp deleted file mode 100644 index 68945f7336..0000000000 --- a/engines/glk/tads/tads2/tads2_cmap.cpp +++ /dev/null @@ -1,268 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "glk/tads/tads2/tads2.h" -#include "glk/tads/tads2/types.h" - -namespace Glk { -namespace TADS { -namespace TADS2 { - -/** - * Signatures for character map files. The signature is stored at the - * beginning of the file. - */ -// single-byte character map version 1.0.0 -#define CMAP_SIG_S100 "TADS2 charmap S100\n\r\01a" - -void TADS2::cmap_init_default() { - size_t i; - - // initialize the input table - for (i = 0 ; i < sizeof(G_cmap_input)/sizeof(G_cmap_input[0]) ; ++i) - G_cmap_input[i] = (unsigned char)i; - - // initialize the output table - for (i = 0 ; i < sizeof(G_cmap_output)/sizeof(G_cmap_output[0]) ; ++i) - G_cmap_output[i] = (unsigned char)i; - - // we have a null ID - memset(G_cmap_id, 0, sizeof(G_cmap_id)); - - // indicate that it's the default - strcpy(G_cmap_ldesc, "(native/no mapping)"); - - // note that we have no character set loaded - S_cmap_loaded = false; -} - -int TADS2::cmap_load_internal(const char *filename) { - osfildef *fp; - static char sig1[] = CMAP_SIG_S100; - char buf[256]; - uchar lenbuf[2]; - size_t len; - int sysblk; - - // if there's no mapping file, use the default mapping - if (filename == 0) { - // initialize with the default mapping - cmap_init_default(); - - // return success - return 0; - } - - // open the file - fp = osfoprb(filename, OSFTCMAP); - if (fp == 0) - return 1; - - // check the signature - if (osfrb(fp, buf, sizeof(sig1)) - || memcmp(buf, sig1, sizeof(sig1)) != 0) { - osfcls(fp); - return 2; - } - - // load the ID - G_cmap_id[4] = '\0'; - if (osfrb(fp, G_cmap_id, 4)) { - osfcls(fp); - return 3; - } - - // load the long description - if (osfrb(fp, lenbuf, 2) - || (len = osrp2(lenbuf)) > sizeof(G_cmap_ldesc) - || osfrb(fp, G_cmap_ldesc, len)) { - osfcls(fp); - return 4; - } - - // load the two tables - input, then output - if (osfrb(fp, G_cmap_input, sizeof(G_cmap_input)) - || osfrb(fp, G_cmap_output, sizeof(G_cmap_output))) { - osfcls(fp); - return 5; - } - - // read the next section header - if (osfrb(fp, buf, 4)) { - osfcls(fp); - return 6; - } - - // if it's "SYSI", read the system information string - if (!memcmp(buf, "SYSI", 4)) { - // read the length prefix, then the string - if (osfrb(fp, lenbuf, 2) - || (len = osrp2(lenbuf)) > sizeof(buf) - || osfrb(fp, buf, len)) { - osfcls(fp); - return 7; - } - - // we have a system information block - sysblk = true; - } else { - // there's no system information block - sysblk = false; - } - - /* - * call the OS code, so that it can do any system-dependent - * initialization for the new character mapping - */ - os_advise_load_charmap(G_cmap_id, G_cmap_ldesc, sysblk ? buf : ""); - - // read the next section header - if (sysblk && osfrb(fp, buf, 4)) { - osfcls(fp); - return 8; - } - - // see if we have an entity list - if (!memcmp(buf, "ENTY", 4)) { - // read the entities - for (;;) { - unsigned int cval; - char expansion[CMAP_MAX_ENTITY_EXPANSION]; - - // read the next item's length and character value - if (osfrb(fp, buf, 4)) { - osfcls(fp); - return 9; - } - - // decode the values - len = osrp2(buf); - cval = osrp2(buf+2); - - // if we've reached the zero marker, we're done - if (len == 0 && cval == 0) - break; - - // read the string - if (len > CMAP_MAX_ENTITY_EXPANSION - || osfrb(fp, expansion, len)) { - osfcls(fp); - return 10; - } - - // tell the output code about the expansion - tio_set_html_expansion(cval, expansion, len); - } - } - - /* - * ignore anything else we find - if the file format is updated to - * include extra information in the future, and this old code tries - * to load an updated file, we'll just ignore the new information, - * which should always be placed after the "SYSI" block (if present) - * to ensure compatibility with past versions (such as this code) - */ - // no problems - close the file and return success - osfcls(fp); - return 0; -} - -int TADS2::cmap_load(const char *filename) { - int err; - - // try loading the file - if ((err = cmap_load_internal(filename)) != 0) - return err; - - /* - * note that we've explicitly loaded a character set, if they named - * a character set (if not, this simply establishes the default - * setting, so we haven't explicitly loaded anything) - */ - if (filename != nullptr) - S_cmap_loaded = true; - - // success - return 0; -} - -void TADS2::cmap_override() { - // apply the default mapping - cmap_init_default(); - - /* - * pretend we have a character map loaded, so that we don't try to - * load another one if the game specifies a character set - */ - S_cmap_loaded = true; -} - -void TADS2::cmap_set_game_charset(errcxdef *ec, const char *internal_id, - const char *internal_ldesc, const char *argv0) { - char filename[OSFNMAX]; - - /* - * If a character set is already explicitly loaded, ignore the - * game's character set - the player asked us to use a particular - * mapping, so ignore what the game wants. (This will probably - * result in incorrect display of non-ASCII character values, but - * the player is most likely to use this to avoid errors when an - * appropriate mapping file for the game is not available. In this - * case, the player informs us by setting the option that he or she - * knows and accepts that the game will not look exactly right.) - */ - if (S_cmap_loaded) - return; - - /* - * ask the operating system to name the mapping file -- this routine - * will determine, if possible, the current native character set, - * and apply a system-specific naming convention to tell us what - * mapping file we should open - */ - os_gen_charmap_filename(filename, internal_id, argv0); - - // try loading the mapping file - if (cmap_load_internal(filename)) - errsig2(ec, ERR_CHRNOFILE, - ERRTSTR, errstr(ec, filename, strlen(filename)), - ERRTSTR, errstr(ec, internal_ldesc, strlen(internal_ldesc))); - - /** - * We were successful - the game's internal character set is now - * mapped to the current native character set. Even though we - * loaded an ldesc from the mapping file, forget that and store the - * internal ldesc that the game specified. The reason we do this is - * that it's possible that the player will dynamically switch native - * character sets in the future, at which point we'll need to - * re-load the mapping table, which could raise an error if a - * mapping file for the new character set isn't available. So, we - * may need to provide the same explanation later that we needed to - * provide here. Save the game's character set ldesc for that - * eventuality, since it describes exactly what the *game* wanted. - */ - strcpy(G_cmap_ldesc, internal_ldesc); -} - -} // End of namespace TADS2 -} // End of namespace TADS -} // End of namespace Glk diff --git a/engines/glk/tads/tads2/text_io.h b/engines/glk/tads/tads2/text_io.h new file mode 100644 index 0000000000..33a914e0ba --- /dev/null +++ b/engines/glk/tads/tads2/text_io.h @@ -0,0 +1,233 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_TADS_TADS2_TEXT_IO +#define GLK_TADS_TADS2_TEXT_IO + +/* + * Text I/O interface + * + * Formatted text input and output interface definition + */ + +#include "glk/tads/tads.h" +#include "glk/tads/tads2/error_handling.h" +#include "glk/tads/tads2/run.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* forward decls */ +struct runcxdef; + +/** + * Text i/o context + */ +struct tiocxdef { + errcxdef *tiocxerr; /* error handling context */ +}; + +/** + * Initialize the output formatter subsystem. This must be called once + * at startup. + */ +void out_init(); + + +/* redirect all tioxxx routines to TADS v1.x outxxx equivalents */ +#define tioflushn(ctx, nl) outflushn(nl) +#define tioflush(ctx) outflush() +#define tioblank(ctx) outblank() +#define tioreset(ctx) outreset() +#define tiogets(ctx, prompt, str, siz) getstring(prompt, str, siz) +#define tioputs(ctx, str) outformat(str) +#define tioputslen(ctx, str, len) outformatlen(str, len) +#define tiocaps(ctx) outcaps() +#define tionocaps(ctx) outnocaps() +#define tioshow(ctx) outshow() +#define tiohide(ctx) outhide() +#define tioscore(ctx, s1, s2) os_score(s1, s2) +#define tiostrsc(ctx, s) os_strsc(s) + +/* set up format strings in output subsystem */ +void tiosetfmt(tiocxdef *ctx, runcxdef *rctx, uchar *fmtbase, + uint fmtlen); + +/* tell tio subsystem the current actor */ +void tiosetactor(tiocxdef *ctx, objnum actor); + +/* get the current tio subsystem actor */ +objnum tiogetactor(tiocxdef *ctx); + +/* turn output capture on/off */ +void tiocapture(tiocxdef *tioctx, mcmcxdef *memctx, int flag); + +/* get the capture object handle */ +mcmon tiogetcapture(tiocxdef *ctx); + +/* get the amount of text captured */ +uint tiocapturesize(tiocxdef *ctx); + +/* format a length-prefixed (runtime-style) string to the display */ +void outfmt(tiocxdef *ctx, uchar *txt); + +/* format a null-terminated (C-style) string to the display */ +int outformat(char *s); + +/* format a counted-length string, which may not be null-terminated */ +int outformatlen(char *s, uint len); + +/* flush output, with specified newline mode */ +void outflushn(int nl); + +/* flush output */ +void outflush(void); + +/* reset output state */ +void outreset(void); + +/* + * Get a string from the keyboard. Returns non-zero if an error occurs + * (in particular, if no more input is available from the keyboard), + * zero on success. + */ +int getstring(char *prompt, char *buf, int bufl); + +/* set capitalize-next-character mode on/off */ +void outcaps(void); +void outnocaps(void); + +/* open/close output log file */ +int tiologopn(tiocxdef *ctx, char *fn); +int tiologcls(tiocxdef *ctx); + +/* + * Write text explicitly to the log file. This can be used to add + * special text (such as prompt text) that would normally be suppressed + * from the log file. When more mode is turned off, we don't + * automatically copy text to the log file; any text that the caller + * knows should be in the log file during times when more mode is turned + * off can be explicitly added with this function. + * + * If nl is true, we'll add a newline at the end of this text. The + * caller should not include any newlines in the text being displayed + * here. + */ +void out_logfile_print(char *txt, int nl); + + +/* + * Check output status. Indicate whether output is currently hidden, + * and whether any hidden output has occurred. + */ +void outstat(int *hidden, int *output_occurred); + +/* hide/show output */ +void outhide(void); +int outshow(void); + +/* set the flag to indicate that output has occurred */ +void outsethidden(void); + +/* write a blank line */ +void outblank(void); + +/* start/end watchpoint evaluation */ +void outwx(int flag); + +/* Begin/end capturing */ +void tiocapture(tiocxdef *tioctx, mcmcxdef *memctx, int flag); + +/* clear all captured output */ +void tioclrcapture(tiocxdef *tioctx); + +/* + * clear captured output back to a given point -- this can be used to + * remove captured output in an inner capture from an enclosing capture + */ +void tiopopcapture(tiocxdef *tioctx, uint orig_size); + +/* get the object handle of the captured output */ +mcmon tiogetcapture(tiocxdef *ctx); + +/* get the amount of text captured */ +uint tiocapturesize(tiocxdef *ctx); + +/* turn MORE mode on or off */ +int setmore(int state); + +/* explicitly activate the "MORE" prompt */ +void out_more_prompt(); + +/* + * QA controller functions + */ +int qasopn(char *scrnam, int quiet); +void qasclose(void); +char *qasgets(char *buf, int bufl); + +/* + * Set an HTML entity expansion. This is called during initialization + * when we read a character mapping table that includes HTML entity + * expansions. The HTML run-time uses its own expansion mechanism, so + * it will ignore this information. The standard character-mode TADS + * run-time, however, uses this information to map HTML entities to the + * local character set. + */ +void tio_set_html_expansion(unsigned int html_char_val, + const char *expansion, size_t expansion_len); + +/* check for HTML mode - returns true if an "\H+" sequence is active */ +int tio_is_html_mode(); + +/* set the user output filter function */ +void out_set_filter(objnum filter_fn); + +/* set the double-space mode */ +void out_set_doublespace(int dbl); + +/* + * Ask for a filename, using a system-defined dialog (via os_askfile) if + * possible. Uses the same interface as os_askfile(), which we will + * call directly for graphical implementations. We'll use formatted + * text for text-only implementations. + */ +int tio_askfile(const char *prompt, char *fname_buf, int fname_buf_len, + int prompt_type, os_filetype_t file_type); + +/* + * Display a dialog, using a system-defined dialog (via os_input_dialog) + * if possible. Uses the same interface as os_input_dialog(), which we + * will call directly for graphical implementations. We'll use + * formatted text for text-only implementations. + */ +int tio_input_dialog(int icon_id, const char *prompt, int standard_button_set, + const char **buttons, int button_count, + int default_index, int cancel_index); + + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/tokenizer.cpp b/engines/glk/tads/tads2/tokenizer.cpp new file mode 100644 index 0000000000..59dceebaf6 --- /dev/null +++ b/engines/glk/tads/tads2/tokenizer.cpp @@ -0,0 +1,33 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/tads2/tokenizer.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +// TODO: Rest of tokenizer stuff + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/tads2/tokenizer.h b/engines/glk/tads/tads2/tokenizer.h new file mode 100644 index 0000000000..50a8492cd3 --- /dev/null +++ b/engines/glk/tads/tads2/tokenizer.h @@ -0,0 +1,473 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_TADS_TADS2_TOKENIZER +#define GLK_TADS_TADS2_TOKENIZER + +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/error_handling.h" +#include "glk/tads/tads2/line_source.h" +#include "glk/tads/tads2/memory_cache.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* number of entries in hash table - must be power of 2 */ +#define TOKHASHSIZE 256 + +/* symbol types */ +#define TOKSTUNK 0 /* unknown symbol, not yet defined */ +#define TOKSTFUNC 1 /* function; value is object number */ +#define TOKSTOBJ 2 /* object; value is object number */ +#define TOKSTPROP 3 /* property; value is property number */ +#define TOKSTLOCAL 4 /* a local variable or formal parameter */ +#define TOKSTSELF 5 /* the pseudo-object "self" */ +#define TOKSTBIFN 6 /* a built-in function */ +#define TOKSTFWDOBJ 7 /* forward-referenced object */ +#define TOKSTFWDFN 8 /* forward-referenced object */ +#define TOKSTINHERIT 9 /* the pseudo-object "inherited" */ +#define TOKSTEXTERN 10 /* an external function */ +#define TOKSTKW 11 /* keyword; value is token number */ +#define TOKSTLABEL 12 /* statement label */ +#define TOKSTARGC 13 /* 'argcount' pseudo-variable */ +#define TOKSTPROPSPEC 14 /* speculative evaluation property */ + +/* token types */ +#define TOKTEOF 1 + +/* binary operators - keep these together (see prsbopl[] in prs.c) */ +#define TOKTPLUS 2 +#define TOKTMINUS 3 +#define TOKTDIV 4 +#define TOKTTIMES 5 +#define TOKTNOT 6 /* ! or "not" */ +#define TOKTEQ 7 +#define TOKTNE 8 +#define TOKTGT 9 +#define TOKTGE 10 +#define TOKTLT 11 +#define TOKTLE 12 +#define TOKTMOD 13 +#define TOKTBAND 14 +#define TOKTBOR 15 +#define TOKTXOR 16 +#define TOKTSHL 17 +#define TOKTSHR 18 +#define TOKTTILDE 30 + +/* + * special 'dot' replacement for speculative evaluation mode -- this is + * strictly for marking parse tree nodes, and has the same meaning in a + * parse tree node as a regular TOKTDOT, but generates code that can't + * call methods + */ +#define TOKTDOTSPEC 31 + +/* special node marker for explicit superclass inheritance nodes */ +#define TOKTEXPINH 32 + +#define TOKTLPAR 50 /* ( */ +#define TOKTRPAR 51 /* ) */ +#define TOKTCOLON 52 +#define TOKTDSTRING 53 /* string in double quotes */ +#define TOKTSSTRING 54 /* string in single quotes */ +#define TOKTNUMBER 55 +#define TOKTSYMBOL 56 +#define TOKTINVALID 57 /* invalid lexical token */ +#define TOKTLBRACK 58 /* [ */ +#define TOKTRBRACK 59 /* ] */ +#define TOKTLBRACE 60 /* { */ +#define TOKTRBRACE 61 /* } */ +#define TOKTSEM 62 /* ; */ +#define TOKTCOMMA 63 +#define TOKTDOT 64 /* . */ +#define TOKTOR 65 /* | or "if" */ +#define TOKTAND 66 /* & or "and" */ +#define TOKTIF 67 /* keywords */ +#define TOKTELSE 68 +#define TOKTWHILE 69 +#define TOKTFUNCTION 70 +#define TOKTRETURN 71 +#define TOKTLOCAL 72 +#define TOKTOBJECT 73 +#define TOKTBREAK 74 +#define TOKTCONTINUE 75 +#define TOKTLIST 76 /* a list */ +#define TOKTNIL 77 +#define TOKTTRUE 78 +#define TOKTPASS 79 +#define TOKTCLASS 80 +#define TOKTEXIT 81 +#define TOKTABORT 82 +#define TOKTASKDO 83 +#define TOKTASKIO 84 +#define TOKTPOUND 85 /* # */ +#define TOKTQUESTION 86 /* ? */ +#define TOKTCOMPOUND 87 +#define TOKTIOSYN 88 +#define TOKTDOSYN 89 +#define TOKTEXTERN 90 +#define TOKTFORMAT 91 +#define TOKTDO 92 +#define TOKTFOR 93 +#define TOKTNEW 94 +#define TOKTDELETE 95 + +/* assignment operators - keep these together */ +#define TOKTINC 150 /* ++ */ +#define TOKTPOSTINC 151 /* MUST BE TOKTINC + 1 */ +#define TOKTDEC 152 /* -- */ +#define TOKTPOSTDEC 153 /* MUST BE TOKTDEC + 1 */ +#define TOKTPLEQ 154 /* += */ +#define TOKTMINEQ 155 /* -= */ +#define TOKTDIVEQ 156 /* /= */ +#define TOKTTIMEQ 157 /* *= */ +#define TOKTASSIGN 158 /* simple assignment */ +#define TOKTMODEQ 159 /* %= (mod and assign) operator */ +#define TOKTBANDEQ 160 /* &= */ +#define TOKTBOREQ 161 /* |= */ +#define TOKTXOREQ 162 /* ^= (xor and assign) */ +#define TOKTSHLEQ 163 /* <<= (shift left and assign) */ +#define TOKTSHREQ 164 /* >>= (shift right and assign */ + +#define TOKTSWITCH 200 +#define TOKTCASE 201 +#define TOKTDEFAULT 202 +#define TOKTGOTO 203 +#define TOKTELLIPSIS 204 /* ... */ +#define TOKTSPECIAL 205 /* "specialWords" */ +#define TOKTREPLACE 206 /* replace */ +#define TOKTMODIFY 207 /* modify */ + +#define TOKTEQEQ 208 /* the '==' operator */ +#define TOKTPOINTER 209 /* the -> operator */ + +/* the longest a symbol name can be */ +#define TOKNAMMAX 39 + +/* symbol table entry */ +struct toksdef { + uchar tokstyp; /* type of the symbol */ + uchar tokshsh; /* hash value of symbol */ + ushort toksval; /* value of the symbol (depends on type) */ + ushort toksfr; /* frame offset of symbol (for debugger) */ + uchar tokslen; /* length of the symbol's name */ + char toksnam[TOKNAMMAX]; /* name of symbol */ +}; + +/* symbol table entry without 'name' portion - for allocation purposes */ +struct toks1def { + uchar tokstyp; + uchar tokshsh; + ushort toksval; + ushort toksfr; + uchar tokslen; + char toksnam[1]; +}; + +/* generic symbol table object - other symbol tables are subclasses */ +struct toktdef { + void (*toktfadd)(toktdef *tab, char *name, int namel, int typ, + int val, int hash); /* add symbol */ + int (*toktfsea)(toktdef *tab, char *name, int namel, int hash, + toksdef *ret); /* search symbol table */ + void (*toktfset)(toktdef *tab, toksdef *sym); + /* update val & typ of symbol to those in *sym */ + void (*toktfeach)(toktdef *tab, + void (*fn)(void *ctx, toksdef *sym), + void *fnctx); /* call fn for each sym */ + toktdef *toktnxt; /* next symbol table to be searched */ + errcxdef *tokterr; /* error handling context */ +}; + +/* maximum number of pools (TOKTSIZE bytes each) for symbols */ +#define TOKPOOLMAX 128 + +/* pointer to a symbol in a hashed symbol table */ +struct tokthpdef { + mcmon tokthpobj; /* cache manager object number of page */ + uint tokthpofs; /* offset within page of this symbol */ +}; + +/* extended symbol entry in a hashed symbol table */ +struct tokshdef { + tokthpdef tokshnxt; /* pointer to next symbol in the table */ + toksdef tokshsc; /* superclass - normal symbol entry */ +}; + +/* hashing symbol table (subclass of generic symbol table) */ +struct tokthdef { + toktdef tokthsc; /* generic symbol table superclass data */ + mcmcxdef *tokthmem; /* memory manager context */ + tokthpdef tokthhsh[TOKHASHSIZE]; /* hash table */ + uint tokthpcnt; /* number of memory pools for toksdef's */ + mcmon tokthpool[TOKPOOLMAX]; /* memory pools for toksdef's */ + uint tokthfinal[TOKPOOLMAX]; /* actual sizes of these pools */ + uchar *tokthcpool; /* current pool pointer */ + ushort tokthsize; /* remaining size of top memory pool */ + ushort tokthofs; /* allocation offset in top memory pool */ +}; + +/* size of toksdef pools to allocate for hashed symbol tables */ +#define TOKTHSIZE 4096 + +/* + * Linear cache-object-embedded symbol table. This type of symbol + * table is used for frame parameter/local variable lists. It is best + * for small tables, because it isn't broken up into hash buckets, so it + * is searched linearly. As a result, it's small enough to be embedded + * in code. + */ +struct toktldef { + toktdef toktlsc; /* generic symbol table superclass data */ + uchar *toktlptr; /* base of linear symbol table */ + uchar *toktlnxt; /* next free byte in table */ + uint toktlcnt; /* number of objects in the table */ + uint toktlsiz; /* bytes remaining in the table */ +}; + +struct tokdef { + int toktyp; /* type of the token */ + int toklen; /* length of token text, if a symbolic token */ + long tokval; /* numeric value, if applicable */ + ushort tokofs; + uint tokhash; /* token hash value, if a symbolic token */ + char toknam[TOKNAMMAX+1]; /* text of token, if a symbolic token */ + toksdef toksym; /* symbol from table matching token */ +}; + +/* special character sequence */ +#define TOKSCMAX 3 /* maximum length of a special char sequence */ +struct tokscdef { + tokscdef *tokscnxt; /* next sequence with same first character */ + int toksctyp; /* token type corresponding to sequence */ + int toksclen; /* length of the sequence */ + char tokscstr[TOKSCMAX+1]; /* the sequence itself */ +}; + +/* + * Compare a special character sequence - for efficiency, define + * something special for the maximum length available (TOKSCMAX). + * Note that the first character will always be equal, or the + * string wouldn't even get to the point of being tested by this + * macro. + */ +#if TOKSCMAX == 3 +# define toksceq(str1, str2, len1, len2) \ + ((len2) >= (len1) \ + && ((len1) == 1 \ + || ((str1)[1] == (str2)[1] \ + && ((len1) == 2 \ + || (str1)[2] == (str2)[2])))) +#endif /* TOKSCMAX == 3 */ +#ifndef toksceq +# define toksceq(str1, str2, len) (!memcmp(str1, str2, (size_t)(len))) +#endif /* toksceq */ + +/* special character sequence list table entry */ +struct tokldef { + int tokltyp; /* token type corresponding to sequence */ + char toklstr[TOKSCMAX+1]; /* the text of the sequence */ +}; + +/* include path structure */ +struct tokpdef { + tokpdef *tokpnxt; /* next path in list */ + int tokplen; /* length of directory name */ + char tokpdir[1]; /* directory to search */ +}; + +/* #define symbol structure */ +struct tokdfdef { + tokdfdef *nxt; /* next symbol in the same hash chain */ + char *nm; /* name of the symbol */ + int len; /* length of the symbol */ + int explen; /* length of the expansion */ + char expan[1]; /* expansion buffer */ +}; + +/* #define hash table information */ +#define TOKDFHSHSIZ 64 +#define TOKDFHSHMASK 63 + +/* maximum #if nesting */ +#define TOKIFNEST 64 + +/* #if state */ +#define TOKIF_IF_YES 1 /* processing a true #if/#ifdef block */ +#define TOKIF_IF_NO 2 /* processing a false #if/#ifdef block */ +#define TOKIF_ELSE_YES 3 /* processing a true #else part */ +#define TOKIF_ELSE_NO 4 /* processing a false #else part */ + +/* maximum macro expansion nesting */ +#define TOKMACNEST 20 + +/* lexical analysis context */ +struct tokcxdef { + errcxdef *tokcxerr; /* error handling context */ + mcmcxdef *tokcxmem; /* cache manager context */ + struct dbgcxdef *tokcxdbg; /* debugger context */ + lindef *tokcxlin; /* line source */ + tokpdef *tokcxinc; /* head of include path list */ + toktdef *tokcxstab; /* current head of symbol table chain */ + void *tokcxscx; /* context for string storage callback functions */ + ushort (*tokcxsst)(void *ctx); + /* start storing a string; return offset of string's storage */ + void (*tokcxsad)(void *ctx, char *str, ushort len); + /* add characters to a string */ + void (*tokcxsend)(void *ctx); /* finish storing string */ + char *tokcxmsav[TOKMACNEST]; /* saved positions for macro expansion */ + ushort tokcxmsvl[TOKMACNEST]; /* saved lengths for macro expansion */ + int tokcxmlvl; /* macro nesting level */ + int tokcxflg; /* flags */ +# define TOKCXFINMAC 0x01 /* doing <<expr>> macro expansion */ +# define TOKCXCASEFOLD 0x02 /* fold upper and lower case */ +# define TOKCXFCMODE 0x04 /* parse using C operators */ +# define TOKCXF_EMBED_PAREN_PRE 0x08 /* embedded expr - did '(' */ +# define TOKCXF_EMBED_PAREN_AFT 0x10 /* embedded expr - must do ')' */ +# define TOKCXFLIN2 0x20 /* new-style line records */ + tokdef tokcxcur; /* current token */ + char *tokcxbuf; /* buffer for long lines */ + ushort tokcxbsz; /* size of long line buffer */ + char *tokcxptr; /* pointer into line source */ + ushort tokcxlen; /* length of text in buffer */ + uchar tokcxinx[256]; /* special character indices */ + tokdfdef *tokcxdf[TOKDFHSHSIZ]; /* hash table for #define symbols */ + int tokcxifcnt; /* number of #endif's we expect to find */ + char tokcxif[TOKIFNEST]; /* #if state for each nesting level */ + int tokcxifcur; /* current #if state, obeying nesting */ + struct linfdef *tokcxhdr; /* list of previously included headers */ + tokscdef *tokcxsc[1]; /* special character table */ +}; + + +/* allocate and initialize a lexical analysis context */ +tokcxdef *tokcxini(errcxdef *errctx, mcmcxdef *mctx, tokldef *sctab); + +/* add an include path to a token handling context */ +void tokaddinc(tokcxdef *ctx, char *path, int pathlen); + +/* compute the hash value of a string */ +uint tokhsh(char *nam); + +/* + * Fold case of a token if we're in case-insensitive mode. This should + * be called any time a token is constructed artificially; it need not + * be used the token is read through the tokenizer, because the + * tokenizer will always adjust a token as needed before returning it. + */ +void tok_case_fold(tokcxdef *ctx, tokdef *tok); + +/* initialize a hashed symbol table */ +void tokthini(errcxdef *errctx, mcmcxdef *memctx, toktdef *toktab1); + +/* add a symbol to a hashed symbol table */ +void tokthadd(toktdef *toktab, char *name, int namel, + int typ, int val, int hash); + +/* update a symbol in a hashed symbol table */ +void tokthset(toktdef *toktab, toksdef *sym); + +/* search a hashed symbol table for a symbol */ +int tokthsea(toktdef *tab, char *name, int namel, int hash, + toksdef *ret); + +/* call a function for each symbol in a hashed symbol table */ +void toktheach(toktdef *tab, void (*cb)(void *ctx, toksdef *sym), + void *ctx); + +/* find a symbol given type and value */ +int tokthfind(toktdef *tab, int typ, uint val, toksdef *sym); + +/* initialize a linear symbol table */ +void toktlini(errcxdef *errctx, toktldef *toktab, + uchar *mem, uint siz); + +/* add a symbol to a linear symbol table */ +void toktladd(toktdef *toktab, char *name, int namel, + int typ, int val, int hash); + +/* search a linear symbol table */ +int toktlsea(toktdef *tab, char *name, int namel, int hash, + toksdef *ret); + +/* update a symbol in a linear symbol table */ +void toktlset(toktdef *toktab, toksdef *sym); + +/* call a function for each symbol in a local symbol table */ +void toktleach(toktdef *tab, void (*cb)(void *ctx, toksdef *sym), + void *ctx); + +/* delete all symbols from a linear table */ +void toktldel(toktldef *tab); + +/* get next token, removing it from input stream */ +int toknext(tokcxdef *ctx); + +/* general function to get/peek at next token */ +int tokget1(tokcxdef *ctx, tokdef *tok, int consume); + +/* add a symbol to the #define symbol table */ +void tok_add_define(tokcxdef *ctx, char *sym, int len, + char *expan, int explen); + +/* + * add a symbol to the #define symbol table, folding case if we're + * operating in case-insensitive mode + */ +void tok_add_define_cvtcase(tokcxdef *ctx, char *sym, int len, + char *expan, int explen); + +/* add a symbol to the #define symbol table as a number */ +void tok_add_define_num_cvtcase(tokcxdef *ctx, char *sym, int len, int num); + +/* undefine a #define symbol */ +void tok_del_define(tokcxdef *ctx, char *sym, int len); + +/* read/write preprocessor symbols from/to a file */ +void tok_read_defines(tokcxdef *ctx, osfildef *fp, errcxdef *ec); + +/* write preprocessor state to a file */ +void tok_write_defines(tokcxdef *ctx, osfildef *fp, errcxdef *ec); + + +/* determine if a char is a valid non-initial character in a symbol name */ +#define TOKISSYM(c) \ + (isalpha((uchar)(c)) || isdigit((uchar)(c)) || (c)=='_' || (c)=='$') + +/* numeric conversion and checking macros */ +#define TOKISHEX(c) \ + (isdigit((uchar)(c))||((c)>='a'&&(c)<='f')||((c)>='A'&&(c)<='F')) +#define TOKISOCT(c) \ + (isdigit((uchar)(c))&&!((c)=='8'||(c)=='9')) + +#define TOKHEX2INT(c) \ + (isdigit((uchar)c)?(c)-'0':((c)>='a'?(c)-'a'+10:(c)-'A'+10)) +#define TOKOCT2INT(c) ((c)-'0') +#define TOKDEC2INT(c) ((c)-'0') + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/types.h b/engines/glk/tads/tads2/types.h deleted file mode 100644 index 5da759fda7..0000000000 --- a/engines/glk/tads/tads2/types.h +++ /dev/null @@ -1,190 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifndef GLK_TADS_TADS2_TYPES -#define GLK_TADS_TADS2_TYPES - -#include "common/stream.h" - -namespace Glk { -namespace TADS { -namespace TADS2 { - -typedef unsigned char uchar; -typedef Common::SeekableReadStream osfildef; - -/** - * Allocate a memory block - */ -#define mchalo(CTX, SIZE, COMMENT) ((byte *)new byte[SIZE]) - -/** - * Free a memory block - */ -#define mchfre(PTR) delete[] (byte *)PTR - -/** - * Close a file - */ -#define osfcls(FP) delete FP - -/** - * Read from a file - */ -#define osfrb(FP, BUF, SIZE) FP->read(BUF, SIZE) - -/** - * Read a 16-bit integer from memory - */ -#define osrp2(MEM) READ_LE_UINT16(MEM) - -/** - * The character (or characters) which mark the beginning of a special fileref string. - * The important thing is that the string be one that is either not allowed in - * filenames on your platform or is unlikely to be the first part of a filename. - */ -#define OSS_FILEREF_STRING_PREFIX ":" - -/** - * The character (or characters) which mark the end of a special fileref string. - * Using this and OSS_FILEREF_STRING_PREFIX, you should be able to come up with - * something which forms an invalid filename - */ -#define OSS_FILEREF_STRING_SUFFIX "" - -/** - * Maximum length of status line text - */ -#define OSS_STATUS_STRING_LEN 80 - -/** - * Maximum size for filenames - */ -#define OSFNMAX 1024 - -/** - * Important note: do not change these values when porting TADS. These - * values can be used by games, so they must be the same on all platforms. - */ -enum { - OS_AFP_OPEN = 1, ///< choose an existing file to open for reading - OS_AFP_SAVE = 2 ///< choose a filename for saving to a file -}; - -/** - * File types.These type codes are used when opening or creating a file, - * so that the OS routine can set appropriate file system metadata - * to describe or find the file type. - * - * The type os_filetype_t is defined for documentary purposes; it's - * always just an int. - */ -enum os_filetype_t { - OSFTGAME = 0, ///< a game data file (.gam) - OSFTSAVE = 1, ///< a saved game (.sav) - OSFTLOG = 2, ///< a transcript (log) file - OSFTSWAP = 3, ///< swap file - OSFTDATA = 4, ///< user data file (used with the TADS fopen() call) - OSFTCMD = 5, ///< QA command/log file - OSFTERRS = 6, ///< error message file - OSFTTEXT = 7, ///< text file - used for source files - OSFTBIN = 8, ///< binary file of unknown type - resources, etc - OSFTCMAP = 9, ///< character mapping file - OSFTPREF = 10, ///< preferences file - OSFTUNK = 11, ///< unknown - as a filter, matches any file type - OSFTT3IMG = 12, ///< T3 image file (.t3 - formerly .t3x) - OSFTT3OBJ = 13, ///< T3 object file (.t3o) - OSFTT3SYM = 14, ///< T3 symbol export file (.t3s) - OSFTT3SAV = 15 ///< T3 saved state file (.t3v) -}; - -/** - * Constants for os_getc() when returning commands. When used for command line - * editing, special keys (arrows, END, etc.) should cause os_getc() to return 0, - * and return the appropriate CMD_ value on the NEXT call. Hence, os_getc() must - * keep the appropriate information around statically for the next call when a - * command key is issued. - * - * The comments indicate which CMD_xxx codes are "translated" codes and which are - * "raw"; the difference is that, when a particular keystroke could be interpreted - * as two different CMD_xxx codes, one translated and the other raw, os_getc() - * should always return the translated version of the key, and os_getc_raw() - * should return the raw version. - */ -enum KeyCmd { - CMD_UP = 1, ///< move up/up arrow (translated) - CMD_DOWN = 2, ///< move down/down arrow (translated) - CMD_RIGHT = 3, ///< move right/right arrow (translated) - CMD_LEFT = 4, ///< move left/left arrow (translated) - CMD_END = 5, ///< move cursor to end of line (translated) - CMD_HOME = 6, ///< move cursor to start of line (translated) - CMD_DEOL = 7, ///< delete to end of line (translated) - CMD_KILL = 8, ///< delete entire line (translated) - CMD_DEL = 9, ///< delete current character (translated) - CMD_SCR = 10, ///< toggle scrollback mode (translated) - CMD_PGUP = 11, ///< page up (translated) - CMD_PGDN = 12, ///< page down (translated) - CMD_TOP = 13, ///< top of file (translated) - CMD_BOT = 14, ///< bottom of file (translated) - CMD_F1 = 15, ///< function key F1 (raw) - CMD_F2 = 16, ///< function key F2 (raw) - CMD_F3 = 17, ///< function key F3 (raw) - CMD_F4 = 18, ///< function key F4 (raw) - CMD_F5 = 19, ///< function key F5 (raw) - CMD_F6 = 20, ///< function key F6 (raw) - CMD_F7 = 21, ///< function key F7 (raw) - CMD_F8 = 22, ///< function key F8 (raw) - CMD_F9 = 23, ///< function key F9 (raw) - CMD_F10 = 24, ///< function key F10 (raw) - CMD_CHOME = 25, ///< control-home (raw) - CMD_TAB = 26, ///< tab (translated) - CMD_SF2 = 27, ///< shift-F2 (raw) - ///< not used (obsolete) - 28 - CMD_WORD_LEFT = 29, ///< word left (ctrl-left on dos) (translated) - CMD_WORD_RIGHT = 30,///< word right (ctrl-right on dos) (translated) - CMD_WORDKILL = 31, ///< delete word right (translated) - CMD_EOF = 32, ///< end-of-file (raw) - CMD_BREAK = 33, ///< break (Ctrl-C or local equivalent) (translated) - CMD_INS = 34, ///< insert key (raw) - - /** - * ALT-keys - add alphabetical code to CMD_ALT: ALT-A == CMD_ALT + 0, - * ALT-B == CMD_ALT + 1, ALT-C == CMD_ALT + 2, etc - * - * These keys are all raw (untranslated). - */ - CMD_ALT = 128 ///< start of ALT keys -}; - -/** - * Status mode codes - */ -enum StatusMode { - OSS_STATUS_MODE_STORY = 0, - OSS_STATUS_MODE_STATUS = 1 -}; - -} // End of namespace TADS2 -} // End of namespace TADS -} // End of namespace Glk - -#endif diff --git a/engines/glk/tads/tads2/vocabulary.cpp b/engines/glk/tads/tads2/vocabulary.cpp index 4647b1bd30..e20bbe3de8 100644 --- a/engines/glk/tads/tads2/vocabulary.cpp +++ b/engines/glk/tads/tads2/vocabulary.cpp @@ -20,6 +20,7 @@ * */ +#include "glk/tads/tads2/run.h" #include "glk/tads/tads2/vocabulary.h" namespace Glk { diff --git a/engines/glk/tads/tads2/vocabulary.h b/engines/glk/tads/tads2/vocabulary.h index b4079932a5..5574675aa2 100644 --- a/engines/glk/tads/tads2/vocabulary.h +++ b/engines/glk/tads/tads2/vocabulary.h @@ -24,19 +24,774 @@ * Defines TADS vocabulary (player command parser) functionality */ -#ifndef GLK_TADS_TADS2_OS -#define GLK_TADS_TADS2_OS +#ifndef GLK_TADS_TADS2_VOCABULARY +#define GLK_TADS_TADS2_VOCABULARY -#include "glk/tads/tads2/types.h" +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/object.h" +#include "glk/tads/tads2/property.h" +#include "glk/tads/tads2/run.h" namespace Glk { namespace TADS { namespace TADS2 { -// TODO: Rest of vocabulary stuff +/* + * Cover macro for parser errors. Any parser error should be covered + * with this macro for documentation and search purposes. (The macro + * doesn't do anything - this is just something to search for when we're + * trying to enumerate parser error codes.) + */ +#define VOCERR(errcode) errcode + +/* maximum number of objects matching an ambiguous word */ +#define VOCMAXAMBIG 200 + +/* size of input buffer */ +#define VOCBUFSIZ 128 + +/* + * Vocabulary relation structure - this structure relates a vocabulary + * word to an object and part of speech. A list of these structures is + * attached to each vocabulary word structure to provide the word's + * meanings. + */ +struct vocwdef { + uint vocwnxt; /* index of next vocwdef attached to the same word */ + objnum vocwobj; /* object associated with the word */ + uchar vocwtyp; /* property associated with the word (part of speech) */ + uchar vocwflg; /* flags for the word */ +#define VOCFCLASS 1 /* word is for a class object */ +#define VOCFINH 2 /* word is inherited from a superclass */ +#define VOCFNEW 4 /* word was added at run-time */ +#define VOCFDEL 8 /* word has been deleted */ +}; + +/* vocabulary word structure */ +struct vocdef { + vocdef *vocnxt; /* next word at same hash value */ + uchar voclen; /* length of the word */ + uchar vocln2; /* length of second word (0 if no second word) */ + uint vocwlst; /* head of list of vocwdef's attached to the word */ + uchar voctxt[1]; /* text of the word */ +}; + +/* vocabulary inheritance cell */ +struct vocidef { + uchar vocinsc; /* # of superclasses (gives size of record) */ + union { + struct { + uchar vociusflg; /* flags for entry */ +#define VOCIFCLASS 1 /* entry refers to a class object (loc records only) */ +#define VOCIFVOC 2 /* entry has vocabulary words defined */ +#define VOCIFXLAT 4 /* superclasses must be translated from portable fmt */ +#define VOCIFLOCNIL 8 /* location is explicitly set to nil */ +#define VOCIFNEW 16 /* object was allocated at run-time with "new" */ + objnum vociusloc; /* location of the object */ + objnum vociusilc; /* inherited location */ + objnum vociussc[1]; /* array of superclasses */ + } vocius; + vocidef *vociunxt; + } vociu; +#define vociflg vociu.vocius.vociusflg +#define vociloc vociu.vocius.vociusloc +#define vociilc vociu.vocius.vociusilc +#define vocisc vociu.vocius.vociussc +#define vocinxt vociu.vociunxt +}; + +/* size of a page in a vocabulary pool */ +#define VOCPGSIZ 8192 + +/* number of bytes in an inheritance cell page */ +#define VOCISIZ 8192 + +/* maximum number of inheritance pages */ +#define VOCIPGMAX 32 + +/* maximum number of inheritance pages (256 objects per page) */ +#define VOCINHMAX 128 + +/* size of vocabulary hash table */ +#define VOCHASHSIZ 256 + +/* size of a template structure */ #define VOCTPLSIZ 10 + +/* new-style template structure */ #define VOCTPL2SIZ 16 + +/* + * vocwdef's are fixed in size. They're allocated in a set of arrays + * (the voccxwp member of the voc context has the list of arrays). Each + * array is of a fixed number of vocwdef entries; a maximum number of + * vocwdef arrays is possible. + */ +#define VOCWPGSIZ 2000 /* number of vocwdef's per array */ +#define VOCWPGMAX 16 /* maximum number of vocwdef arrays */ + +/* + * To find a vocwdef entry given its index, divide the index by the + * number of entries per array to find the array number, and use the + * remainder to find the index within that array. + */ +/*#define VOCW_IN_CACHE*/ +#ifdef VOCW_IN_CACHE +vocwdef *vocwget(struct voccxdef *ctx, uint idx); +#else +#define vocwget(ctx, idx) \ + ((idx) == VOCCXW_NONE ? (vocwdef *)0 : \ + ((ctx)->voccxwp[(idx)/VOCWPGSIZ] + ((idx) % VOCWPGSIZ))) +#endif + +/* + * Special values for vocdtim - these values indicate that the daemon + * does not have a normal turn-based expiration time. + */ +#define VOCDTIM_EACH_TURN 0xffff /* the daemon fires every turn */ + +/* daemon/fuse/alarm slot */ +struct vocddef { + objnum vocdfn; /* object number of function to be invoked */ + runsdef vocdarg; /* argument for daemon/fuse function */ + prpnum vocdprp; /* property number (used only for alarms) */ + uint vocdtim; /* time for fuses/alarms (0xffff -> each-turn alarm) */ +}; + +/* vocabulary object list entry */ +struct vocoldef { + objnum vocolobj; /* object matching the word */ + char *vocolfst; /* first word in cmd[] that identified object */ + char *vocollst; /* last word in cmd[] that identified object */ + char *vocolhlst; /* hypothetical last word, if we trimmed a prep */ + int vocolflg; /* special flags (ALL, etc) */ +}; + +/* vocabulary context */ +struct voccxdef { + errcxdef *voccxerr; /* error handling context */ + tiocxdef *voccxtio; /* text i/o context */ + runcxdef *voccxrun; /* execution context */ + mcmcxdef *voccxmem; /* memory manager context */ + objucxdef *voccxundo; /* undo context */ + uchar *voccxpool; /* next free byte in vocdef pool */ + vocdef *voccxfre; /* head of vocdef free list */ + char *voccxcpp; /* pointer to compound word area */ + int voccxcpl; /* length of compound word area */ + char *voccxspp; /* pointer to special word area */ + int voccxspl; /* length of special word area */ + uint voccxrem; /* number of bytes remaining in vocdef pool */ + vocidef **voccxinh[VOCINHMAX]; /* vocidef page table: 256 per page */ + uchar *voccxip[VOCIPGMAX]; /* inheritance cell pool */ + vocidef *voccxifr; /* head of inheritance cell free list */ + uint voccxiplst; /* last inheritance cell page allocated */ + uint voccxilst; /* next unused byte in last inheritance page */ + int voccxredo; /* flag: redo command in buffer */ + + /* + * redo buffer - if voccxredo is set, and this buffer is not empty, + * we'll redo the command in this buffer rather than the one in our + * internal stack buffer + */ + char voccxredobuf[VOCBUFSIZ]; + + /* + * "again" buffer - when we save the last command for repeating via + * the "again" command, we'll save the direct and indirect object + * words here, so that they can be recovered if "again" is used + */ + char voccxagainbuf[VOCBUFSIZ]; + + vocdef *voccxhsh[VOCHASHSIZ]; /* hash table */ + +#ifdef VOCW_IN_CACHE + mcmon voccxwp[VOCWPGMAX]; /* list of pages of vocab records */ + mcmon voccxwplck; /* locked page of vocab records */ + vocwdef *voccxwpgptr; /* pointer to currently locked page */ +#else + vocwdef *voccxwp[VOCWPGMAX]; /* vocabulary word pool */ +#endif + + uint voccxwalocnt; /* number of vocwdef's used so far */ + uint voccxwfre; /* index of first vocwdef in free list */ +#define VOCCXW_NONE ((uint)(-1)) /* index value indicating end of list */ + + vocddef *voccxdmn; /* array of daemon slots */ + uint voccxdmc; /* number of slots in daemon array */ + vocddef *voccxfus; /* array of fuse slots */ + uint voccxfuc; /* number of slots in fuse array */ + vocddef *voccxalm; /* array of alarm slots */ + uint voccxalc; /* number of slots in alarm array */ + char voccxtim[26]; /* game's timestamp (asctime value) */ + + objnum voccxvtk; /* object number of "take" deepverb */ + objnum voccxme; /* object number of "Me" actor */ + objnum voccxme_init; /* initial setting of "Me" */ + objnum voccxstr; /* object number of "strObj" */ + objnum voccxnum; /* object number of "numObj" */ + objnum voccxit; /* last "it" value */ + objnum voccxhim; /* last "him" value */ + objnum voccxher; /* last "her" value */ + objnum voccxthc; /* count of items in "them" list */ + objnum voccxthm[VOCMAXAMBIG]; /* list of items in "them" */ + objnum voccxprd; /* "pardon" function object number */ + objnum voccxpre; /* "preparse" function object number */ + objnum voccxppc; /* "preparseCmd" function object number */ + objnum voccxpre2; /* "preparseExt" function object number */ + objnum voccxvag; /* "again" verb object */ + objnum voccxini; /* "init" function */ + objnum voccxper; /* "parseError" function object number */ + objnum voccxprom; /* "cmdPrompt" function object number */ + objnum voccxpostprom; /* "cmdPostPrompt" function object number */ + objnum voccxpdis; /* parseDisambig function */ + objnum voccxper2; /* parseError2 function */ + objnum voccxperp; /* parseErrorParam function */ + objnum voccxpdef; /* parseDefault function */ + objnum voccxpdef2; /* parseDefaultExt function */ + objnum voccxpask; /* parseAskobj function */ + objnum voccxpask2; /* parseAskobjActor function */ + objnum voccxpask3; /* parseAskobjIndirect function */ + objnum voccxinitrestore; /* "initRestore" function object number */ + objnum voccxpuv; /* parseUnknownVerb function object number */ + objnum voccxpnp; /* parseNounPhrase function object number */ + objnum voccxpostact; /* postAction function object number */ + objnum voccxprecmd; /* preCommand function object number */ + objnum voccxendcmd; /* endCommand function object number */ + + /* current command word list values */ + vocoldef *voccxdobj; /* current direct object word list */ + vocoldef *voccxiobj; /* current indirect object word list */ + + /* current command objects */ + objnum voccxactor; /* current actor */ + objnum voccxverb; /* current command deepverb */ + objnum voccxprep; /* current command preposition */ + + /* previous command values - used by "again" */ + objnum voccxlsa; /* previous actor */ + objnum voccxlsv; /* previous verb */ + vocoldef voccxlsd; /* previous direct object */ + vocoldef voccxlsi; /* previous indirect object */ + objnum voccxlsp; /* preposition */ + int voccxlssty; /* style (new/old) of last template */ + uchar voccxlst[VOCTPL2SIZ]; /* template */ + + objnum voccxpreinit; /* preinit function */ + + /* special flags */ + uchar voccxflg; +#define VOCCXFCLEAR 1 /* ignore remainder of command line (restore) */ +#define VOCCXFVWARN 2 /* generate redundant verb warnings */ +#define VOCCXFDBG 4 /* debug mode: show parsing information */ +#define VOCCXAGAINDEL 8 /* "again" lost due to object deletion */ + + /* number of remaining unresolved unknown words in the command */ + int voccxunknown; + + /* total number of unresolved words in the last command */ + int voccxlastunk; + + /* parser stack area */ + uchar *voc_stk_ptr; + uchar *voc_stk_cur; + uchar *voc_stk_end; +}; + +/* allocate and push a list, returning a pointer to the list's memory */ +uchar *voc_push_list_siz(voccxdef *ctx, uint lstsiz); + +/* push a list of objects from a vocoldef array */ +void voc_push_vocoldef_list(voccxdef *ctx, vocoldef *objlist, int cnt); + +/* push a list of objects from an objnum array */ +void voc_push_objlist(voccxdef *ctx, objnum objlist[], int cnt); + +/* change the player character ("Me") object */ +void voc_set_me(voccxdef *ctx, objnum new_me); + +/* add a vocabulary word */ +void vocadd(voccxdef *ctx, prpnum p, objnum objn, + int classflag, char *wrdval); + +/* internal addword - must already be split into two words and lengths */ +void vocadd2(voccxdef *ctx, prpnum p, objnum objn, int classflg, + uchar *wrd1, int len1, uchar *wrd2, int len2); + +/* delete vocabulary for a given object */ +void vocdel(voccxdef *ctx, objnum objn); + +/* lower-level vocabulary deletion routine */ +void vocdel1(voccxdef *ctx, objnum objn, char *wrd, prpnum prp, + int really_delete, int revert, int keep_undo); + +/* delete all inherited vocabulary */ +void vocdelinh(voccxdef *ctx); + +/* allocate space for an inheritance record if needed */ +void vocialo(voccxdef *ctx, objnum obj); + +/* add an inheritance/location record */ +void vociadd(voccxdef *ctx, objnum obj, objnum loc, + int numsc, objnum *sc, int flags); + +/* delete inheritance records for an object */ +void vocidel(voccxdef *ctx, objnum chi); + +/* renumber an object's inheritance records - used for 'modify' */ +void vociren(voccxdef *ctx, objnum oldnum, objnum newnum); + +/* caller-provided context structure for vocffw/vocfnw searches */ +struct vocseadef { + vocdef *v; + vocwdef *vw; + uchar *wrd1; + int len1; + uchar *wrd2; + int len2; +}; + +/* find first word matching a given word */ +vocwdef *vocffw(voccxdef *ctx, char *wrd, int len, char *wrd2, int len2, + int p, vocseadef *search_ctx); + +/* find next word */ +vocwdef *vocfnw(voccxdef *voccx, vocseadef *search_ctx); + +/* read a line of input text */ +int vocread(voccxdef *ctx, objnum actor, objnum verb, + char *buf, int bufl, int type); +#define VOCREAD_OK 0 +#define VOCREAD_REDO 1 + +/* compute size of a vocoldef list */ +int voclistlen(vocoldef *lst); + +/* tokenize an input buffer */ +int voctok(voccxdef *ctx, char *cmd, char *outbuf, + char **wrd, int lower, int cvt_ones, int show_errors); + +/* get types for a word list */ +int vocgtyp(voccxdef *ctx, char **cmd, int *types, char *orgbuf); + +/* execute a player command */ +int voccmd(voccxdef *ctx, char *cmd, uint cmdlen); + +/* disambiguator */ +int vocdisambig(voccxdef *ctx, vocoldef *outlist, vocoldef *inlist, + prpnum defprop, prpnum accprop, prpnum verprop, + char *cmd[], objnum otherobj, objnum cmdActor, + objnum cmdVerb, objnum cmdPrep, char *cmdbuf, + int silent); + +/* display a multiple-object prefix */ +void voc_multi_prefix(voccxdef *ctx, objnum objn, + int show_prefix, int multi_flags, + int cur_index, int count); + +/* low-level executor */ +int execmd(voccxdef *ctx, objnum actor, objnum prep, + char *vverb, char *vprep, vocoldef *dolist, vocoldef *iolist, + char **cmd, int *typelist, + char *cmdbuf, int wrdcnt, uchar **preparse_list, int *next_start); + +/* recursive command execution */ +int execmd_recurs(voccxdef *ctx, objnum actor, objnum verb, + objnum dobj, objnum prep, objnum iobj, + int validate_dobj, int validate_iobj); + +/* try running preparseCmd user function */ +int try_preparse_cmd(voccxdef *ctx, char **cmd, int wrdcnt, + uchar **preparse_list); + +/* + * Handle an unknown verb or sentence structure. We'll call this when + * we encounter a sentence where we don't know the verb word, or we + * don't know the combination of verb and verb preposition, or we don't + * recognize the sentence structure (for example, an indirect object is + * present, but we don't have a template defined using an indirect + * object for the verb). + * + * 'wrdcnt' is the number of words in the cmd[] array. If wrdcnt is + * zero, we'll automatically count the array entries, with the end of + * the array indicated by a null pointer entry. + * + * If do_fuses is true, we'll execute the fuses and daemons if the + * function exists and doesn't throw an ABORT error, or any other + * run-time error other than EXIT. + * + * This function calls the game-defined function parseUnknownVerb, if it + * exists. If the function doesn't exist, we'll simply display the + * given error message, using the normal parseError mechanism. The + * function should use "abort" or "exit" if it wants to cancel further + * processing of the command. + * + * We'll return true if the function exists and executes successfully, + * in which case normal processing should continue with any remaining + * command on the command line. We'll return false if the function + * doesn't exist or throws an error other than EXIT, in which case the + * remainder of the command should be aborted. + */ +int try_unknown_verb(voccxdef *ctx, objnum actor, + char **cmd, int *typelist, int wrdcnt, int *next_start, + int do_fuses, int err, char *msg, ...); + +/* find a template */ +int voctplfnd(voccxdef *ctx, objnum verb_in, objnum prep, + uchar *tplout, int *newstyle); + +/* build a printable name for an object from the words in a command list */ +void voc_make_obj_name(voccxdef *ctx, char *namebuf, char *cmd[], + int firstwrd, int lastwrd); +void voc_make_obj_name_from_list(voccxdef *ctx, char *namebuf, + char *cmd[], char *firstwrd, char *lastwrd); + +/* + * check noun - determines whether the next set of words is a valid noun + * phrase. No complaint is issued if not; this check is generally made + * to figure out what type of sentence we're dealing with. This is + * simple; we just call vocgobj() with the complaint flag turned off. + */ +/* int vocchknoun(voccxdef *ctx, char **cmd, int *typelist, int cur, + int *next, vocoldef *nounlist, int chkact); */ +#define vocchknoun(ctx, cmd, typelist, cur, next, nounlist, chkact) \ + vocgobj(ctx, cmd, typelist, cur, next, FALSE, nounlist, TRUE, chkact, 0) +#define vocchknoun2(ctx, cmd, typlst, cur, next, nounlist, chkact, nomatch) \ + vocgobj(ctx, cmd, typlst, cur, next, FALSE, nounlist, TRUE, chkact, nomatch) + +/* + * get noun - reads an object list. We simply call vocgobj() with the + * complaint and multiple-noun flags turned on. + */ +/* int vocgetnoun(voccxdef *ctx, char **cmd, int *typelist, int cur, + int *next, vocoldef *nounlist); */ +#define vocgetnoun(ctx, cmd, typelist, cur, next, nounlist) \ + vocgobj(ctx, cmd, typelist, cur, next, TRUE, nounlist, TRUE, FALSE, 0) + +/* get object */ +int vocgobj(voccxdef *ctx, char **cmd, int *typelist, int cur, + int *next, int complain, vocoldef *nounlist, + int multi, int chkact, int *nomatch); + +/* tokenize a string - TADS program code interface */ +void voc_parse_tok(voccxdef *ctx); + +/* get token types - TADS program code interface */ +void voc_parse_types(voccxdef *ctx); + +/* get objects matching all of the given words - TADS program code interface */ +void voc_parse_dict_lookup(voccxdef *ctx); + +/* parse a noun list - TADS program code interface */ +void voc_parse_np(voccxdef *ctx); + +/* disambiguate a noun list - TADS program code interface */ +void voc_parse_disambig(voccxdef *ctx); + +/* replace the current command - TADS program code interface */ +void voc_parse_replace_cmd(voccxdef *ctx); + +/* check access to an object */ +int vocchkaccess(voccxdef *ctx, objnum obj, prpnum verprop, + int seqno, objnum actor, objnum verb); + +/* check to see if an object is visible */ +int vocchkvis(voccxdef *ctx, objnum obj, objnum cmdActor); + +/* display an appropriate message for an unreachable object */ +void vocnoreach(voccxdef *ctx, objnum *list1, int cnt, + objnum actor, objnum verb, objnum prep, prpnum defprop, + int show_multi_prefix, int multi_flags, + int multi_base_index, int multi_total_count); + +/* set {numObj | strObj}.value, as appropriate */ +void vocsetobj(voccxdef *ctx, objnum obj, dattyp typ, void *val, + vocoldef *inobj, vocoldef *outobj); + +/* macros to read values out of templates */ +#define voctplpr(tpl) ((objnum)osrp2(((uchar *)tpl))) /* preposition */ +#define voctplvi(tpl) ((prpnum)osrp2(((uchar *)tpl) + 2)) /* verIoVerb */ +#define voctplio(tpl) ((prpnum)osrp2(((uchar *)tpl) + 4)) /* ioVerb */ +#define voctplvd(tpl) ((prpnum)osrp2(((uchar *)tpl) + 6)) /* verDoVerb */ +#define voctpldo(tpl) ((prpnum)osrp2(((uchar *)tpl) + 8)) /* doVerb */ +#define voctplflg(tpl) (*(((uchar *)tpl) + 10)) /* flags */ + +/* flag values for the voctplflg */ +#define VOCTPLFLG_DOBJ_FIRST 0x01 /* disambiguate direct object first */ + + +/* word type flags */ +#define VOCT_ARTICLE 1 +#define VOCT_ADJ 2 +#define VOCT_NOUN 4 +#define VOCT_PREP 8 +#define VOCT_VERB 16 +#define VOCT_SPEC 32 /* special words - "of", ",", ".", etc. */ +#define VOCT_PLURAL 64 +#define VOCT_UNKNOWN 128 /* word is unknown */ + +/* special type flags */ +#define VOCS_ALL 1 /* "all" */ +#define VOCS_EXCEPT 2 /* "except" */ +#define VOCS_IT 4 /* "it" */ +#define VOCS_THEM 8 /* "them" */ +#define VOCS_NUM 16 /* a number */ +#define VOCS_COUNT 32 /* a number being used as a count */ +#define VOCS_PLURAL 64 /* plural */ +#define VOCS_ANY 128 /* "any" */ +#define VOCS_HIM 256 /* "him" */ +#define VOCS_HER 512 /* "her" */ +#define VOCS_STR 1024 /* a quoted string */ +#define VOCS_UNKNOWN 2048 /* noun phrase contains an unknown word */ +#define VOCS_ENDADJ 4096 /* word matched adjective at end of phrase */ +#define VOCS_TRUNC 8192 /* truncated match - word is leading substring */ +#define VOCS_TRIMPREP 16384 /* trimmed prep phrase: assumed it was for verb */ + +/* special internally-defined one-character word flags */ +#define VOCW_AND ',' +#define VOCW_THEN '.' +#define VOCW_OF 'O' +#define VOCW_ALL 'A' +#define VOCW_BOTH 'B' +#define VOCW_IT 'I' +#define VOCW_HIM 'M' +#define VOCW_ONE 'N' +#define VOCW_ONES 'P' +#define VOCW_HER 'R' +#define VOCW_THEM 'T' +#define VOCW_BUT 'X' +#define VOCW_ANY 'Y' + +/* structure for special internal word table */ +struct vocspdef { + char *vocspin; + char vocspout; +}; + +/* check if a word is a special word - true if word is given special word */ +/* int vocspec(char *wordptr, int speccode); */ +#define vocspec(w, s) (*(w) == (s)) + +/* + * Set a fuse/daemon/notifier. + */ +void vocsetfd(voccxdef *ctx, vocddef *what, objnum func, prpnum prop, + uint tm, runsdef *val, int err); + +/* remove a fuse/daemon/notifier */ +void vocremfd(voccxdef *ctx, vocddef *what, objnum func, prpnum prop, + runsdef *val, int err); + +/* count a turn (down all fuse/notifier timers) */ +void vocturn(voccxdef *ctx, int turncnt, int do_fuses); + +/* initialize voc context */ +void vocini(voccxdef *vocctx, errcxdef *errctx, mcmcxdef *memctx, + runcxdef *runctx, objucxdef *undoctx, int fuses, + int daemons, int notifiers); + +/* clean up the voc context - frees memory allocated by vocini() */ +void vocterm(voccxdef *vocctx); + +/* allocate/free fuse/daemon/notifier array for voc ctx initialization */ +void vocinialo(voccxdef *ctx, vocddef **what, int cnt); +void voctermfree(vocddef *what); + +/* get a vocidef given an object number */ +/* vocidef *vocinh(voccxdef *ctx, objnum obj); */ +#define vocinh(ctx, obj) ((ctx)->voccxinh[(obj) >> 8][(obj) & 255]) + +/* revert all objects back to original state, using inheritance records */ +void vocrevert(voccxdef *ctx); + +/* clear all fuses/daemons/notifiers (useful for restarting) */ +void vocdmnclr(voccxdef *ctx); + +/* display a parser error message */ +void vocerr(voccxdef *ctx, int err, char *f, ...); + +/* + * display a parser informational error message - this will display the + * message whether or not we're suppressing messages due to unknown + * words, and should be used when providing information, such as objects + * we're assuming by default + */ +void vocerr_info(voccxdef *ctx, int err, char *f, ...); + +/* client undo callback - undoes a daemon/fuse/notifier */ +void vocdundo(void *ctx, uchar *data); + +/* client undo size figuring callback - return size of client undo record */ +ushort OS_LOADDS vocdusz(void *ctx, uchar *data); + +/* save undo for object creation */ +void vocdusave_newobj(voccxdef *ctx, objnum objn); + +/* save undo for adding a word */ +void vocdusave_addwrd(voccxdef *ctx, objnum objn, prpnum typ, int flags, + char *wrd); + +/* save undo for deleting a word */ +void vocdusave_delwrd(voccxdef *ctx, objnum objn, prpnum typ, int flags, + char *wrd); + +/* save undo for object deletion */ +void vocdusave_delobj(voccxdef *ctx, objnum objn); + +/* save undo for changing the "Me" object */ +void vocdusave_me(voccxdef *ctx, objnum old_me); + +/* compute vocabulary word hash value */ +uint vochsh(uchar *t, int len); + +/* TADS versions of isalpha, isspace, isdigit, etc */ +#define vocisupper(c) ((uchar)(c) <= 127 && isupper((uchar)(c))) +#define vocislower(c) ((uchar)(c) <= 127 && islower((uchar)(c))) +#define vocisalpha(c) ((uchar)(c) > 127 || isalpha((uchar)(c))) +#define vocisspace(c) ((uchar)(c) <= 127 && isspace((uchar)(c))) +#define vocisdigit(c) ((uchar)(c) <= 127 && isdigit((uchar)(c))) + + +/* + * Undo types for voc subsystem + */ +#define VOC_UNDO_DAEMON 1 /* fuse/daemon status change */ +#define VOC_UNDO_NEWOBJ 2 /* object creation */ +#define VOC_UNDO_DELOBJ 3 /* object deletion */ +#define VOC_UNDO_ADDVOC 4 /* add vocabulary to an object */ +#define VOC_UNDO_DELVOC 5 /* delete vocabulary from an object */ +#define VOC_UNDO_SETME 6 /* set the "Me" object */ + + +/* + * Our own stack. We need to allocate some fairly large structures + * (for the disambiguation lists, mostly) in a stack-like fashion, and + * we don't want to consume vast quantities of the real stack, because + * some machines have relatively restrictive limitations on stack usage. + * To provide some elbow room, we'll use a stack-like structure of our + * own: we'll allocate out of this structure as needed, and whenever we + * leave a C stack frame, we'll also leave our own stack frame. + */ + +/* re-initialize the stack, allocating space for it if needed */ +void voc_stk_ini(voccxdef *ctx, uint siz); + +/* enter a stack frame, marking our current position */ +#define voc_enter(ctx, marker) (*(marker) = (ctx)->voc_stk_cur) + +/* leave a stack frame, restoring the entry position */ +#define voc_leave(ctx, marker) ((ctx)->voc_stk_cur = marker) + +/* return a value */ +#define VOC_RETVAL(ctx, marker, retval) \ + voc_leave(ctx, marker); return retval + +/* allocate space from the stack */ +void *voc_stk_alo(voccxdef *ctx, uint siz); + +/* allocation cover macros */ +#define VOC_STK_ARRAY(ctx, typ, var, cnt) \ + (var = (typ *)voc_stk_alo(ctx, (uint)((cnt) * sizeof(typ)))) + +#define VOC_MAX_ARRAY(ctx, typ, var) \ + VOC_STK_ARRAY(ctx, typ, var, VOCMAXAMBIG) + +/* + * Stack size for the vocab stack. We'll scale our stack needs based + * on the size of the vocoldef structure, since this is the most common + * item to be allocated on the vocab stack. We'll also scale based on + * the defined VOCMAXAMBIG parameter, since it is the number of elements + * usually allocated. The actual amount of space needed depends on how + * the functions in vocab.c and execmd.c work, so this parameter may + * need to be adjusted for changes to the player command parser. + */ +#define VOC_STACK_SIZE (16 * VOCMAXAMBIG * sizeof(vocoldef)) + +/* + * Execute all fuses and daemons, then execute the endCommand user + * function. Returns zero on success, or ERR_ABORT if 'abort' was + * thrown during execution. This is a convenient cover single function + * to do all end-of-turn processing; this calls exefuse() and exedaem() + * as needed, trapping any 'abort' or 'exit' errors that occur. + * + * If 'do_fuses' is true, we'll run fuses and daemons. Otherwise, + */ +int exe_fuses_and_daemons(voccxdef *ctx, int err, int do_fuses, + objnum actor, objnum verb, + vocoldef *dobj_list, int do_cnt, + objnum prep, objnum iobj); + +/* + * Execute any pending fuses. Return TRUE if any fuses were executed, + * FALSE otherwise. + */ +int exefuse(voccxdef *ctx, int do_run); + +/* + * Execute daemons + */ +void exedaem(voccxdef *ctx); + +/* + * Get the number and size of words defined for an object. The size + * returns the total byte count from all the words involved. Do not + * include deleted words in the count. + */ +void voc_count(voccxdef *ctx, objnum objn, prpnum prp, int *cnt, int *siz); + +/* + * Iterate through all words for a particular object, calling a + * function with each vocwdef found. If objn == MCMONINV, we'll call + * the callback for every word. + */ +void voc_iterate(voccxdef *ctx, objnum objn, + void (*fn)(void *, vocdef *, vocwdef *), void *fnctx); + +/* ------------------------------------------------------------------------ */ +/* + * disambiguation status codes - used for disambigDobj and disambigIobj + * methods in the deepverb + */ + +/* continue with disambiguation process (using possibly updated list) */ +#define VOC_DISAMBIG_CONT 1 + +/* done - the list is fully resolved; return with (possibly updated) list */ +#define VOC_DISAMBIG_DONE 2 + +/* error - abort the command */ +#define VOC_DISAMBIG_ERROR 3 + +/* parse string returned in second element of list as interactive response */ +#define VOC_DISAMBIG_PARSE_RESP 4 + +/* already asked for an interactive response, but didn't read it yet */ +#define VOC_DISAMBIG_PROMPTED 5 + + +/* ------------------------------------------------------------------------ */ +/* + * parseNounPhrase status codes + */ + +/* parse error occurred */ +#define VOC_PNP_ERROR 1 + +/* use built-in default parser */ +#define VOC_PNP_DEFAULT 2 + +/* successful parse */ +#define VOC_PNP_SUCCESS 3 + + +/* ------------------------------------------------------------------------ */ +/* + * parserResolveObjects usage codes + */ +#define VOC_PRO_RESOLVE_DOBJ 1 /* direct object */ +#define VOC_PRO_RESOLVE_IOBJ 2 /* indirect object */ +#define VOC_PRO_RESOLVE_ACTOR 3 /* actor */ + } // End of namespace TADS2 } // End of namespace TADS } // End of namespace Glk -- cgit v1.2.3