diff options
| author | Paul Gilbert | 2019-05-17 10:04:03 -1000 | 
|---|---|---|
| committer | Paul Gilbert | 2019-05-24 18:21:06 -0700 | 
| commit | 0279143a62c2cdab635894103da03b43eba7cf9c (patch) | |
| tree | fbc77cd007a4104204287825902601b6fb50bad1 | |
| parent | 54d240d81f8858f7ad694c690fcf738b3ec8b89d (diff) | |
| download | scummvm-rg350-0279143a62c2cdab635894103da03b43eba7cf9c.tar.gz scummvm-rg350-0279143a62c2cdab635894103da03b43eba7cf9c.tar.bz2 scummvm-rg350-0279143a62c2cdab635894103da03b43eba7cf9c.zip  | |
GLK: TADS2: Adding headers
49 files changed, 13987 insertions, 2884 deletions
diff --git a/engines/glk/module.mk b/engines/glk/module.mk index d84d542825..43c2523b80 100644 --- a/engines/glk/module.mk +++ b/engines/glk/module.mk @@ -92,13 +92,22 @@ MODULE_OBJS := \  	scott/detection.o \  	scott/scott.o \  	tads/detection.o \ +	tads/osfrobtads.o \  	tads/tads.o \ +	tads/tads2/character_map.o \  	tads/tads2/data.o \ -	tads/tads2/ler.o \ +	tads/tads2/error.o \ +	tads/tads2/error_handling.o \ +	tads/tads2/file_io.o \ +	tads/tads2/memory_cache.o \ +	tads/tads2/memory_cache_heap.o \ +	tads/tads2/memory_cache_loader.o \ +	tads/tads2/memory_cache_swap.o \  	tads/tads2/os.o \  	tads/tads2/regex.o \ +	tads/tads2/run.o \  	tads/tads2/tads2.o \ -	tads/tads2/tads2_cmap.o \ +	tads/tads2/tokenizer.o \  	tads/tads2/vocabulary.o \  	tads/tads3/tads3.o diff --git a/engines/glk/tads/os_filetype.h b/engines/glk/tads/os_filetype.h new file mode 100644 index 0000000000..93b101698f --- /dev/null +++ b/engines/glk/tads/os_filetype.h @@ -0,0 +1,64 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* TADS OS interface file type definitions + * + * Defines certain datatypes used in the TADS operating system interface + */ + +#ifndef GLK_TADS_OS_DATATYPE +#define GLK_TADS_OS_DATATYPE + +namespace Glk { +namespace TADS { + +/** + * File types.  These type codes are used when opening or creating a + *   file, so that the OS routine can set appropriate file system metadata + *   to describe or find the file type. + *    + *   The type os_filetype_t is defined for documentary purposes; it's + *   always just an int.   + */ +enum os_filetype_t { +	OSFTGAME  = 0,                               /* a game data file (.gam) */ +	OSFTSAVE  = 1,                                   /* a saved game (.sav) */ +	OSFTLOG   = 2,                               /* a transcript (log) file */ +	OSFTSWAP  = 3,                                             /* swap file */ +	OSFTDATA  = 4,      /* user data file (used with the TADS fopen() call) */ +	OSFTCMD   = 5,                                   /* QA command/log file */ +	OSFTERRS  = 6,                                    /* error message file */ +	OSFTTEXT  = 7,                     /* text file - used for source files */ +	OSFTBIN   = 8,          /* binary file of unknown type - resources, etc */ +	OSFTCMAP  = 9,                                /* character mapping file */ +	OSFTPREF  = 10,                                     /* preferences file */ +	OSFTUNK   = 11,         /* unknown - as a filter, matches any file type */ +	OSFTT3IMG = 12,                  /* T3 image file (.t3 - formerly .t3x) */ +	OSFTT3OBJ = 13,                                /* T3 object file (.t3o) */ +	OSFTT3SYM = 14,                         /* T3 symbol export file (.t3s) */ +	OSFTT3SAV = 15,                           /* T3 saved state file (.t3v) */ +}; + +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/osfrobtads.cpp b/engines/glk/tads/osfrobtads.cpp new file mode 100644 index 0000000000..fbcf1c1050 --- /dev/null +++ b/engines/glk/tads/osfrobtads.cpp @@ -0,0 +1,42 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/osfrobtads.h" +#include "common/file.h" + +namespace Glk { +namespace TADS { + +osfildef *osfoprb(const char *fname, os_filetype_t typ) { +	Common::File f; +	if (f.open(fname)) +		return f.readStream(f.size()); +	else +		return nullptr; +} + +int osfrb(osfildef *fp, void *buf, size_t count) { +	return fp->read(buf, count); +} + +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/osfrobtads.h b/engines/glk/tads/osfrobtads.h new file mode 100644 index 0000000000..2dce84c5ec --- /dev/null +++ b/engines/glk/tads/osfrobtads.h @@ -0,0 +1,267 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* OS-layer functions and macros. + * + * This file does not introduce any curses (or other screen-API) + * dependencies; it can be used for both the interpreter as well as the + * compiler. + */ + +#ifndef GLK_TADS_OSFROBTADS +#define GLK_TADS_OSFROBTADS + +#include "common/fs.h" +#include "common/stream.h" +#include "glk/glk_api.h" +#include "glk/tads/os_filetype.h" + +namespace Glk { +namespace TADS { + + +/* Defined for Gargoyle. */ +#define HAVE_STDINT_ + +#if 0 +#include "common.h" +#endif + +/* Used by the base code to inhibit "unused parameter" compiler warnings. */ +#ifndef VARUSED +#define VARUSED(var) (void)var +#endif + +/* We assume that the C-compiler is mostly ANSI compatible. */ +#define OSANSI + +/* Special function qualifier needed for certain types of callback + * functions.  This is for old 16-bit systems; we don't need it and + * define it to nothing. */ +#define OS_LOADDS + +/* Unices don't suffer the near/far pointers brain damage (thank God) so + * we make this a do-nothing macro. */ +#define osfar_t + +/* This is used to explicitly discard computed values (some compilers + * would otherwise give a warning like "computed value not used" in some + * cases).  Casting to void should work on every ANSI-Compiler. */ +#define DISCARD (void) + +/* Copies a struct into another.  ANSI C allows the assignment operator + * to be used with structs. */ +#define OSCPYSTRUCT(x,y) ((x)=(y)) + +/* Link error messages into the application. */ +#define ERR_LINK_MESSAGES + +/* Program Exit Codes. */ +#define OSEXSUCC 0 /* Successful completion. */ +#define OSEXFAIL 1 /* Failure. */ + +/* Here we configure the osgen layer; refer to tads2/osgen3.c for more + * information about the meaning of these macros. */ +#define USE_DOSEXT +#define USE_NULLSTYPE + +/* Theoretical maximum osmalloc() size. + * Unix systems have at least a 32-bit memory space.  Even on 64-bit + * systems, 2^32 is a good value, so we don't bother trying to find out + * an exact value. */ +#define OSMALMAX 0xffffffffL + +#define OSFNMAX 255 + +/* File handle structure for osfxxx functions. */ +typedef Common::SeekableReadStream osfildef; + +/* Directory handle for searches via os_open_dir() et al. */ +typedef Common::FSNode *osdirhdl_t; + +/* file type/mode bits */ +#define OSFMODE_FILE    S_IFREG +#define OSFMODE_DIR     S_IFDIR +#define OSFMODE_CHAR    S_IFCHR +#define OSFMODE_BLK     S_IFBLK +#define OSFMODE_PIPE    S_IFIFO +#ifdef S_IFLNK +#define OSFMODE_LINK    S_IFLNK +#else +#define OSFMODE_LINK    0 +#endif +#ifdef S_IFSOCK +#define OSFMODE_SOCKET  S_IFSOCK +#else +#define OSFMODE_SOCKET  0 +#endif + +/* File attribute bits. */ +#define OSFATTR_HIDDEN  0x0001 +#define OSFATTR_SYSTEM  0x0002 +#define OSFATTR_READ    0x0004 +#define OSFATTR_WRITE   0x0008 + +/* Get a file's stat() type. */ +int osfmode( const char* fname, int follow_links, unsigned long* mode, +             unsigned long* attr ); + +#if 0 +/* The maximum width of a line of text. + * + * We ignore this, but the base code needs it defined.  If the + * interpreter is run inside a console or terminal with more columns + * than the value defined here, weird things will happen, so we go safe + * and use a large value. */ +#define OS_MAXWIDTH 255 +#endif + +/* Disable the Tads swap file; computers have plenty of RAM these days. + */ +#define OS_DEFAULT_SWAP_ENABLED 0 + +/* TADS 2 macro/function configuration.  Modern configurations always + * use the no-macro versions, so these definitions should always be set + * as shown below. */ +#define OS_MCM_NO_MACRO +#define ERR_NO_MACRO + +/* These values are used for the "mode" parameter of osfseek() to + * indicate where to seek in the file. */ +#define OSFSK_SET SEEK_SET /* Set position relative to the start of the file. */ +#define OSFSK_CUR SEEK_CUR /* Set position relative to the current file position. */ +#define OSFSK_END SEEK_END /* Set position relative to the end of the file. */ + + +/* ============= Functions follow ================ */ + +/* Allocate a block of memory of the given size in bytes. */ +#define osmalloc malloc + +/* Free memory previously allocated with osmalloc(). */ +#define osfree free + +/* Reallocate memory previously allocated with osmalloc() or osrealloc(), + * changing the block's size to the given number of bytes. */ +#define osrealloc realloc + +/* Open text file for reading. */ +#define osfoprt(fname,typ) (fopen((fname),"r")) + +/* Open text file for writing. */ +#define osfopwt(fname,typ) (fopen((fname),"w")) + +/* Open text file for reading and writing, keeping the file's existing + * contents if the file already exists or creating a new file if no + * such file exists. */ +osfildef *osfoprwt(const char *fname, os_filetype_t typ); + +/* Open text file for reading/writing.  If the file already exists, + * truncate the existing contents.  Create a new file if it doesn't + * already exist. */ +#define osfoprwtt(fname,typ) (fopen((fname),"w+")) + +/* Open binary file for writing. */ +#define osfopwb(fname,typ) (fopen((fname),"wb")) + +/* Open source file for reading - use the appropriate text or binary + * mode. */ +#define osfoprs osfoprt + +/* Open binary file for reading. */ +osfildef *osfoprb(const char *fname, os_filetype_t typ); + +/* Open binary file for reading/writing.  If the file already exists, + * keep the existing contents.  Create a new file if it doesn't already + * exist. */ +osfildef* +osfoprwb( const char* fname, os_filetype_t typ ); + +/* Open binary file for reading/writing.  If the file already exists, + * truncate the existing contents.  Create a new file if it doesn't + * already exist. */ +#define osfoprwtb(fname,typ) (fopen((fname),"w+b")) + +/* Get a line of text from a text file. */ +#define osfgets fgets + +/* Write a line of text to a text file. */ +#define osfputs fputs + +/* Write bytes to file. */ +#define osfwb(fp,buf,bufl) (fwrite((buf),(bufl),1,(fp))!=1) + +/* Flush buffered writes to a file. */ +#define osfflush fflush + +/* Read bytes from file. */ +int osfrb(osfildef *fp, void *buf, size_t count); + +/* Read bytes from file and return the number of bytes read. */ +#define osfrbc(fp,buf,bufl) (fread((buf),1,(bufl),(fp))) + +/* Get the current seek location in the file. */ +#define osfpos ftell + +/* Seek to a location in the file. */ +#define osfseek fseek + +/* Close a file. */ +#define osfcls delete + +/* Delete a file. */ +#define osfdel remove + +/* Access a file - determine if the file exists. + * + * We map this to the access() function.  It should be available in + * virtually every system out there, as it appears in many standards + * (SVID, AT&T, POSIX, X/OPEN, BSD 4.3, DOS, MS Windows, maybe more). */ +#define osfacc(fname) (access((fname), F_OK)) + +/* Rename a file. */ +#define os_rename_file(from, to) (rename(from, to) == 0) + +/* Get a file's stat() type. */ +struct os_file_stat_t; +int os_file_stat( const char* fname, int follow_links, +                  struct os_file_stat_t* s ); + +/* Get a character from a file. */ +#define osfgetc fgetc + +/* Set busy cursor. + * + * We don't have a mouse cursor so there's no need to implement this. */ +#define os_csr_busy(a) + +/* Update progress display. + * + * We don't provide any kind of "compilation progress display", so we + * just define this as an empty macro. + */ +#define os_progress(fname,linenum) + +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/appctx.h b/engines/glk/tads/tads2/appctx.h new file mode 100644 index 0000000000..3b3f499777 --- /dev/null +++ b/engines/glk/tads/tads2/appctx.h @@ -0,0 +1,248 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_TADS_TADS2_APPCTX +#define GLK_TADS_TADS2_APPCTX + +#include "common/scummsys.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/** + *   Application container context.  The TADS run-time is a subsystem that + *   can be invoked from different types of applications; in fact, even + *   when only the standard stand-alone run-time is considered, multiple + *   application containers must be supported because of differences + *   between operating systems.  The application container context is an + *   optional mechanism that the main application can use to provide + *   structured interaction between itself and the TADS run-time subsystem. + *    + *   The function pointers contained herein are intended to allow the + *   run-time subsystem to call the host system to notify it of certain + *   events, or obtain optional services from the host system.  Any of + *   these function pointers can be null, in which case the run-time + *   subsystem will skip calling them. + *    + *   Note that each function has an associated callback context.  This + *   allows the host system to recover any necessary context information + *   when the callback is invoked.   + */ +struct appctxdef { +    /** +     *   Get the .GAM file name.  The run-time will call this only if it +     *   can't find a game file to load through some other means first. +     *   The run-time determines the game file first by looking at the +     *   command line, then by checking to see if a .GAM file is attached +     *   to the executable.  If none of these checks yields a game, the +     *   run-time will call this routine to see if the host system wants +     *   to provide a game.  This routine can be implemented on a GUI +     *   system, for example, to display a dialog prompting the user to +     *   select a game file to open.  A trivial implementation of this +     *   routine (that merely returns false) is okay. +     *    +     *   This routine should return true (any non-zero value) if it +     *   provides the name of a file to open, false (zero) if not.   +     */ +    int (*get_game_name)(void *appctxdat, char *buf, size_t buflen); +    void *get_game_name_ctx; + +    /** +     *   Set the .GAM file name.  When the run-time determines the name of +     *   the file it will use to read the game, it calls this routine. +     *   The host system should note the game filename if it will need to +     *   access the game file itself (for example, to load resources).   +     */ +    void (*set_game_name)(void *appctxdat, const char *fname); +    void *set_game_name_ctx; + +    /** +     *   Set the root path for individual resources.  By default, we use the +     *   directory containing the game file, but this can be used to override +     *   that.   +     */ +    void (*set_res_dir)(void *appctxdat, const char *fname); +    void *set_res_dir_ctx; + +    /** +     *   Set the resource map address in the game file.  If the .GAM +     *   reader encounters a resource map in the file, it calls this +     *   routine with the seek offset of the first resource.  Each +     *   resource's address is given as an offset from this point. +     *    +     *   fileno is the file number assigned by the host system in +     *   add_resfile.  File number zero is always the .GAM file.   +     */ +    void (*set_resmap_seek)(void *appctxdat, unsigned long seekpos, int fileno); +    void *set_resmap_seek_ctx; + +    /** +     *   Add a resource entry.  The 'ofs' entry is the byte offset of the +     *   start of the resource, relative to the seek position previously +     *   set with set_resmap_seek.  'siz' is the size of the resource in +     *   bytes; the resource is stored as contiguous bytes starting at the +     *   given offset for the given size.  Note that resources may be +     *   added before the resource map seek position is set, so the host +     *   system must simply store the resource information for later use. +     *   The 'fileno' is zero for the .GAM file, or the number assigned by +     *   the host system in add_resfile for other resource files.   +     */ +    void (*add_resource)(void *appctxdat, unsigned long ofs, +                         unsigned long siz, const char *nm, size_t nmlen, +                         int fileno); +    void *add_resource_ctx; + +    /** +     *   Add a resource link entry.  'fname' and 'fnamelen' give the name of +     *   a local file containing the resource data; 'resname' and +     *   'resnamelen' give the name of the resource as it appears within the +     *   compiled game file.  This creates a link from a .GAM resource name +     *   to a local filename, where the actual binary resource data reside, +     *   so that we can retrieve a resource by .GAM resource name without +     *   actually copying the data into the .GAM file.  This is used mostly +     *   for debugging purposes: it allows the compiler to skip the step of +     *   copying the resource data into the .GAM file, but still allows the +     *   game to load resources by .GAM resource name, to create a testing +     *   environment that's consistent with the full build version (where the +     *   resources would actually be copied).   +     */ +    void (*add_resource_link)(void *appctxdat, +                              const char *fname, size_t fnamelen, +                              const char *resname, size_t resnamelen); +    void *add_resource_link_ctx; + +    /** +     *   Add a resource path.  'path' is a string giving a directory prefix +     *   in local system notation. +     *    +     *   This adds a directory to the list of directories that we'll search +     *   when we're looking for an individual resource as an external file +     *   (such as a ".jpg" image file or ".ogg" sound file).  This can be +     *   called zero or more times; each call adds another directory to +     *   search after any previous directories.  We'll always search the +     *   default directory first (this is the directory containing the game +     *   file); then we'll search directories added with this call in the +     *   order in which the directories were added.   +     */ +    void (*add_res_path)(void *appctxdat, const char *path, size_t len); +    void *add_res_path_ctx; + +    /** +     *   Find a resource entry.  If the resource can be found, this must +     *   return an osfildef* handle to the resource, with its seek position +     *   set to the first byte of the resource data, and set *res_size to +     *   the size in bytes of the resource data in the file.  If the +     *   resource cannot be found, returns null.   +     */ +    osfildef *(*find_resource)(void *appctxdat, +                               const char *resname, size_t resnamelen, +                               unsigned long *res_size); +    void *find_resource_ctx; + +    /** +     *   Add a resource file.  The return value is a non-zero file number +     *   assigned by the host system; we'll use this number in subsequent +     *   calls to add_resource to add the resources from this file. +     *    +     *   After calling this routine to add the file, we'll parse the file +     *   and add any resources using add_resource.   +     */ +    int (*add_resfile)(void *appctxdat, const char *fname); +    void *add_resfile_ctx; + +    /** +     *   Determine if a resource exists.  Returns true if the resource can +     *   be loaded, false if not.  The resource name is in the standard +     *   URL-style format.   +     */ +    int (*resfile_exists)(void *appctxdat, const char *res_name, +                          size_t res_name_len); +    void *resfile_exists_ctx; + +    /** +     *   Resource file path.  If we should look for resource files in a +     *   different location than the .GAM file, the host system can set +     *   this to a path that we should use to look for resource files.  If +     *   it's null, we'll look in the directory that contains the .GAM +     *   file.  Note that if the path is provided, it must be set up with +     *   a trailing path separator character, so that we can directly +     *   append a name to this path to form a valid fully-qualified +     *   filename.   +     */ +    const char *ext_res_path; + +    /** +     *   File safety level get/set.  During initialization, we'll call the +     *   host system to tell it the file safety level selected by the user on +     *   the command line; if the host system is saving preference +     *   information, it should temporarily override its saved preferences +     *   and use the command line setting (and it may, if appropriate, want +     *   to save the command line setting as the saved preference setting, +     *   depending on how it handles preferences).  During execution, any +     *   time the game tries to open a file (using the fopen built-in +     *   function), we'll call the host system to ask it for the current +     *   setting, and use this new setting rather than the original command +     *   line setting. +     *    +     *   Refer to bif.c for information on the meanings of the file safety +     *   levels.   +     */ +    void (*set_io_safety_level)(void *ctx, int read, int write); +    void (*get_io_safety_level)(void *ctx, int *read, int *write); +    void  *io_safety_level_ctx; + +    /** +     *   Network safety level get/set.  This is analogous to the file safety +     *   level scheme, but controls access to network resources.  There are +     *   two components to the network safety setting: client and server. +     *   The client component controls the game's ability to open network +     *   connections to access information on remote machines, such as +     *   opening http connections to access web sites.  The server component +     *   controls the game's ability to create servers of its own and accept +     *   incoming connections.  Each component can be set to one of the +     *   following: +     *    +     *.     0 = no restrictions (least "safety"): all network access granted +     *.     1 = 'localhost' access only +     *.     2 = no network access +     *    +     *   This only applies to the TADS 3 VM.  TADS 2 doesn't support any +     *   network features, so this doesn't apply.   +     */ +    void (*set_net_safety_level)(void *ctx, int client_level, int srv_level); +    void (*get_net_safety_level)(void *ctx, int *client_level, int *srv_level); +    void *net_safety_level_ctx; + +    /** +     *   Name of run-time application for usage messages.  If this is +     *   null, the default run-time application name will be used for +     *   usage messages.  +     */ +    const char *usage_app_name; +}; + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/built_in.h b/engines/glk/tads/tads2/built_in.h new file mode 100644 index 0000000000..f5807e61a8 --- /dev/null +++ b/engines/glk/tads/tads2/built_in.h @@ -0,0 +1,216 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* Built-in functions interface + * + * Interface to run-time intrinsic function implementation + */ + +#ifndef GLK_TADS_TADS2_BUILT_IN +#define GLK_TADS_TADS2_BUILT_IN + +#include "glk/tads/tads2/error.h" +#include "glk/tads/tads2/run.h" +#include "glk/tads/tads2/text_io.h" +#include "glk/tads/tads2/regex.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* forward definitions */ +struct vocidef; +struct voccxdef; + +/* maximum number of file handles available */ +#define BIFFILMAX  10 + + +/* file contexts for the built-in file handling functions */ +struct biffildef { +    osfildef *fp;                          /* underyling system file handle */ +    uint      flags;                                               /* flags */ +#define BIFFIL_F_BINARY    0x01                           /* file is binary */ +}; + +/* built-in execution context */ +struct bifcxdef { +    errcxdef *bifcxerr;                           /* error-handling context */ +    runcxdef *bifcxrun;                           /* code execution context */ +    tiocxdef *bifcxtio;                                 /* text I/O context */ +    long      bifcxrnd;                               /* random number seed */ +    int       bifcxseed1;                   /* first seed for new generator */ +    int       bifcxseed2;                  /* second seed for new generator */ +    int       bifcxseed3;                   /* third seed for new generator */ +    int       bifcxrndset;                   /* randomize() has been called */ +    biffildef bifcxfile[BIFFILMAX];          /* file handles for fopen, etc */ +    int       bifcxsafetyr;                 /* file I/O safety level - read */ +    int       bifcxsafetyw;                /* file I/O safety level - write */ +    char     *bifcxsavext;        /* saved game extension (null by default) */ +    appctxdef *bifcxappctx;                     /* host application context */ +    re_context bifcxregex;          /* regular expression searching context */ + +	bifcxdef() : bifcxerr(nullptr), bifcxrun(nullptr), bifcxtio(nullptr), +		bifcxrnd(0), bifcxseed1(0), bifcxseed2(0), bifcxseed3(0), bifcxrndset(0), +		bifcxsafetyr(0), bifcxsafetyw(0), bifcxsavext(nullptr), bifcxappctx(nullptr) { +	} +}; + +/* + *   argument list checking routines - can be disabled for faster + *   run-time  + */ + +/* check for proper number of arguments */ +/* void bifcntargs(bifcxdef *ctx, int argcnt) */ + +/* check that next argument has proper type */ +/* void bifchkarg(bifcxdef *ctx, dattyp typ); */ + +#ifdef RUNFAST +# define bifcntargs(ctx, parmcnt, argcnt) +# define bifchkarg(ctx, typ) +#else /* RUNFAST */ +# define bifcntargs(ctx, parmcnt, argcnt) \ +  (parmcnt == argcnt ? DISCARD 0 : \ +   (runsig(ctx->bifcxrun, ERR_BIFARGC), DISCARD 0)) +# define bifchkarg(ctx, typ) \ +  (runtostyp(ctx->bifcxrun) == typ ? DISCARD 0 : \ +   (runsig(ctx->bifcxrun, ERR_INVTBIF), DISCARD 0)) +#endif /* RUNFAST */ + +/* determine if one object is a subclass of another */ +int bifinh(struct voccxdef *voc, struct vocidef *v, objnum cls); + + +/* enumerate the built-in functions */ +void bifyon(bifcxdef *ctx, int argc);                   /* yorn - yes or no */ +void bifsfs(bifcxdef *ctx, int argc);                            /* setfuse */ +void bifrfs(bifcxdef *ctx, int argc);                            /* remfuse */ +void bifsdm(bifcxdef *ctx, int argc);                          /* setdaemon */ +void bifrdm(bifcxdef *ctx, int argc);                          /* remdaemon */ +void bifinc(bifcxdef *ctx, int argc);                            /* incturn */ +void bifqui(bifcxdef *ctx, int argc);                               /* quit */ +void bifsav(bifcxdef *ctx, int argc);                               /* save */ +void bifrso(bifcxdef *ctx, int argc);                            /* restore */ +void biflog(bifcxdef *ctx, int argc);                            /* logging */ +void bifres(bifcxdef *ctx, int argc);                            /* restart */ +void bifinp(bifcxdef *ctx, int argc);           /* input - get line from kb */ +void bifnfy(bifcxdef *ctx, int argc);                             /* notify */ +void bifunn(bifcxdef *ctx, int argc);                           /* unnotify */ +void biftrc(bifcxdef *ctx, int argc);                       /* trace on/off */ +void bifsay(bifcxdef *ctx, int argc);                                /* say */ +void bifcar(bifcxdef *ctx, int argc);                                /* car */ +void bifcdr(bifcxdef *ctx, int argc);                                /* cdr */ +void bifcap(bifcxdef *ctx, int argc);                               /* caps */ +void biflen(bifcxdef *ctx, int argc);                             /* length */ +void biffnd(bifcxdef *ctx, int argc);                               /* find */ +void bifsit(bifcxdef *ctx, int argc);           /* setit - set current 'it' */ +void bifsrn(bifcxdef *ctx, int argc);               /* randomize: seed rand */ +void bifrnd(bifcxdef *ctx, int argc);         /* rand - get a random number */ +void bifask(bifcxdef *ctx, int argc);                            /* askfile */ +void bifssc(bifcxdef *ctx, int argc);                           /* setscore */ +void bifsub(bifcxdef *ctx, int argc);                             /* substr */ +void bifcvs(bifcxdef *ctx, int argc);          /* cvtstr: convert to string */ +void bifcvn(bifcxdef *ctx, int argc);          /* cvtnum: convert to number */ +void bifupr(bifcxdef *ctx, int argc);                              /* upper */ +void biflwr(bifcxdef *ctx, int argc);                              /* lower */ +void biffob(bifcxdef *ctx, int argc);                           /* firstobj */ +void bifnob(bifcxdef *ctx, int argc);                            /* nextobj */ +void bifsvn(bifcxdef *ctx, int argc);                         /* setversion */ +void bifarg(bifcxdef *ctx, int argc);                             /* getarg */ +void biftyp(bifcxdef *ctx, int argc);                           /* datatype */ +void bifisc(bifcxdef *ctx, int argc);                            /* isclass */ +void bifund(bifcxdef *ctx, int argc);                               /* undo */ +void bifdef(bifcxdef *ctx, int argc);                            /* defined */ +void bifpty(bifcxdef *ctx, int argc);                           /* proptype */ +void bifoph(bifcxdef *ctx, int argc);                            /* outhide */ +void bifgfu(bifcxdef *ctx, int argc);                            /* getfuse */ +void bifruf(bifcxdef *ctx, int argc);                           /* runfuses */ +void bifrud(bifcxdef *ctx, int argc);                         /* rundaemons */ +void biftim(bifcxdef *ctx, int argc);                            /* gettime */ +void bifsct(bifcxdef *ctx, int argc);                          /* intersect */ +void bifink(bifcxdef *ctx, int argc);                           /* inputkey */ +void bifwrd(bifcxdef *ctx, int argc);                           /* objwords */ +void bifadw(bifcxdef *ctx, int argc);                            /* addword */ +void bifdlw(bifcxdef *ctx, int argc);                            /* delword */ +void bifgtw(bifcxdef *ctx, int argc);                           /* getwords */ +void bifnoc(bifcxdef *ctx, int argc);                             /* nocaps */ +void bifskt(bifcxdef *ctx, int argc);                           /* skipturn */ +void bifcls(bifcxdef *ctx, int argc);                        /* clearscreen */ +void bif1sc(bifcxdef *ctx, int argc);                            /* firstsc */ +void bifvin(bifcxdef *ctx, int argc);                           /* verbinfo */ +void bifcapture(bifcxdef *ctx, int argc);                     /* outcapture */ + +void biffopen(bifcxdef *ctx, int argc);                            /* fopen */ +void biffclose(bifcxdef *ctx, int argc);                          /* fclose */ +void biffwrite(bifcxdef *ctx, int argc);                          /* fwrite */ +void biffread(bifcxdef *ctx, int argc);                            /* fread */ +void biffseek(bifcxdef *ctx, int argc);                            /* fseek */ +void biffseekeof(bifcxdef *ctx, int argc);                      /* fseekeof */ +void bifftell(bifcxdef *ctx, int argc);                            /* ftell */ + +void bifsysinfo(bifcxdef *ctx, int argc);                     /* systemInfo */ +void bifmore(bifcxdef *ctx, int argc);                        /* morePrompt */ +void bifsetme(bifcxdef *ctx, int argc);                      /* parserSetMe */ +void bifgetme(bifcxdef *ctx, int argc);                      /* parserGetMe */ + +void bifresearch(bifcxdef *ctx, int argc);                      /* reSearch */ +void bifregroup(bifcxdef *ctx, int argc);                     /* reGetGroup */ + +void bifinpevt(bifcxdef *ctx, int argc);                      /* inputevent */ +void bifdelay(bifcxdef *ctx, int argc);                        /* timeDelay */ + +void bifsetoutfilter(bifcxdef *ctx, int argc);           /* setOutputFilter */ +void bifexec(bifcxdef *ctx, int argc);                       /* execCommand */ +void bifgetobj(bifcxdef *ctx, int argc);                    /* parserGetObj */ +void bifparsenl(bifcxdef *ctx, int argc);            /* parserParseNounList */ +void bifprstok(bifcxdef *ctx, int argc);                  /* parserTokenize */ +void bifprstoktyp(bifcxdef *ctx, int argc);            /* parserGetTokTypes */ +void bifprsdict(bifcxdef *ctx, int argc);               /* parserDictLookup */ +void bifprsrslv(bifcxdef *ctx, int argc);           /* parserResolveObjects */ +void bifprsrplcmd(bifcxdef *ctx, int argc);         /* parserReplaceCommand */ +void bifexitobj(bifcxdef *ctx, int argc);                        /* exitobj */ +void bifinpdlg(bifcxdef *ctx, int argc);                     /* inputdialog */ +void bifresexists(bifcxdef *ctx, int argc);               /* resourceExists */ + +/* + *   TADS/graphic functions - these are present in the text system, but + *   don't do anything. + */ +void bifgrp(bifcxdef *ctx, int argc);            /* g_readpic: read picture */ +void bifgsp(bifcxdef *ctx, int argc);            /* g_showpic: show picture */ +void bifgsh(bifcxdef *ctx, int argc);             /* g_sethot: set hot list */ +void bifgin(bifcxdef *ctx, int argc);                        /* g_inventory */ +void bifgco(bifcxdef *ctx, int argc);                          /* g_compass */ +void bifgov(bifcxdef *ctx, int argc);                          /* g_overlay */ +void bifgmd(bifcxdef *ctx, int argc);                             /* g_mode */ +void bifgmu(bifcxdef *ctx, int argc);                            /* g_music */ +void bifgpa(bifcxdef *ctx, int argc);                            /* g_pause */ +void bifgef(bifcxdef *ctx, int argc);                           /* g_effect */ +void bifgsn(bifcxdef *ctx, int argc);                            /* g_sound */ + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/character_map.cpp b/engines/glk/tads/tads2/character_map.cpp new file mode 100644 index 0000000000..91daa8668c --- /dev/null +++ b/engines/glk/tads/tads2/character_map.cpp @@ -0,0 +1,339 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/tads2/character_map.h" +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/error.h" +#include "glk/tads/tads2/os.h" +#include "glk/tads/tads2/text_io.h" +#include "glk/tads/osfrobtads.h" +#include "common/algorithm.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* ------------------------------------------------------------------------ */ +/* + *   Global variables for character mapping tables  + */ + +unsigned char G_cmap_input[256]; +unsigned char G_cmap_output[256]; +char G_cmap_id[5]; +char G_cmap_ldesc[CMAP_LDESC_MAX_LEN + 1]; + + +/*  + *   static variables  + */ + +/*  + *   flag: true -> a character set has been explicitly loaded, so we + *   should ignore any game character set setting  + */ +static int S_cmap_loaded; + + +/* ------------------------------------------------------------------------ */ +/* + *   Initialize the default character mappings  + */ +void cmap_init_default(void) +{ +    size_t i; + +    /* initialize the input table */ +    for (i = 0 ; i < sizeof(G_cmap_input)/sizeof(G_cmap_input[0]) ; ++i) +        G_cmap_input[i] = (unsigned char)i; + +    /* initialize the output table */ +    for (i = 0 ; i < sizeof(G_cmap_output)/sizeof(G_cmap_output[0]) ; ++i) +        G_cmap_output[i] = (unsigned char)i; + +    /* we have a null ID */ +    memset(G_cmap_id, 0, sizeof(G_cmap_id)); + +    /* indicate that it's the default */ +    strcpy(G_cmap_ldesc, "(native/no mapping)"); + +    /* note that we have no character set loaded */ +    S_cmap_loaded = FALSE; +} + +/* ------------------------------------------------------------------------ */ +/* + *   Internal routine to load a character map from a file  + */ +static int cmap_load_internal(char *filename) +{ +    osfildef *fp; +    static char sig1[] = CMAP_SIG_S100; +    char buf[256]; +    uchar lenbuf[2]; +    size_t len; +    int sysblk; + +    /* if there's no mapping file, use the default mapping */ +    if (filename == 0) +    { +        /* initialize with the default mapping */ +        cmap_init_default(); + +        /* return success */ +        return 0; +    } +     +    /* open the file */ +    fp = osfoprb(filename, OSFTCMAP); +    if (fp == 0) +        return 1; + +    /* check the signature */ +    if (osfrb(fp, buf, sizeof(sig1)) +        || memcmp(buf, sig1, sizeof(sig1)) != 0) +    { +        osfcls(fp); +        return 2; +    } + +    /* load the ID */ +    G_cmap_id[4] = '\0'; +    if (osfrb(fp, G_cmap_id, 4)) +    { +        osfcls(fp); +        return 3; +    } + +    /* load the long description */ +    if (osfrb(fp, lenbuf, 2) +        || (len = osrp2(lenbuf)) > sizeof(G_cmap_ldesc) +        || osfrb(fp, G_cmap_ldesc, len)) +    { +        osfcls(fp); +        return 4; +    } + +    /* load the two tables - input, then output */ +    if (osfrb(fp, G_cmap_input, sizeof(G_cmap_input)) +        || osfrb(fp, G_cmap_output, sizeof(G_cmap_output))) +    { +        osfcls(fp); +        return 5; +    } + +    /* read the next section header */ +    if (osfrb(fp, buf, 4)) +    { +        osfcls(fp); +        return 6; +    } + +    /* if it's "SYSI", read the system information string */ +    if (!memcmp(buf, "SYSI", 4)) +    { +        /* read the length prefix, then the string */ +        if (osfrb(fp, lenbuf, 2) +            || (len = osrp2(lenbuf)) > sizeof(buf) +            || osfrb(fp, buf, len)) +        { +            osfcls(fp); +            return 7; +        } + +        /* we have a system information block */ +        sysblk = TRUE; +    } +    else +    { +        /* there's no system information block */ +        sysblk = FALSE; +    } + +    /*  +     *   call the OS code, so that it can do any system-dependent +     *   initialization for the new character mapping  +     */ +    os_advise_load_charmap(G_cmap_id, G_cmap_ldesc, sysblk ? buf : ""); + +    /* read the next section header */ +    if (sysblk && osfrb(fp, buf, 4)) +    { +        osfcls(fp); +        return 8; +    } + +    /* see if we have an entity list */ +    if (!memcmp(buf, "ENTY", 4)) +    { +        /* read the entities */ +        for (;;) +        { +            size_t blen; +            unsigned int cval; +            char expansion[CMAP_MAX_ENTITY_EXPANSION]; +             +            /* read the next item's length and character value */ +            if (osfrb(fp, buf, 4)) +            { +                osfcls(fp); +                return 9;  +            } + +            /* decode the values */ +            blen = osrp2(buf); +            cval = osrp2(buf+2); + +            /* if we've reached the zero marker, we're done */ +            if (blen == 0 && cval == 0) +                break; + +            /* read the string */ +            if (blen > CMAP_MAX_ENTITY_EXPANSION +                || osfrb(fp, expansion, blen)) +            { +                osfcls(fp); +                return 10; +            } + +            /* tell the output code about the expansion */ +            tio_set_html_expansion(cval, expansion, blen); +        } +    } + +    /*  +     *   ignore anything else we find - if the file format is updated to +     *   include extra information in the future, and this old code tries +     *   to load an updated file, we'll just ignore the new information, +     *   which should always be placed after the "SYSI" block (if present) +     *   to ensure compatibility with past versions (such as this code) +     */ + +    /* no problems - close the file and return success */ +    osfcls(fp); +    return 0; +} + + +/* ------------------------------------------------------------------------ */ +/* + *   Explicitly load a character set from a file.  This character set + *   mapping will override any implicit character set mapping that we read + *   from a game file.  This should be called when the player explicitly + *   loads a character set (via a command line option or similar action).   + */ +int cmap_load(char *filename) +{ +    int err; +     +    /* try loading the file */ +    if ((err = cmap_load_internal(filename)) != 0) +        return err; + +    /*  +     *   note that we've explicitly loaded a character set, if they named +     *   a character set (if not, this simply establishes the default +     *   setting, so we haven't explicitly loaded anything)  +     */ +    if (filename != 0) +        S_cmap_loaded = TRUE; + +    /* success */ +    return 0; +} + + +/* ------------------------------------------------------------------------ */ +/* + *   Explicitly override any game character set and use no character set + *   instead.  + */ +void cmap_override(void) +{ +    /* apply the default mapping */ +    cmap_init_default(); + +    /*  +     *   pretend we have a character map loaded, so that we don't try to +     *   load another one if the game specifies a character set +     */ +    S_cmap_loaded = TRUE; +} + + +/* ------------------------------------------------------------------------ */ +/* + *   Set the game's internal character set.  This is called when a game is + *   loaded, and the game specifies a character set.  + */ +void cmap_set_game_charset(errcxdef *ec, +                           char *internal_id, char *internal_ldesc, +                           char *argv0) +{ +    char filename[OSFNMAX]; +     +    /*  +     *   If a character set is already explicitly loaded, ignore the +     *   game's character set - the player asked us to use a particular +     *   mapping, so ignore what the game wants.  (This will probably +     *   result in incorrect display of non-ASCII character values, but +     *   the player is most likely to use this to avoid errors when an +     *   appropriate mapping file for the game is not available.  In this +     *   case, the player informs us by setting the option that he or she +     *   knows and accepts that the game will not look exactly right.)   +     */ +    if (S_cmap_loaded) +        return; + +    /*  +     *   ask the operating system to name the mapping file -- this routine +     *   will determine, if possible, the current native character set, +     *   and apply a system-specific naming convention to tell us what +     *   mapping file we should open  +     */ +    os_gen_charmap_filename(filename, internal_id, argv0); + +    /* try loading the mapping file */ +    if (cmap_load_internal(filename)) +        errsig2(ec, ERR_CHRNOFILE, +                ERRTSTR, errstr(ec, filename, strlen(filename)), +                ERRTSTR, errstr(ec, internal_ldesc, strlen(internal_ldesc))); + +    /* +     *   We were successful - the game's internal character set is now +     *   mapped to the current native character set.  Even though we +     *   loaded an ldesc from the mapping file, forget that and store the +     *   internal ldesc that the game specified.  The reason we do this is +     *   that it's possible that the player will dynamically switch native +     *   character sets in the future, at which point we'll need to +     *   re-load the mapping table, which could raise an error if a +     *   mapping file for the new character set isn't available.  So, we +     *   may need to provide the same explanation later that we needed to +     *   provide here.  Save the game's character set ldesc for that +     *   eventuality, since it describes exactly what the *game* wanted.   +     */ +    strcpy(G_cmap_ldesc, internal_ldesc); +} + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/tads2/character_map.h b/engines/glk/tads/tads2/character_map.h new file mode 100644 index 0000000000..c61406fd27 --- /dev/null +++ b/engines/glk/tads/tads2/character_map.h @@ -0,0 +1,131 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_TADS_CHARACTER_MAP +#define GLK_TADS_CHARACTER_MAP + +namespace Glk { +namespace TADS { +namespace TADS2 { + +struct errcxdef; + +/* ------------------------------------------------------------------------ */ +/* + *   Initialize the default character mappings.  If no mapping file is to + *   be read, this function will establish identify mappings that leave + *   characters untranslated.  + */ +void cmap_init_default(void); + + +/* + *   Load a character map file.  Returns zero on success, non-zero on + *   failure.  If filename is null, we'll use the default mapping. + */ +int cmap_load(char *filename); + + +/* + *   Turn off character translation.  This overrides any game character + *   set that we find and simply uses the default translation.  + */ +void cmap_override(void); + + +/* + *   Set the game's internal character set.  This should be called when a + *   game is loaded, and the game specifies an internal character set.  If + *   there is no character map file explicitly loaded, we will attempt to + *   load a character mapping file that maps this character set to the + *   current native character set.  Signals an error on failure.  This + *   routine will succeed (without doing anything) if a character set has + *   already been explicitly loaded, since an explicitly-loaded character + *   set overrides the automatic character set selection that we attempt + *   when loading a game. + *    + *   argv0 must be provided so that we know where to look for our mapping + *   file on systems where mapping files are stored in the same directory + *   as the TADS executables.   + */ +void cmap_set_game_charset(struct errcxdef *errctx, +                           char *internal_id, char *internal_ldesc, +                           char *argv0); + + +/* ------------------------------------------------------------------------ */ +/* + *   Mapping macros  + */ + +/* map a native character (read externally) into an internal character */ +#define cmap_n2i(c) (G_cmap_input[(unsigned char)(c)]) + +/* map an internal character into a native character (for display) */ +#define cmap_i2n(c) (G_cmap_output[(unsigned char)(c)]) + + +/* ------------------------------------------------------------------------ */ +/* + *   Global character mapping tables.  The character map is established at + *   start-up.  + */ + +/*  + *   input-mapping table - for native character 'n', cmap_input[n] yields + *   the internal character code  + */ +extern unsigned char G_cmap_input[256]; + +/* + *   output-mapping table - for internal character 'n', cmap_output[n] + *   yields the output character code  + */ +extern unsigned char G_cmap_output[256]; + +/* the ID of the loaded character set */ +extern char G_cmap_id[5]; + +/* the full name (for display purposes) of the loaded character set */ +#define CMAP_LDESC_MAX_LEN  40 +extern char G_cmap_ldesc[CMAP_LDESC_MAX_LEN + 1]; + +/* + *   Maximum expansion for an HTML entity mapping  + */ +#define CMAP_MAX_ENTITY_EXPANSION  50 + + +/* ------------------------------------------------------------------------ */ +/*  + *   Signatures for character map files.  The signature is stored at the + *   beginning of the file.   + */ + +/* single-byte character map version 1.0.0 */ +#define CMAP_SIG_S100  "TADS2 charmap S100\n\r\01a" + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/data.cpp b/engines/glk/tads/tads2/data.cpp index c60fcb2c5f..b19f45f1fd 100644 --- a/engines/glk/tads/tads2/data.cpp +++ b/engines/glk/tads/tads2/data.cpp @@ -21,7 +21,8 @@   */  #include "glk/tads/tads2/data.h" -#include "glk/tads/tads2/types.h" +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/run.h"  #include "glk/tads/tads2/vocabulary.h"  #include "common/algorithm.h" diff --git a/engines/glk/tads/tads2/data.h b/engines/glk/tads/tads2/data.h index 8db2153e1f..8d122c444a 100644 --- a/engines/glk/tads/tads2/data.h +++ b/engines/glk/tads/tads2/data.h @@ -47,6 +47,7 @@ enum DataType {  	DAT_REDIR   = 16,		///< redirection to different object  	DAT_TPL2    = 17		///< new-style template  }; +typedef DataType dattyp;  class Data {  private: diff --git a/engines/glk/tads/tads2/debug.h b/engines/glk/tads/tads2/debug.h new file mode 100644 index 0000000000..52695cfb86 --- /dev/null +++ b/engines/glk/tads/tads2/debug.h @@ -0,0 +1,589 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_TADS_TADS2_DEBUG +#define GLK_TADS_TADS2_DEBUG + +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/object.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + + +/* forward declarations */ +struct bifcxdef; +struct toksdef; +struct toktdef; +struct tokcxdef; + +/* stack frame record */ +struct dbgfdef +{ +    struct runsdef *dbgfbp;                        /* base pointer of frame */ +    objnum  dbgfself;             /* 'self' object (MCMONINV for functions) */ +    objnum  dbgftarg;                               /* actual target object */ +    prpnum  dbgfprop;                          /* property being evalutated */ +    int     dbgfargc;                                /* number of arguments */ +    int     dbgfbif;      /* set to built-in function number if in built-in */ +    uint    dbgffr;         /* offset in object of local frame symbol table */ +    uint    dbgflin;                      /* OPCLINE operand of latest line */ +}; +typedef struct dbgfdef dbgfdef; + +/* max number of frames to store in debug frame memory */ +#define DBGMAXFRAME  100 + +/* maximum number of breakpoints set concurrently */ +#define DBGBPMAX 50 + +/* breakpoint structure */ +struct dbgbpdef +{ +    objnum dbgbpself;               /* the "self" object for the breakpoint */ +    objnum dbgbptarg;            /* actual target object for the breakpoint */ +    uint   dbgbpofs;                  /* offset in object of the breakpoint */ +    uint   dbgbpflg;                                    /* breakpoint flags */ +#define DBGBPFUSED  0x01                         /* breakpoint has been set */ +#define DBGBPFNAME  0x02                 /* name of address has been stored */ +#define DBGBPFCOND  0x04             /* breakpoint has a condition attached */ +#define DBGBPFDISA  0x08                          /* breakpoint is disabled */ +#define DBGBPFCONDNAME 0x10        /* condition name string has been stored */ +    uint   dbgbpnam;       /* offset of address name within dbgcxnam buffer */ +    uint   dbgbpcondnam;        /* offset of condition string within buffer */ +    objnum dbgbpcond;        /* object containing compiled condition for bp */ +}; +typedef struct dbgbpdef dbgbpdef; + +/* maximum number of watch expressions set concurrently */ +#define DBGWXMAX 30 + +/* watch expression structure */ +struct dbgwxdef +{ +    objnum  dbgwxobj;              /* object containing compiled expression */ +    objnum  dbgwxself;                         /* 'self' for the expression */ +    uint    dbgwxnam;   /* offset of expression text within dbgcxnam buffer */ +    uint    dbgwxflg;               /* flags for this watch expression slot */ +#define DBGWXFUSED  0x01                            /* watch slot is in use */ +#define DBGWXFNAME  0x02                   /* name of watch has been stored */ +}; +typedef struct dbgwxdef dbgwxdef; + +/* amount of space for bp names (original address strings from user) */ +#define DBGCXNAMSIZ 2048 + +/* debug context */ +struct dbgcxdef +{ +    struct tiocxdef *dbgcxtio;                          /* text i/o context */ +    struct tokthdef *dbgcxtab;                              /* symbol table */ +    struct mcmcxdef *dbgcxmem;              /* memory cache manager context */ +    struct errcxdef *dbgcxerr;                    /* error handling context */ +    struct    lindef *dbgcxlin;                    /* chain of line sources */ +    int       dbgcxfcn;                          /* number of frames in use */ +    int       dbgcxdep;          /* actual depth (if overflow frame buffer) */ +    int       dbgcxfid;                        /* source file serial number */ +    dbgfdef   dbgcxfrm[DBGMAXFRAME];                        /* stack frames */ +    int       dbgcxflg;                          /* flags for debug session */ +#define    DBGCXFSS   0x01                  /* single-stepping source lines */ +#define    DBGCXFSO   0x02          /* stepping over a function/method call */ +#define    DBGCXFOK   0x04                         /* debugger is linked in */ +#define    DBGCXFIND  0x08     /* in debugger - suppress stack trace on err */ +#define    DBGCXFGBP  0x10                  /* global breakpoints in effect */ +#define    DBGCXFTRC  0x20                        /* call tracing activated */ +#define    DBGCXFLIN2 0x40         /* new-style line records (line numbers) */ +    int       dbgcxsof;                    /* frame depth at step-over time */ +    dbgbpdef  dbgcxbp[DBGBPMAX];                             /* breakpoints */ +    dbgwxdef  dbgcxwx[DBGWXMAX];                       /* watch expressions */ +    struct    prscxdef *dbgcxprs;                        /* parsing context */ +    struct    runcxdef *dbgcxrun;                      /* execution context */ +    uint      dbgcxnamf;               /* next free byte of dbgcxnam buffer */ +    uint      dbgcxnams;                         /* size of dbgcxnam buffer */ +    char     *dbgcxnam;                       /* space for bp address names */ +    char     *dbgcxhstp;                             /* call history buffer */ +    uint      dbgcxhstl;                           /* history buffer length */ +    uint      dbgcxhstf;             /* offset of next free byte of history */ + +    /*  +     *   This member is for the use of the user interface code.  If the +     *   user interface implementation needs to store additional context, +     *   it can allocate a structure of its own (it should probably do +     *   this in dbguini()) and store a pointer to that structure here. +     *   Since the user interface entrypoints always have the debugger +     *   context passed as a parameter, the user interface code can +     *   recover its extra context information by following this pointer +     *   and casting it to its private structure type.  The TADS code +     *   won't do anything with this pointer except initialize it to null +     *   when initializing the debugger context.   +     */ +    void     *dbgcxui; +}; +typedef struct dbgcxdef dbgcxdef; + + +/* ======================================================================== */ +/* + *   Compiler interface.  These routines are called by the compiler to + *   inform the debug record generator about important events as + *   compilation proceeds.  + */ + + +/* + *   Tell the current line source that we're compiling an executable + *   line, and tell it the object number and offset of the code within the + *   object.  + */ +void dbgclin(struct tokcxdef *tokctx, objnum objn, uint ofs); + +/* size of information given to line source via lincmpinf method */ +#define DBGLINFSIZ   4 + + + +/* ======================================================================== */ +/* + *   Run-time interface.  These routines are called by the run-time + *   system to apprise the debugger of important events during execution. + */ + + +/* + *   Determine if the debugger is present.  Returns true if so, false if + *   not.  This should return false for any stand-alone version of the + *   executable that isn't linked with the debugger.  If this returns + *   true, dbgucmd() must not have a trivial implementation -- dbgucmd() + *   must at least let the user quit out of the game. + *    + *   This can be switched at either link time or compile time.  If DBG_OFF + *   is defined, we'll force this to return false; otherwise, we'll let + *   the program define the appropriate implementation through the linker. + */ +#ifdef DBG_OFF +#define dbgpresent() (FALSE) +#else +int dbgpresent(); +#endif + + +/* add a debug tracing record */ +/* void dbgenter(dbgcxdef *ctx, runsdef *bp, objnum self, objnum target, +                 prpnum prop, int binum, int argc); */  + +/* tell debugger where the current line's local frame table is located */ +/* void dbgframe(dbgcxdef *ctx, uint ofsfr, ofslin); */ + +/*  + *   Single-step interrupt: the run-time has reached a new source line. + *   ofs is the offset from the start of the object of the line record, + *   and p is the current execution pointer.  *p can be changed upon + *   return, in which case the run-time will continue from the new + *   position; however, the new address must be within the same function + *   or method as it was originally. + */ +/* void dbgssi(dbgcxdef *ctx, uint ofs, int instr, +               int err, uchar *noreg *p); */ + +/* pop debug trace level */ +/* void dbgleave(dbgcxdef *ctx, int exittype); */ +#define DBGEXRET   0                                /* return with no value */ +#define DBGEXVAL   1                                 /* return with a value */ +#define DBGEXPASS  2                       /* use 'pass' to exit a function */ + +/* dump the stack into text output */ +/* void dbgdump(dbgcxdef *ctx); */ + +/* reset debug stack (throw away entire contents) */ +/* void dbgrst(dbgcxdef *ctx); */ + +/* activate debugger if possible; returns TRUE if no debugger is present */ +int dbgstart(dbgcxdef *ctx); + +/* add a string to the history buffer */ +void dbgaddhist(dbgcxdef *ctx, char *buf, int bufl); + +/* + *   Find a base pointer, given the object+offset of the frame.  If the + *   frame is not active, this routine signals ERR_INACTFR; otherwise, the + *   bp value for the frame is returned.  + */ +struct runsdef *dbgfrfind(dbgcxdef *ctx, objnum frobj, uint frofs); + + +/* ======================================================================== */ +/* + *   User Interface Support routines.  These routines are called by the + *   user interface layer to get information from the debugger and perform + *   debugging operations.  + */ + + +/* get a symbol name; returns length of name */ +int dbgnam(dbgcxdef *ctx, char *outbuf, int typ, int val); + +/* + *   Get information about current line.  It is assumed that the caller + *   knows the size of the line information . + */ +void dbglget(dbgcxdef *ctx, uchar *buf); + +/* + *   Get information about a line in an enclosing stack frame.  Level 0 is + *   the current line, level 1 is the first enclosing frame, and so on. + *   Returns 0 on success, non-zero if the frame level is invalid.   + */ +int dbglgetlvl(dbgcxdef *ctx, uchar *buf, int level); + +/* + *   Set a breakpoint by symbolic address: "function" or + *   "object.property".  The string may contain whitespace characters + *   around each symbol; it must be null-terminated.  If an error occurs, + *   the error number is returned.  bpnum returns with the breakpoint + *   number if err == 0.  If the condition string is given (and is not an + *   empty string), the condition is compiled in the scope of the + *   breakpoint and attached as the breakpoint condition.   + */ +int dbgbpset(dbgcxdef *ctx, char *addr, int *bpnum); + +/*  + *   Set a breakpoint at an object + offset location.  If 'toggle' is + *   true, and there's already a breakpoint at the given location, we'll + *   clear the breakpoint; in this case, *did_set will return false to + *   indicate that an existing breakpoint was cleared rather than a new + *   breakpoint created.  *did_set will return true if a new breakpoint + *   was set.   + */ +int dbgbpat(dbgcxdef *ctx, objnum objn, objnum self, +            uint ofs, int *bpnum, char *bpname, int toggle, +            char *condition, int *did_set); + +/*  + *   Set a breakpoint at an object + offset location, optionally with a + *   condition, using an existing breakpoint slot.  If the slot is already + *   in use, we'll return an error.   + */ +int dbgbpatid(dbgcxdef *ctx, int bpnum, objnum target, objnum self, +              uint ofs, char *bpname, int toggle, char *cond, +              int *did_set); + +/* + *   Determine if there's a breakpoint at a given code location.  Fills in + *   *bpnum with the breakpoint identifier and returns true if a + *   breakpoint is found at the given location; returns false if there are + *   no breakpoints matching the description.   + */ +int dbgisbp(dbgcxdef *ctx, objnum target, objnum self, uint ofs, int *bpnum); + +/* + *   Determine if the given breakpoint is enabled  + */ +int dbgisbpena(dbgcxdef *ctx, int bpnum); + +/* + *   Delete a breakpoint by breakpoint number (as returned from + *   dbgbpset).  Returns error number, or 0 for success.  + */ +int dbgbpdel(dbgcxdef *ctx, int bpnum); + +/* disable or enable a breakpoint, by breakpoint number; returns error num */ +int dbgbpdis(dbgcxdef *ctx, int bpnum, int disable); + +/* + *   Set a new condition for the given breakpoint.  Replaces any existing + *   condition.  If an error occurs, we'll leave the old condition as it + *   was and return a non-zero error code; on success, we'll update the + *   condition and return zero.  + */ +int dbgbpsetcond(dbgcxdef *ctx, int bpnum, char *cond); + +/* list breakpoints, using user callback to do display */ +void dbgbplist(dbgcxdef *ctx, +               void (*dispfn)(void *ctx, const char *str, int len), +               void *dispctx); + +/* enumerate breakpoints */ +void dbgbpenum(dbgcxdef *ctx, +               void (*cbfunc)(void *cbctx, int bpnum, const char *desc, +                              const char *cond, int disabled), void *cbctx); + +/* call callback with lindef data for each breakpoint currently set */ +void dbgbpeach(dbgcxdef *ctx, +               void (*fn)(void *, int, uchar *, uint), +               void *fnctx); + +/*  + *   Get information on a specific breakpoint.  Returns zero on success, + *   non-zero on failure.  + */ +int dbgbpgetinfo(dbgcxdef *ctx, int bpnum, char *descbuf, size_t descbuflen, +                 char *condbuf, size_t condbuflen); + +/*  + *   Evaluate an expression (a text string to be parsed) at a particular + *   stack context level; returns error number.  Invokes the callback + *   function repeatedly to display the value string, and ends the display + *   with a newline.  If showtype is true, we'll include a type name + *   prefix, otherwise we'll simply display the value.   + */ +int dbgeval(dbgcxdef *ctx, char *expr, +            void (*dispfn)(void *dispctx, const char *str, int strl), +            void *dispctx, int level, int showtype); + +/* + *   Evaluate an expression, extended version.  For aggregate values + *   (objects, lists), we'll invoke a callback function for each value + *   contained by the aggregate value, passing the callback the name and + *   relationship of the subitem.  The relationship is simply the operator + *   that should be used to join the parent expression and the subitem + *   name to form the full subitem expression; for objects, it's ".", and + *   for lists it's null (because for lists the subitem names will include + *   brackets).  'speculative' is passed to dbgcompile; see the comments + *   there for information on the purpose of this flag.   + */ +int dbgevalext(dbgcxdef *ctx, char *expr, +               void (*dispfn)(void *dispctx, const char *str, int strl), +               void *dispctx, int level, int showtype, dattyp *dat, +               void (*aggcb)(void *aggctx, const char *subname, +                             int subnamelen, const char *relationship), +               void *aggctx, int speculative); + +/*  + *   enumerate local variables at a given stack context level by calling + *   the given function once for each local variable  + */ +void dbgenumlcl(dbgcxdef *ctx, int level, +                void (*func)(void *ctx, const char *lclnam, size_t lclnamlen), +                void *cbctx); + +/*  + *   Compile an expression in a given frame context.  Returns an error + *   number.  Allocates a new object to contain the compiled code, and + *   returns the object number in *objn; the caller is responsible for + *   freeing the object when done with it. + *    + *   If 'speculative' is set to true, we'll prohibit the expression from + *   making any assignments or calling any methods or functions.  This + *   mode can be used to try compiling an expression that the user could + *   conceivably be interested in but has not expressly evaluated; for + *   example, this can be used to implement "tooltip evaluation," where + *   the debugger automatically shows a little pop-up window with the + *   expression under the mouse cursor if the mouse cursor is left + *   hovering over some text for a few moments.  In such cases, since the + *   user hasn't explicitly requested evaluation, it would be bad to make + *   any changes to game state, hence the prohibition of assignments or + *   calls.   + */ +int dbgcompile(dbgcxdef *ctx, char *expr, dbgfdef *fr, objnum *objn, +               int speculative); + +/* display a stack traceback through a user callback */ +void dbgstktr(dbgcxdef *ctx, +              void (*dispfn)(void *dispctx, const char *str, int strl), +              void *dispctx, int level, int toponly, int include_markers); + +/* format a display of where execution is stopped into a buffer */ +void dbgwhere(dbgcxdef *ctx, char *buf); + +/* set a watch expression; returns error or 0 for success */ +int dbgwxset(dbgcxdef *ctx, char *expr, int *wxnum, int level); + +/* delete a watch expression */ +int dbgwxdel(dbgcxdef *ctx, int wxnum); + +/* update all watch expressions */ +void dbgwxupd(dbgcxdef *ctx, +              void (*dispfn)(void *dispctx, const char *txt, int len), +              void *dispctx); + +/* switch to a new active lindef */ +void dbgswitch(struct lindef **linp, struct lindef *newlin); + + + +/* ======================================================================== */ +/* + *   User Interface Routines.  The routines are called by the debugger + *   to perform user interaction. + */ + +/*  + *   Debugger user interface initialization, phase one.  TADS calls this + *   routine during startup, before reading the .GAM file, to let the user + *   interface perform any initialization it requires before the .GAM file + *   is loaded.   + */ +void dbguini(dbgcxdef *ctx, const char *game_filename); + +/* + *   Debugger user interface initialization, phase two.  TADS calls this + *   routine during startup, after read the .GAM file.  The debugger user + *   interface code can perform any required initialization that depends + *   on the .GAM file having been read.   + */ +void dbguini2(dbgcxdef *ctx); + +/* + *   Determine if the debugger can resume from a run-time error.  This + *   reflects the capabilities of the user interface of the debugger.  In + *   particular, if the UI provides a way to change the instruction + *   pointer, then the debugger can resume from an error, since the user + *   can always move past the run-time error and continue execution.  If + *   the UI doesn't let the user change the instruction pointer, resuming + *   from an error won't work, since the program will keep hitting the + *   same error and re-entering the debugger.  If this returns false, the + *   run-time will trap to the debugger on an error, but will simply abort + *   the current command when the debugger returns.  If this returns true, + *   the run-time will trap to the debugger on an error with the + *   instruction pointer set back to the start of the line containing the + *   error, and will thus re-try the same line of code when the debugger + *   returns, unless the debugger explicitly moves the instruction pointer + *   before returning.   + */ +int dbgu_err_resume(dbgcxdef *ctx); + +/* + *   Find a source file.  origname is the name of the source file as it + *   appears in the game's debugging information; this routine should + *   figure out where the file actually is, and put the fully-qualified + *   path to the file in fullname.  The debugger calls this after it + *   exhausts all of its other methods of finding a source file (such as + *   searching the include path). + *    + *   Return true if the source file should be considered valid, false if + *   not.  Most implementations will simply return true if the file was + *   found, false if not; however, this approach will cause the debugger + *   to terminate with an error at start-up if the user hasn't set up the + *   debugger's include path correctly before running the debugger.  Some + *   implementations, in particular GUI implementations, may wish to wait + *   to find a file until the file is actually needed, rather than pester + *   the user with file search dialogs repeatedly at start-up. + *    + *   must_find_file specifies how to respond if we can't find the file. + *   If must_find_file is true, we should always return false if we can't + *   find the file.  If must_find_file is false, however, we can + *   optionally return true even if we can't find the file.  Doing so + *   indicates that the debugger UI will defer locating the file until it + *   is actually needed. + *    + *   If this routine returns true without actually finding the file, it + *   should set fullname[0] to '\0' to indicate that fullname doesn't + *   contain a valid filename.   + */ +int dbgu_find_src(const char *origname, int origlen, +                  char *fullname, size_t full_len, int must_find_file); + + +/*  + *   Debugger user interface main command loop.  If err is non-zero, the + *   debugger was entered because a run-time error occurred; otherwise, if + *   bphit is non-zero, it's the number of the breakpoint that was + *   encountered; otherwise, the debugger was entered through a + *   single-step of some kind.  exec_ofs is the byte offset within the + *   target object of the next instruction to be executed.  This can be + *   changed upon return, in which case execution will continue from the + *   new offset, but the offset must be within the same method of the same + *   object (or within the same function) as it was upon entry.  + */ +void dbgucmd(dbgcxdef *ctx, int bphit, int err, unsigned int *exec_ofs); + +/* + *   Debugger UI - quitting game.  The runtime calls this routine just + *   before the play loop is about to terminate after the game code has + *   called the "quit" built-in function.  If the debugger wants, it can + *   take control here (just as with dbgucmd()) for as long as it wants. + *   If the debugger wants to restart the game, it should call bifrst(). + *   If this routine returns without signalling a RUN_RESTART error, TADS + *   will terminate.  If a RUN_RESTART error is signalled, TADS will + *   resume the play loop.   + */ +void dbguquitting(dbgcxdef *ctx); + +/*  + *   debugger user interface termination - this routine is called when the + *   debugger is about to terminate, so that the user interface can close + *   itself down (close windows, release memory, etc)  + */ +void dbguterm(dbgcxdef *ctx); + +/* + *   Debugger user interface: display an error.  This is called mainly so + *   that the debugger can display an error using special output + *   formatting if the error occurs while debugging.  + */ +void dbguerr(dbgcxdef *ctx, int errnum, char *msg); + +/* turn hidden output tracing on/off */ +void trchid(void); +void trcsho(void); + + +/* ======================================================================== */ +/* + *   optional debugger macros - these compile to nothing when compiling a + *   version for use without the debugger  + */ + +#ifdef DBG_OFF +# define dbgenter(ctx, bp, self, target, prop, binum, argc) +# define dbgleave(ctx, exittype) +# define dbgdump(ctx) +# define dbgrst(ctx) ((void)0) +# define dbgframe(ctx, frofs, linofs) +# define dbgssi(ctx, ofs, instr, err, p) +#else /* DBG_OFF */ +# define dbgenter(ctx, bp, self, target, prop, binum, argc) \ +   dbgent(ctx, bp, self, target, prop, binum, argc) +# define dbgleave(ctx, exittype) dbglv(ctx, exittype) +# define dbgdump(ctx) dbgds(ctx) +# define dbgrst(ctx) ((ctx)->dbgcxfcn = (ctx)->dbgcxdep = 0) +# define dbgframe(ctx, frofs, linofs) \ +   (((ctx)->dbgcxfrm[(ctx)->dbgcxfcn - 1].dbgffr = (frofs)), \ +    ((ctx)->dbgcxfrm[(ctx)->dbgcxfcn - 1].dbgflin = (linofs))) +# define dbgssi(ctx, ofs, instr, err, p) dbgss(ctx, ofs, instr, err, p) +#endif /* DBG_OFF */ + +/* ======================================================================== */ +/* private internal routines */ + +void dbgent(dbgcxdef *ctx, struct runsdef *bp, objnum self, objnum target, +            prpnum prop, int binum, int argc); + +void dbglv(dbgcxdef *ctx, int exittype); + +void dbgds(dbgcxdef *ctx); + +void dbgss(dbgcxdef *ctx, uint ofs, int instr, int err, uchar *noreg *p); + +void dbgpval(struct dbgcxdef *ctx, struct runsdef *val, +             void (*dispfn)(void *, const char *, int), +             void *dispctx, int showtype); + +int dbgtabsea(struct toktdef *tab, char *name, int namel, int hash, +              struct toksdef *ret); + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/error.cpp b/engines/glk/tads/tads2/error.cpp new file mode 100644 index 0000000000..793ddccb1d --- /dev/null +++ b/engines/glk/tads/tads2/error.cpp @@ -0,0 +1,32 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/tads2/error.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/tads2/error.h b/engines/glk/tads/tads2/error.h new file mode 100644 index 0000000000..96831068d7 --- /dev/null +++ b/engines/glk/tads/tads2/error.h @@ -0,0 +1,390 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* Error handling + * This package defines a set of macros that allows code to raise and + * handle exceptions.A macro is provided which signals an error, which + * does a non - local goto to the innermost enclosing exception handler. + * A set of macros sets up exception handling code. + *  + * To catch exceptions that occur inside a block of code(i.e., in the + * code or in any subroutines called by the code), begin the block with + * ERRBEGIN.At the end of the protected code, place the exception + * handler, which starts with ERRCATCH.At the end of the exception + * handler, place ERREND.If no exception occurs, execution goes + * through the protected code, then resumes at the code following + * the ERREND. + *  + * The exception handler can signal another error, which will cause + * the next enclosing frame to catch the error.Alternatively, if + * the exception handler doesn't signal an error or return, execution + * continues at the code following the ERREND.Exceptions that are + * signalled during exception handling will be caught by the next + * enclosing frame, unless the exception handler code is itself + * protected by another ERRBEGIN - ERREND block. + *  + * To signal an error, use errsig(). + *  + * To use a string argument in a signalled error, cover the string + * with errstr(ctx, str, len); for example: + *  + * errsig1(ctx, ERR_XYZ, ERRTSTR, errstr(ctx, buf, strlen(buf))); + *  + * This copies the string into a buffer that is unaffected by + * stack resetting during error signalling. + */ + +#ifndef GLK_TADS_TADS2_ERROR +#define GLK_TADS_TADS2_ERROR + +#include "glk/tads/tads2/lib.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* + *   for compatility with old facility-free mechanism, signal with + *   facility "TADS"  + */ +#define errsig(ctx, err) errsigf(ctx, "TADS", err) +#define errsig1(c, e, t, a) errsigf1(c,"TADS",e,t,a) +#define errsig2(c, e, t1, a1, t2, a2) errsigf2(c,"TADS",e,t1,a1,t2,a2) +#define errlog(c, e) errlogf(c, "TADS", e) +#define errlog1(c, e, t, a) errlogf1(c,"TADS",e,t,a) +#define errlog2(c, e, t1, a1, t2, a2) errlogf2(c,"TADS",e,t1,a1,t2,a2) + + + +/*---------------------------- TADS Error Codes ----------------------------*/ +/* memory/cache manager errors */ +#define ERR_NOMEM   1                                      /* out of memory */ +#define ERR_FSEEK   2                              /* error seeking in file */ +#define ERR_FREAD   3                            /* error reading from file */ +#define ERR_NOPAGE  4                                 /* no more page slots */ +#define ERR_REALCK  5           /* attempting to reallocate a locked object */ +#define ERR_SWAPBIG 6     /* swapfile limit reached - out of virtual memory */ +#define ERR_FWRITE  7                                 /* error writing file */ +#define ERR_SWAPPG  8                     /* exceeded swap page table limit */ +#define ERR_CLIUSE  9      /* requested client object number already in use */ +#define ERR_CLIFULL 10                      /* client mapping table is full */ +#define ERR_NOMEM1  11   /* swapping/garbage collection failed to find enuf */ +#define ERR_NOMEM2  12            /* no memory to resize (expand) an object */ +#define ERR_OPSWAP  13                          /* unable to open swap file */ +#define ERR_NOHDR   14                     /* can't get a new object header */ +#define ERR_NOLOAD  15   /* mcm cannot find object to load (internal error) */ +#define ERR_LCKFRE  16     /* attempting to free a locked object (internal) */ +#define ERR_INVOBJ  17                                    /* invalid object */ +#define ERR_BIGOBJ  18  /* object too big - exceeds memory allocation limit */ + +/* lexical analysis errors */ +#define ERR_INVTOK  100                                    /* invalid token */ +#define ERR_STREOF  101                /* end of file while scanning string */ +#define ERR_TRUNC   102                      /* symbol too long - truncated */ +#define ERR_NOLCLSY 103                   /* no space in local symbol table */ +#define ERR_PRPDIR  104               /* invalid preprocessor (#) directive */ +#define ERR_INCNOFN 105                /* no filename in #include directive */ +#define ERR_INCSYN  106                          /* invalid #include syntax */ +#define ERR_INCSEAR 107                         /* can't find included file */ +#define ERR_INCMTCH 108       /* no matching delimiter in #include filename */ +#define ERR_MANYSYM 109                    /* out of space for symbol table */ +#define ERR_LONGLIN 110                                    /* line too long */ +#define ERR_INCRPT  111           /* header file already included - ignored */ +#define ERR_PRAGMA  112                         /* unknown pragma (ignored) */ +#define ERR_BADPELSE 113                                /* unexpected #else */ +#define ERR_BADENDIF 114                               /* unexpected #endif */ +#define ERR_BADELIF 115                                 /* unexpected #elif */ +#define ERR_MANYPIF 116                             /* #if nesting too deep */ +#define ERR_DEFREDEF  117                 /* #define symbol already defined */ +#define ERR_PUNDEF  118                        /* #undef symbol not defined */ +#define ERR_NOENDIF 119                                   /* missing #endif */ +#define ERR_MACNEST 120                         /* macros nested too deeply */ +#define ERR_BADISDEF 121         /* invalid argument for defined() operator */ +#define ERR_PIF_NA  122                           /* #if is not implemented */ +#define ERR_PELIF_NA 123                        /* #elif is not implemented */ +#define ERR_P_ERROR 124                              /* error directive: %s */ +#define ERR_LONG_FILE_MACRO 125              /* __FILE__ expansion too long */ +#define ERR_LONG_LINE_MACRO 126              /* __LINE__ expansion too long */ + +/* undo errors */ +#define ERR_UNDOVF  200                /* operation is too big for undo log */ +#define ERR_NOUNDO  201                         /* no more undo information */ +#define ERR_ICUNDO  202          /* incomplete undo (no previous savepoint) */ + +/* parser errors */ +#define ERR_REQTOK  300    /* expected token (arg 1) - found something else */ +#define ERR_REQSYM  301                                  /* expected symbol */ +#define ERR_REQPRP  302                         /* expected a property name */ +#define ERR_REQOPN  303                                 /* expected operand */ +#define ERR_REQARG  304       /* expected comma or closing paren (arg list) */ +#define ERR_NONODE  305                      /* no space for new parse node */ +#define ERR_REQOBJ  306                             /* epxected object name */ +#define ERR_REQEXT  307           /* redefining symbol as external function */ +#define ERR_REQFCN  308                    /* redefining symbol as function */ +#define ERR_NOCLASS 309  /* can't use CLASS with function/external function */ +#define ERR_REQUNO  310                          /* required unary operator */ +#define ERR_REQBIN  311                         /* required binary operator */ +#define ERR_INVBIN  312                          /* invalid binary operator */ +#define ERR_INVASI  313                               /* invalid assignment */ +#define ERR_REQVAR  314                           /* required variable name */ +#define ERR_LCLSYN  315        /* required comma or semicolon in local list */ +#define ERR_REQRBR  316   /* required right brace (eof before end of group) */ +#define ERR_BADBRK  317                          /* 'break' without 'while' */ +#define ERR_BADCNT  318                       /* 'continue' without 'while' */ +#define ERR_BADELS  319                              /* 'else' without 'if' */ +#define ERR_WEQASI  320 /* warning: possible use of '=' where ':=' intended */ +#define ERR_EOF     321                           /* unexpected end of file */ +#define ERR_SYNTAX  322                             /* general syntax error */ +#define ERR_INVOP   323                             /* invalid operand type */ +#define ERR_NOMEMLC 324             /* no memory for new local symbol table */ +#define ERR_NOMEMAR 325              /* no memory for argument symbol table */ +#define ERR_FREDEF  326   /* redefining a function which is already defined */ +#define ERR_NOSW    327      /* 'case' or 'default' and not in switch block */ +#define ERR_SWRQCN  328           /* constant required in switch case value */ +#define ERR_REQLBL  329                        /* label required for 'goto' */ +#define ERR_NOGOTO  330                       /* 'goto' label never defined */ +#define ERR_MANYSC  331                 /* too many superclasses for object */ +#define ERR_OREDEF  332                      /* redefining symbol as object */ +#define ERR_PREDEF  333               /* property being redefined in object */ +#define ERR_BADPVL  334                           /* invalid property value */ +#define ERR_BADVOC  335                    /* bad vocabulary property value */ +#define ERR_BADTPL  336       /* bad template property value (need sstring) */ +#define ERR_LONGTPL 337             /* template base property name too long */ +#define ERR_MANYTPL 338     /* too many templates (internal compiler limit) */ +#define ERR_BADCMPD 339   /* bad value for compound word (sstring required) */ +#define ERR_BADFMT  340     /* bad value for format string (sstring needed) */ +#define ERR_BADSYN  341     /* invalid value for synonym (sstring required) */ +#define ERR_UNDFSYM 342                                 /* undefined symbol */ +#define ERR_BADSPEC 343                                 /* bad special word */ +#define ERR_NOSELF  344                 /* "self" not valid in this context */ +#define ERR_STREND  345            /* warning: possible unterminated string */ +#define ERR_MODRPLX 346    /* modify/replace not allowed with external func */ +#define ERR_MODFCN  347                 /* modify not allowed with function */ +#define ERR_MODFWD  348     /* modify/replace not allowed with forward func */ +#define ERR_MODOBJ  349    /* modify can only be used with a defined object */ +#define ERR_RPLSPEC 350                 /* warning - replacing specialWords */ +#define ERR_SPECNIL 351        /* nil only allowed with modify specialWords */ +#define ERR_BADLCL  353   /* 'local' statement must precede executable code */ +#define ERR_IMPPROP 354          /* implied verifier '%s' is not a property */ +#define ERR_BADTPLF 355                    /* invalid command template flag */ +#define ERR_NOTPLFLG 356      /* flags are not allowed with old file format */ +#define ERR_AMBIGBIN 357          /* warning: operator '%s' could be binary */ +#define ERR_PIA     358           /* warning: possibly incorrect assignment */ +#define ERR_BADSPECEXPR 359               /* invalid speculation evaluation */ + +/* code generation errors */ +#define ERR_OBJOVF  400     /* object cannot grow any bigger - code too big */ +#define ERR_NOLBL   401                  /* no more temporary labels/fixups */ +#define ERR_LBNOSET 402                 /* (internal error) label never set */ +#define ERR_INVLSTE 403                /* invalid datatype for list element */ +#define ERR_MANYDBG 404  /* too many debugger line records (internal limit) */ + +/* vocabulary setup errors */ +#define ERR_VOCINUS 450            /* vocabulary being redefined for object */ +#define ERR_VOCMNPG 451          /* too many vocwdef pages (internal limit) */ +#define ERR_VOCREVB 452                             /* redefining same verb */ +#define ERR_VOCREVB2 453            /* redefining same verb - two arguments */ + +/* set-up errors */ +#define ERR_LOCNOBJ 500           /* location of object %s is not an object */ +#define ERR_CNTNLST 501                /* contents of object %s is not list */ +#define ERR_SUPOVF  502           /* overflow trying to build contents list */ +#define ERR_RQOBJNF 503                     /* required object %s not found */ +#define ERR_WRNONF  504                    /* warning - object %s not found */ +#define ERR_MANYBIF 505     /* too many built-in functions (internal error) */ + +/* fio errors */ +#define ERR_OPWGAM  600                  /* unable to open game for writing */ +#define ERR_WRTGAM  601                       /* error writing to game file */ +#define ERR_FIOMSC  602              /* too many sc's for writing in fiowrt */ +#define ERR_UNDEFF  603                               /* undefined function */ +#define ERR_UNDEFO  604                                 /* undefined object */ +#define ERR_UNDEF   605                          /* undefined symbols found */ +#define ERR_OPRGAM  606                  /* unable to open game for reading */ +#define ERR_RDGAM   607                          /* error reading game file */ +#define ERR_BADHDR  608     /* file has invalid header - not TADS game file */ +#define ERR_UNKRSC  609               /* unknown resource type in .gam file */ +#define ERR_UNKOTYP 610              /* unknown object type in OBJ resource */ +#define ERR_BADVSN  611     /* file saved by different incompatible version */ +#define ERR_LDGAM   612                   /* error loading object on demand */ +#define ERR_LDBIG   613  /* object too big for load region (prob. internal) */ +#define ERR_UNXEXT  614                 /* did not expect external function */ +#define ERR_WRTVSN  615      /* compiler cannot write the requested version */ +#define ERR_VNOCTAB 616         /* format version cannot be used with -ctab */ +#define ERR_BADHDRRSC 617        /* invalid resource file header in file %s */ +#define ERR_RDRSC   618                /* error reading resource file "xxx" */ + +/* character mapping errors */ +#define ERR_CHRNOFILE 700          /* unable to load character mapping file */ + +/* user interrupt */ +#define ERR_USRINT  990       /* user requested cancel of current operation */ + +/* run-time errors */ +#define ERR_STKOVF  1001                                  /* stack overflow */ +#define ERR_HPOVF   1002                                   /* heap overflow */ +#define ERR_REQNUM  1003                          /* numeric value required */ +#define ERR_STKUND  1004                                 /* stack underflow */ +#define ERR_REQLOG  1005                          /* logical value required */ +#define ERR_INVCMP  1006      /* invalid datatypes for magnitude comparison */ +#define ERR_REQSTR  1007                           /* string value required */ +#define ERR_INVADD  1008              /* invalid datatypes for '+' operator */ +#define ERR_INVSUB  1009       /* invalid datatypes for binary '-' operator */ +#define ERR_REQVOB  1010                            /* require object value */ +#define ERR_REQVFN  1011                       /* required function pointer */ +#define ERR_REQVPR  1012                  /* required property number value */ + +/* non-error conditions:  run-time EXIT, ABORT, ASKIO, ASKDO */ +#define ERR_RUNEXIT 1013                       /* 'exit' statement executed */ +#define ERR_RUNABRT 1014                      /* 'abort' statement executed */ +#define ERR_RUNASKD 1015                      /* 'askdo' statement executed */ +#define ERR_RUNASKI 1016             /* 'askio' executed; int arg 1 is prep */ +#define ERR_RUNQUIT 1017                                 /* 'quit' executed */ +#define ERR_RUNRESTART 1018                             /* 'reset' executed */ +#define ERR_RUNEXITOBJ 1019                           /* 'exitobj' executed */ + +#define ERR_REQVLS  1020                             /* list value required */ +#define ERR_LOWINX  1021              /* index value too low (must be >= 1) */ +#define ERR_HIGHINX 1022  /* index value too high (must be <= length(list)) */ +#define ERR_INVTBIF 1023              /* invalid type for built-in function */ +#define ERR_INVVBIF 1024             /* invalid value for built-in function */ +#define ERR_BIFARGC 1025           /* wrong number of arguments to built-in */ +#define ERR_ARGC    1026      /* wrong number of arguments to user function */ +#define ERR_FUSEVAL 1027     /* string/list not allowed for fuse/daemon arg */ +#define ERR_BADSETF 1028      /* internal error in setfuse/setdaemon/notify */ +#define ERR_MANYFUS 1029                                  /* too many fuses */ +#define ERR_MANYDMN 1030                                /* too many daemons */ +#define ERR_MANYNFY 1031                              /* too many notifiers */ +#define ERR_NOFUSE  1032                       /* fuse not found in remfuse */ +#define ERR_NODMN   1033                   /* daemon not found in remdaemon */ +#define ERR_NONFY   1034                  /* notifier not found in unnotify */ +#define ERR_BADREMF 1035    /* internal error in remfuse/remdaemon/unnotify */ +#define ERR_DMDLOOP 1036     /* load-on-demand loop: property not being set */ +#define ERR_UNDFOBJ 1037             /* undefined object in vocabulary tree */ +#define ERR_BIFCSTR 1038            /* c-string conversion overflows buffer */ +#define ERR_INVOPC  1039                                  /* invalid opcode */ +#define ERR_RUNNOBJ 1040     /* runtime error: property taken of non-object */ +#define ERR_EXTLOAD 1041           /* unable to load external function "%s" */ +#define ERR_EXTRUN  1042          /* error executing external function "%s" */ +#define ERR_CIRCSYN 1043                                /* circular synonym */ +#define ERR_DIVZERO 1044                                  /* divide by zero */ +#define ERR_BADDEL  1045      /* can only delete objects created with "new" */ +#define ERR_BADNEWSC 1046    /* superclass for "new" cannot be a new object */ +#define ERR_VOCSTK  1047              /* insufficient space in parser stack */ +#define ERR_BADFILE 1048                             /* invalid file handle */ + +#define ERR_RUNEXITPRECMD 1049                    /* exited from preCommand */ + +/* run-time parser errors */ +#define ERR_PRS_SENT_UNK  1200         /* sentence structure not recognized */ +#define ERR_PRS_VERDO_FAIL 1201                         /* verDoVerb failed */ +#define ERR_PRS_VERIO_FAIL 1202                         /* verIoVerb failed */ +#define ERR_PRS_NO_VERDO  1203            /* no verDoVerb for direct object */ +#define ERR_PRS_NO_VERIO  1204            /* no verIoVerb for direct object */ +#define ERR_PRS_VAL_DO_FAIL  1205        /* direct object validation failed */ +#define ERR_PRS_VAL_IO_FAIL  1206      /* indirect object validation failed */ + +/* compiler/runtime/debugger driver errors */ +#define ERR_USAGE   1500                                   /* invalid usage */ +#define ERR_OPNINP  1501                        /* error opening input file */ +#define ERR_NODBG   1502                 /* game not compiled for debugging */ +#define ERR_ERRFIL  1503               /* unable to open error capture file */ +#define ERR_PRSCXSIZ 1504              /* parse pool + local size too large */ +#define ERR_STKSIZE 1505                            /* stack size too large */ +#define ERR_OPNSTRFIL 1506             /* error opening string capture file */ +#define ERR_INVCMAP 1507                      /* invalid character map file */ + +/* debugger errors */ +#define ERR_BPSYM   2000                 /* symbol not found for breakpoint */ +#define ERR_BPPROP  2002             /* breakpoint symbol is not a property */ +#define ERR_BPFUNC  2003             /* breakpoint symbol is not a function */ +#define ERR_BPNOPRP 2004              /* property is not defined for object */ +#define ERR_BPPRPNC 2005                            /* property is not code */ +#define ERR_BPSET   2006         /* breakpoint already set at this location */ +#define ERR_BPNOTLN 2007     /* breakpoint is not at a line (OPCLINE instr) */ +#define ERR_MANYBP  2008                            /* too many breakpoints */ +#define ERR_BPNSET  2009            /* breakpoint to be deleted was not set */ +#define ERR_DBGMNSY 2010  /* too many symbols in debug expression (int lim) */ +#define ERR_NOSOURC 2011                   /* unable to find source file %s */ +#define ERR_WTCHLCL 2012        /* illegal to assign to local in watch expr */ +#define ERR_INACTFR 2013 /* inactive frame (expression value not available) */ +#define ERR_MANYWX  2014                      /* too many watch expressions */ +#define ERR_WXNSET  2015                              /* watchpoint not set */ +#define ERR_EXTRTXT 2016               /* extraneous text at end of command */ +#define ERR_BPOBJ   2017              /* breakpoint symbol is not an object */ +#define ERR_DBGINACT 2018                         /* debugger is not active */ +#define ERR_BPINUSE 2019                      /* breakpoint is already used */ +#define ERR_RTBADSPECEXPR 2020            /* invalid speculative expression */ +#define ERR_NEEDLIN2 2021    /* -ds2 information not found - must recompile */ + +/* usage error messages */ +#define ERR_TCUS1   3000                          /* first tc usage message */ +#define ERR_TCUSL   3024                           /* last tc usage message */ +#define ERR_TCTGUS1 3030                         /* first tc toggle message */ +#define ERR_TCTGUSL 3032 +#define ERR_TCZUS1  3040            /* first tc -Z suboptions usage message */ +#define ERR_TCZUSL  3041 +#define ERR_TC1US1  3050            /* first tc -1 suboptions usage message */ +#define ERR_TC1USL  3058 +#define ERR_TCMUS1  3070            /* first tc -m suboptions usage message */ +#define ERR_TCMUSL  3076 +#define ERR_TCVUS1  3080                /* first -v suboption usage message */ +#define ERR_TCVUSL  3082 +#define ERR_TRUSPARM 3099 +#define ERR_TRUS1   3100                          /* first tr usage message */ +#define ERR_TRUSL   3117 +#define ERR_TRUSFT1 3118                       /* first tr "footer" message */ +#define ERR_TRUSFTL 3119                        /* last tr "footer" message */ +#define ERR_TRSUS1  3150            /* first tr -s suboptions usage message */ +#define ERR_TRSUSL  3157 +#define ERR_TDBUSPARM 3199 +#define ERR_TDBUS1  3200                         /* first tdb usage message */ +#define ERR_TDBUSL  3214                          /* last tdb usage message */ + +/* TR 16-bit MSDOS-specific usage messages */ +#define ERR_TRUS_DOS_1 3300 +#define ERR_TRUS_DOS_L 3300 + +/* TR 32-bit MSDOS console mode usage messages */ +#define ERR_TRUS_DOS32_1  3310 +#define ERR_TRUS_DOS32_L  3312 + +/* TADS/Graphic errors */ +#define ERR_GNOFIL  4001                     /* can't find graphics file %s */ +#define ERR_GNORM   4002                              /* can't find room %s */ +#define ERR_GNOOBJ  4003                   /* can't find hot spot object %s */ +#define ERR_GNOICN  4004                       /* can't find icon object %s */ + + +/* + *   Special error flag - this is returned from execmd() when preparseCmd + *   returns a command list.  This indicates to voc1cmd that it should try + *   the command over again, using the words in the new list.  + */ +#define ERR_PREPRSCMDREDO  30000             /* preparseCmd returned a list */ +#define ERR_PREPRSCMDCAN   30001    /* preparseCmd returned 'nil' to cancel */ + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/error_handling.cpp b/engines/glk/tads/tads2/error_handling.cpp new file mode 100644 index 0000000000..a2300ee834 --- /dev/null +++ b/engines/glk/tads/tads2/error_handling.cpp @@ -0,0 +1,32 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "error_handling.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/tads2/error_handling.h b/engines/glk/tads/tads2/error_handling.h new file mode 100644 index 0000000000..a225bbea74 --- /dev/null +++ b/engines/glk/tads/tads2/error_handling.h @@ -0,0 +1,338 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* Library error handling definitions + * All of the functions and macros in here are named ERRxxx because + * this file was based on the TADS err.h, which used the ERRxxx naming + * convention, and it would be a lot of trouble to change. + *  + * This package defines a set of macros that allows code to raise and + * handle exceptions.  A macro is provided which signals an error, which + * does a non-local goto to the innermost enclosing exception handler. + * A set of macros sets up exception handling code. + *  + * To catch exceptions that occur inside a block of code (i.e., in the + * code or in any subroutines called by the code), begin the block with + * ERRBEGIN.  At the end of the protected code, place the exception + * handler, which starts with ERRCATCH.  At the end of the exception + * handler, place ERREND.  If no exception occurs, execution goes + * through the protected code, then resumes at the code following + * the ERREND. + *  + * The exception handler can signal another error, which will cause + * the next enclosing frame to catch the error.  Alternatively, if + * the exception handler doesn't signal an error or return, execution + * continues at the code following the ERREND.  Exceptions that are + * signalled during exception handling will be caught by the next + * enclosing frame, unless the exception handler code is itself + * protected by another ERRBEGIN-ERREND block. + *  + * To signal an error, use errsig(). + *  + * To use a string argument in a signalled error, cover the string + * with errstr(ctx, str, len); for example: + *  + * errsig1(ctx, ERR_XYZ, ERRTSTR, errstr(ctx, buf, strlen(buf))); + *  + * This copies the string into a buffer that is unaffected by + * stack resetting during error signalling. + */ + +#ifndef GLK_TADS_TADS2_ERROR_HANDLING +#define GLK_TADS_TADS2_ERROR_HANDLING + +#include "glk/tads/tads2/lib.h" +#include "glk/tads/osfrobtads.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +// TODO: Clean away use of jmp_buf, since ScummVM doesn't allow it +struct jmp_buf {}; + +/** + * Maximum length of a facility identifier + */ +#define ERRFACMAX    6 + +union erradef { +    int   erraint;                                      /* integer argument */ +    char *errastr;                                  /* text string argument */ +}; + +struct errdef { +    struct errdef *errprv;                          /* previous error frame */ +    int            errcode;        /* error code of exception being handled */ +    char           errfac[ERRFACMAX+1];        /* facility of current error */ +    erradef        erraav[10];                      /* parameters for error */ +    int            erraac;                   /* count of parameters in argc */ +    jmp_buf        errbuf;           /* jump buffer for current error frame */ +}; + +#define ERRBUFSIZ 512 + +/** + * Seek location record for an error message by number + */ +struct errmfdef { +    uint  errmfnum;                                         /* error number */ +    ulong errmfseek;                       /* seek location of this message */ +}; +typedef struct errmfdef errmfdef; + +struct errcxdef { +    errdef   *errcxptr;                              /* current error frame */ +    void    (*errcxlog)(void *, char *fac, int err, int argc, erradef *); +                                         /* error logging callback function */ +    void     *errcxlgc;               /* context for error logging callback */ +    int       errcxofs;                        /* offset in argument buffer */ +    char      errcxbuf[ERRBUFSIZ];            /* space for argument strings */ +    osfildef *errcxfp;                /* message file, if one is being used */ +    errmfdef *errcxseek;              /* seek locations of messages in file */ +    uint      errcxsksz;                         /* size of errcxseek array */ +    ulong     errcxbase;   /* offset in physical file of logical error file */ +    struct appctxdef *errcxappctx;              /* host application context */ +}; + +/** + * Begin protected code + */ +#define ERRBEGIN(ctx) \ +  { \ +    errdef fr_; \ +    if ((fr_.errcode = setjmp(fr_.errbuf)) == 0) \ +    { \ +      fr_.errprv = (ctx)->errcxptr; \ +      (ctx)->errcxptr = &fr_; + +/** + * End protected code, begin error handler + */ +#define ERRCATCH(ctx, e) \ +      assert(1==1 && (ctx)->errcxptr != fr_.errprv); \ +      (ctx)->errcxptr = fr_.errprv; \ +    } \ +    else \ +    { \ +      assert(2==2 && (ctx)->errcxptr != fr_.errprv); \ +      (e) = fr_.errcode; \ +      (ctx)->errcxptr = fr_.errprv; +         +/* retrieve argument (int, string) in current error frame */ +#define errargint(argnum) (fr_.erraav[argnum].erraint) +#define errargstr(argnum) (fr_.erraav[argnum].errastr) + +     +#define ERREND(ctx) \ +    } \ +  } + +/* end protected code, begin cleanup (no handling; just cleaning up) */ +#define ERRCLEAN(ctx) \ +      assert((ctx)->errcxptr != fr_.errprv); \ +      (ctx)->errcxptr = fr_.errprv; \ +    } \ +    else \ +    { \ +      assert((ctx)->errcxptr != fr_.errprv); \ +      (ctx)->errcxptr = fr_.errprv; + +#define ERRENDCLN(ctx) \ +      errrse(ctx); \ +    } \ +  } + + + +/* argument types for errors with arguments */ +#define ERRTINT  erraint +#define ERRTSTR  errastr + +/* set argument count in error frame */ +#define errargc(ctx,cnt) ((ctx)->errcxptr->erraac=(cnt)) + +/* enter string argument; returns pointer to argument used in errargv */ +#ifdef ERR_NO_MACRO +char *errstr(errcxdef *ctx, const char *str, int len); +#else /* ERR_NO_MACRO */ +   +#define errstr(ctx,str,len) \ +  ((memcpy(&(ctx)->errcxbuf[(ctx)->errcxofs],str,(size_t)len), \ +   (ctx)->errcxofs += (len), \ +   (ctx)->errcxbuf[(ctx)->errcxofs++] = '\0'), \ +   &(ctx)->errcxbuf[(ctx)->errcxofs-(len)-1]) + +#endif /* ERR_NO_MACRO */ + +/* set argument in error frame argument vector */ +#define errargv(ctx,index,typ,arg) \ +  ((ctx)->errcxptr->erraav[index].typ=(arg)) + +/* signal an error with argument count already set */ +#ifdef ERR_NO_MACRO +void errsign(errcxdef *ctx, int e, char *facility); +#else /* ERR_NO_MACRO */ +# ifdef DEBUG +void errjmp(jmp_buf buf, int e); +#  define errsign(ctx, e, fac) \ +   (strncpy((ctx)->errcxptr->errfac, fac, ERRFACMAX),\ +    (ctx)->errcxptr->errfac[ERRFACMAX]='\0',\ +    (ctx)->errcxofs=0, errjmp((ctx)->errcxptr->errbuf, e)) +# else /* DEBUG */ +#  define errsign(ctx, e, fac) \ +   (strncpy((ctx)->errcxptr->errfac, fac, ERRFACMAX),\ +    (ctx)->errcxptr->errfac[ERRFACMAX]='\0',\ +    (ctx)->errcxofs=0, longjmp((ctx)->errcxptr->errbuf, e)) +# endif /* DEBUG */ +#endif /* ERR_NO_MACRO */ + + +/* signal an error with no arguments */ +#ifdef ERR_NO_MACRO +void errsigf(errcxdef *ctx, char *facility, int err); +#else /* ERR_NO_MACRO */ +#define errsigf(ctx, fac, e) (errargc(ctx,0),errsign(ctx,e,fac)) +#endif /* ERR_NO_MACRO */ +   +/* signal an error with one argument */ +#define errsigf1(ctx, fac, e, typ1, arg1) \ +  (errargv(ctx,0,typ1,arg1),errargc(ctx,1),errsign(ctx,e,fac)) + +/* signal an error with two arguments */ +#define errsigf2(ctx, fac, e, typ1, arg1, typ2, arg2) \ +  (errargv(ctx,0,typ1,arg1), errargv(ctx,1,typ2,arg2), \ +   errargc(ctx,2), errsign(ctx,e,fac)) + +/* resignal the current error - only usable within exception handlers */ +#ifdef ERR_NO_MACRO +void errrse1(errcxdef *ctx, errdef *fr); +# define errrse(ctx) errrse1(ctx, &fr_) +#else /* ERR_NO_MACRO */ + +/* void errrse(errcxdef *ctx); */ +# define errrse(ctx) \ +  (errargc(ctx, fr_.erraac),\ +   memcpy((ctx)->errcxptr->erraav, fr_.erraav, \ +    (size_t)(fr_.erraac*sizeof(erradef))),\ +   errsign(ctx, fr_.errcode, fr_.errfac)) + +#endif /* ERR_NO_MACRO */ + +/* + *   For use in an error handler (ERRCATCH..ERREND) only: Copy the + *   parameters from the error currently being handled to the enclosing + *   frame.  This is useful when "keeping" an error being handled - i.e., + *   the arguments will continue to be used outside of the + *   ERRCATCH..ERREND code.  + */ +/* void errkeepargs(errcxdef *ctx); */ +#define errkeepargs(ctx) errcopyargs(ctx, &fr_) + +/*  + *   copy the parameters for an error from another frame into the current + *   frame - this can be used when we want to be able to display an error + *   that occurred in an inner frame within code that is protected by a + *   new enclosing error frame  + */ +/* void errcopyargs(errcxdef *ctx, errdef *fr); */ +#define errcopyargs(ctx, fr) \ +   (errargc((ctx), (fr)->erraac), \ +    memcpy((ctx)->errcxptr->erraav, (fr)->erraav, \ +           (size_t)((fr)->erraac*sizeof(erradef)))) + +/* log error that's been caught, using arguments already caught */ +#define errclog(ctx) \ + ((*(ctx)->errcxlog)((ctx)->errcxlgc,fr_.errfac,fr_.errcode,\ +  fr_.erraac,fr_.erraav)) + +/* log an error that's been set up but not signalled yet */ +#define errprelog(ctx, err) \ + ((*(ctx)->errcxlog)((ctx)->errcxlgc,(ctx)->errcxptr->errfac,\ +   err,(ctx)->errcxptr->erraac,\ +   (ctx)->errcxptr->erraav)) + +/* log an error (no signalling, just reporting) */ +#ifdef ERR_NO_MACRO +void errlogn(errcxdef *ctx, int err, char *facility); +#else /* ERR_NO_MACRO */ + +#define errlogn(ctx,err,fac) \ + ((ctx)->errcxofs=0,\ +  (*(ctx)->errcxlog)((ctx)->errcxlgc,fac,err,(ctx)->errcxptr->erraac,\ +  (ctx)->errcxptr->erraav)) + +#endif /* ERR_NO_MACRO */ + +/* log an error with no arguments */ +#ifdef ERR_NO_MACRO +void errlogf(errcxdef *ctx, char *facility, int err); +#else /* ERR_NO_MACRO */ + +/* void errlogf(errcxdef *ctx, char *facility, int err); */ +#define errlogf(ctx,fac,err) (errargc(ctx,0),errlogn(ctx,err,fac)) + +#endif /* ERR_NO_MACRO */ + +/* log an error with one argument */ +#define errlogf1(ctx, fac, e, typ1, arg1) \ + (errargv(ctx,0,typ1,arg1),errargc(ctx,1),errlogn(ctx,e,fac)) +   +/* log an error with two arguments */ +#define errlogf2(ctx, fac, e, typ1, arg1, typ2, arg2) \ + (errargv(ctx,0,typ1,arg1),errargv(ctx,1,typ2,arg2),\ +  errargc(ctx,2),errlogn(ctx,e,fac)) + + +/* + *   Format an error message, sprintf-style, using arguments in an + *   erradef array (which is passed to the error-logging callback). + *   Returns the length of the output string, even if the actual + *   output string was truncated because the outbuf was too short. + *   (If called with outbufl == 0, nothing will be written out, but + *   the size of the buffer needed, minus the terminating null byte, + *   will be computed and returned.) + */ +int errfmt(char *outbuf, int outbufl, char *fmt, int argc, +           erradef *argv); +   +/* get the text of an error */ +void errmsg(errcxdef *ctx, char *outbuf, uint outbufl, uint err); +   +/* initialize error subsystem, opening error message file if necessary */ +void errini(errcxdef *ctx, osfildef *fp); + +/* allocate and initialize error context, free error context */ +errcxdef *lerini(); +void      lerfre(errcxdef *ctx); + +/* error message structure - number + text */ +struct errmdef { +    uint   errmerr;                                         /* error number */ +    char  *errmtxt;                                /* text of error message */ +}; + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/file_io.cpp b/engines/glk/tads/tads2/file_io.cpp new file mode 100644 index 0000000000..581601fbf0 --- /dev/null +++ b/engines/glk/tads/tads2/file_io.cpp @@ -0,0 +1,32 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/tads2/file_io.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/tads2/file_io.h b/engines/glk/tads/tads2/file_io.h new file mode 100644 index 0000000000..8599c4909f --- /dev/null +++ b/engines/glk/tads/tads2/file_io.h @@ -0,0 +1,137 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * file i/o interface + */ + +#ifndef GLK_TADS_TADS2_FILE_IO +#define GLK_TADS_TADS2_FILE_IO + +#include "glk/tads/tads2/lib.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* forward declarations */ +struct voccxdef; + +/* load-on-demand context (passed in by mcm in load callback) */ +typedef struct fiolcxdef fiolcxdef; +struct fiolcxdef +{ +    osfildef *fiolcxfp;                        /* file pointer of load file */ +    errcxdef *fiolcxerr;                          /* error handling context */ +    ulong     fiolcxst;                          /* starting offset in file */ +    uint      fiolcxflg;                   /* flags from original load file */ +    uint      fiolcxseed;                                    /* fioxor seed */ +    uint      fiolcxinc;                                /* fioxor increment */ +}; + +/* write game to binary file */ +void fiowrt(struct mcmcxdef *mctx, voccxdef *vctx, +            struct tokcxdef *tokctx, struct tokthdef *tab, +            uchar *fmts, uint fmtl, char *fname, uint flags, objnum preinit, +            int extc, uint prpcnt, char *filever); + +/* flag values for use with fiowrt */ +#define FIOFSYM   0x01               /* include symbol table in output file */ +#define FIOFLIN   0x02          /* include source file tracking information */ +#define FIOFPRE   0x04        /* preinit needs to be run after reading game */ +#define FIOFCRYPT 0x08           /* "encrypt" objects prior to writing them */ +#define FIOFBIN   0x10                        /* writing precompiled header */ +#define FIOFFAST  0x20                     /* fast-load records are in file */ +#define FIOFCASE  0x40    /* case folding was turned on in original compile */ +#define FIOFLIN2  0x80                            /* new-style line records */ + +/* read game from binary file; sets up loader callback context */ +void fiord(struct mcmcxdef *mctx, voccxdef *vctx, +           struct tokcxdef *tctx, +           char *fname, char *exename, +           struct fiolcxdef *setupctx, objnum *preinit, uint *flagp, +           struct tokpdef *path, uchar **fmtsp, uint *fmtlp, +           uint *pcntptr, int flags, struct appctxdef *appctx, +           char *argv0); + +/* shut down load-on-demand subsystem, close load file */ +void fiorcls(fiolcxdef *ctx); + +/* loader callback - load an object on demand */ +void OS_LOADDS fioldobj(void *ctx, mclhd handle, uchar *ptr, +                        ushort siz); + +/*  + *   Save a game - returns TRUE on failure.  We'll save the file to + *   'fname'.  'game_fname' is the name of the game file; if this is not + *   null, we'll save it to the saved game file so that the player can + *   later start the game by specifying only the saved game file to the + *   run-time.  'game_fname' can be null, in which case we'll omit the + *   game file information.   + */ +int fiosav(voccxdef *vctx, char *fname, char *game_fname); + +/* + *   fiorso() result codes  + */ +#define FIORSO_SUCCESS          0                                /* success */ +#define FIORSO_FILE_NOT_FOUND   1                         /* file not found */ +#define FIORSO_NOT_SAVE_FILE    2                  /* not a saved game file */ +#define FIORSO_BAD_FMT_VSN      3       /* incompatible file format version */ +#define FIORSO_BAD_GAME_VSN     4  /* file saved by another game or version */ +#define FIORSO_READ_ERROR       5            /* error reading from the file */ +#define FIORSO_NO_PARAM_FILE    6   /* no parameter file (for restore(nil)) */ + +/* restore a game - returns TRUE on failure */ +int fiorso(voccxdef *vctx, char *fname); + +/* + *   Look in a saved game file to determine if it has information on which + *   GAM file created it.  If the GAM file information is available, this + *   routine returns true and stores the game file name in the given + *   buffer; if the information isn't available, we'll return false.   + */ +int fiorso_getgame(char *saved_file, char *buf, size_t buflen); + +/* encrypt/decrypt an object */ +void fioxor(uchar *p, uint siz, uint seed, uint inc); + +/* strings stored in binary game file for identification and validation */ + +/* file header string */ +#define FIOFILHDR    "TADS2 bin\012\015\032" + +/* resource file header string */ +#define FIOFILHDRRSC "TADS2 rsc\012\015\032" + +/* CURRENT file format version string */ +#define FIOVSNHDR  "v2.2.0" + +/* other file format versions that can be READ by this version */ +#define FIOVSNHDR2 "v2.0.0" +#define FIOVSNHDR3 "v2.0.1" + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/ler.cpp b/engines/glk/tads/tads2/ler.cpp deleted file mode 100644 index d996977f0e..0000000000 --- a/engines/glk/tads/tads2/ler.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "ler.h" - -namespace Glk { -namespace TADS { -namespace TADS2 { - -#define TRDLOGERR_PREFIX "\n[An error has occurred within TADS: " - -int errcxdef::errfmt(char *outbuf, int outbufl, char *fmt, int argc, erradef *argv) { -	int    outlen = 0; -	int    argi   = 0; -	int    len; -	char   buf[20]; -	const char  *p = nullptr; -	char   fmtchar; - -	while (*fmt != '\0' && outbufl > 1) { -		switch(*fmt) { -		case '\\': -			++fmt; -			len = 1; -			switch(*fmt) { -			case '\0': -				--fmt; -				break; -			case '\n': -				p = "\n"; -				break; -			case '\t': -				p = "\t"; -				break; -			default: -				p = fmt; -				break; -			} -			break; - -		case '%': -			++fmt; -			fmtchar = *fmt; -			if (argi >= argc) fmtchar = 1;          // too many - ignore it -			switch(fmtchar) { -			case '\0': -				--fmt; -				len = 0; -				break; - -			case '%': -				p = "%"; -				len = 1; -				break; - -			case 'd': -				sprintf(buf, "%d", argv[argi].erraint); -				len = strlen(buf); -				p = buf; -				break; - -			case 'u': -				sprintf(buf, "%u", argv[argi].erraint); -				len = strlen(buf); -				p = buf; -				break; - -			case 's': -				p = argv[argi].errastr; -				len = strlen(p); -				break; - -			default: -				p = ""; -				len = 0; -				--argi; -				break; -			} -			++argi; -			break; - -		default: -			p = fmt; -			len = 1; -			break; -		} - -		/* copy output that was set up above */ -		if (len != 0) { -			if (outbufl >= len) { -				memcpy(outbuf, p, (size_t)len); -				outbufl -= len; -				outbuf += len; -			} else if (outbufl > 1) { -				memcpy(outbuf, p, (size_t)outbufl - 1); -				outbufl = 1; -			} -			outlen += len; -		} -		++fmt; -	} - -	// add a null terminator -	if (outbufl != 0) -		*outbuf++ = '\0'; - -	// return the length -	return outlen; -} - -void errcxdef::errcxlog(void *ctx0, char *fac, int err, int argc, erradef *argv) { -#ifdef TODO -	errcxdef *ctx = (errcxdef *)ctx0; -	char      buf[256]; -	char      msg[256]; - -	// display the prefix message to the console and log file -	sprintf(buf, TRDLOGERR_PREFIX, fac, err); -	trdptf("%s", buf); -	out_logfile_print(buf, false); - -	/* display the error message text to the console and log file */ -	errmsg(ctx, msg, (uint)sizeof(msg), err); -	errfmt(buf, (int)sizeof(buf), msg, argc, argv); -	trdptf("%s]\n", buf); -	out_logfile_print(buf, false); -	out_logfile_print("]", true); -#endif -} - -} // End of namespace TADS2 -} // End of namespace TADS -} // End of namespace Glk diff --git a/engines/glk/tads/tads2/ler.h b/engines/glk/tads/tads2/ler.h deleted file mode 100644 index 57e3e63c2c..0000000000 --- a/engines/glk/tads/tads2/ler.h +++ /dev/null @@ -1,617 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifndef GLK_TADS_TADS2_LER -#define GLK_TADS_TADS2_LER - -#include "common/scummsys.h" -#include "common/stream.h" -#include "common/algorithm.h" - -namespace Glk { -namespace TADS { -namespace TADS2 { - -// maximum length of a facility identifier -#define ERRFACMAX    6 - -enum ErrorCode { -	/* memory/cache manager errors */ -	ERR_NOMEM    =  1,                                     /* out of memory */ -	ERR_FSEEK    =  2,                             /* error seeking in file */ -	ERR_FREAD    =  3,                           /* error reading from file */ -	ERR_NOPAGE   =  4,                                /* no more page slots */ -	ERR_REALCK   =  5,          /* attempting to reallocate a locked object */ -	ERR_SWAPBIG  =  6,    /* swapfile limit reached - out of virtual memory */ -	ERR_FWRITE   =  7,                                /* error writing file */ -	ERR_SWAPPG   =  8,                    /* exceeded swap page table limit */ -	ERR_CLIUSE   =  9,     /* requested client object number already in use */ -	ERR_CLIFULL  = 10,                      /* client mapping table is full */ -	ERR_NOMEM1   = 11,   /* swapping/garbage collection failed to find enuf */ -	ERR_NOMEM2   = 12,            /* no memory to resize (expand) an object */ -	ERR_OPSWAP   = 13,                          /* unable to open swap file */ -	ERR_NOHDR    = 14,                     /* can't get a new object header */ -	ERR_NOLOAD   = 15,   /* mcm cannot find object to load (internal error) */ -	ERR_LCKFRE   = 16,     /* attempting to free a locked object (internal) */ -	ERR_INVOBJ   = 17,                                    /* invalid object */ -	ERR_BIGOBJ   = 18,  /* object too big - exceeds memory allocation limit */ - -	/* lexical analysis errors */ -	ERR_INVTOK   = 100,                                    /* invalid token */ -	ERR_STREOF   = 101,                /* end of file while scanning string */ -	ERR_TRUNC    = 102,                      /* symbol too long - truncated */ -	ERR_NOLCLSY  = 103,                   /* no space in local symbol table */ -	ERR_PRPDIR   = 104,               /* invalid preprocessor (#) directive */ -	ERR_INCNOFN  = 105,                /* no filename in #include directive */ -	ERR_INCSYN   = 106,                          /* invalid #include syntax */ -	ERR_INCSEAR  = 107,                         /* can't find included file */ -	ERR_INCMTCH  = 108,       /* no matching delimiter in #include filename */ -	ERR_MANYSYM  = 109,                    /* out of space for symbol table */ -	ERR_LONGLIN  = 110,                                    /* line too long */ -	ERR_INCRPT   = 111,           /* header file already included - ignored */ -	ERR_PRAGMA   = 112,                         /* unknown pragma (ignored) */ -	ERR_BADPELSE = 113,                                 /* unexpected #else */ -	ERR_BADENDIF = 114,                                /* unexpected #endif */ -	ERR_BADELIF  = 115,                                 /* unexpected #elif */ -	ERR_MANYPIF  = 116,                             /* #if nesting too deep */ -	ERR_DEFREDEF = 117,                           /* symbol already defined */ -	ERR_PUNDEF   = 118,                        /* #undef symbol not defined */ -	ERR_NOENDIF  = 119,                                   /* missing #endif */ -	ERR_MACNEST  = 120,                         /* macros nested too deeply */ -	ERR_BADISDEF = 121,          /* invalid argument for defined() operator */ -	ERR_PIF_NA   = 122,                           /* #if is not implemented */ -	ERR_PELIF_NA = 123,                         /* #elif is not implemented */ -	ERR_P_ERROR  = 124,                              /* error directive: %s */ -	ERR_LONG_FILE_MACRO = 125,               /* __FILE__ expansion too long */ -	ERR_LONG_LINE_MACRO = 126,               /* __LINE__ expansion too long */ - -	/* undo errors */ -	ERR_UNDOVF   = 200,                /* operation is too big for undo log */ -	ERR_NOUNDO   = 201,                         /* no more undo information */ -	ERR_ICUNDO   = 202,          /* incomplete undo (no previous savepoint) */ - -	/* parser errors */ -	ERR_REQTOK   = 300,    /* expected token (arg 1) - found something else */ -	ERR_REQSYM   = 301,                                  /* expected symbol */ -	ERR_REQPRP   = 302,                         /* expected a property name */ -	ERR_REQOPN   = 303,                                 /* expected operand */ -	ERR_REQARG   = 304,       /* expected comma or closing paren (arg list) */ -	ERR_NONODE   = 305,                      /* no space for new parse node */ -	ERR_REQOBJ   = 306,                             /* epxected object name */ -	ERR_REQEXT   = 307,           /* redefining symbol as external function */ -	ERR_REQFCN   = 308,                    /* redefining symbol as function */ -	ERR_NOCLASS  = 309,  /* can't use CLASS with function/external function */ -	ERR_REQUNO   = 310,                          /* required unary operator */ -	ERR_REQBIN   = 311,                         /* required binary operator */ -	ERR_INVBIN   = 312,                          /* invalid binary operator */ -	ERR_INVASI   = 313,                               /* invalid assignment */ -	ERR_REQVAR   = 314,                           /* required variable name */ -	ERR_LCLSYN   = 315,        /* required comma or semicolon in local list */ -	ERR_REQRBR   = 316,   /* required right brace (eof before end of group) */ -	ERR_BADBRK   = 317,                          /* 'break' without 'while' */ -	ERR_BADCNT   = 318,                       /* 'continue' without 'while' */ -	ERR_BADELS   = 319,                              /* 'else' without 'if' */ -	ERR_WEQASI   = 320, /* warning: possible use of '=' where ':=' intended */ -	ERR_EOF      = 321,                           /* unexpected end of file */ -	ERR_SYNTAX   = 322,                             /* general syntax error */ -	ERR_INVOP    = 323,                             /* invalid operand type */ -	ERR_NOMEMLC  = 324,             /* no memory for new local symbol table */ -	ERR_NOMEMAR  = 325,              /* no memory for argument symbol table */ -	ERR_FREDEF   = 326,   /* redefining a function which is already defined */ -	ERR_NOSW     = 327,      /* 'case' or 'default' and not in switch block */ -	ERR_SWRQCN   = 328,           /* constant required in switch case value */ -	ERR_REQLBL   = 329,                        /* label required for 'goto' */ -	ERR_NOGOTO   = 330,                       /* 'goto' label never defined */ -	ERR_MANYSC   = 331,                 /* too many superclasses for object */ -	ERR_OREDEF   = 332,                      /* redefining symbol as object */ -	ERR_PREDEF   = 333,               /* property being redefined in object */ -	ERR_BADPVL   = 334,                           /* invalid property value */ -	ERR_BADVOC   = 335,                    /* bad vocabulary property value */ -	ERR_BADTPL   = 336,       /* bad template property value (need sstring) */ -	ERR_LONGTPL  = 337,             /* template base property name too long */ -	ERR_MANYTPL  = 338,     /* too many templates (internal compiler limit) */ -	ERR_BADCMPD  = 339,   /* bad value for compound word (sstring required) */ -	ERR_BADFMT   = 340,     /* bad value for format string (sstring needed) */ -	ERR_BADSYN   = 341,     /* invalid value for synonym (sstring required) */ -	ERR_UNDFSYM  = 342,                                 /* undefined symbol */ -	ERR_BADSPEC  = 343,                                 /* bad special word */ -	ERR_NOSELF   = 344,                 /* "self" not valid in this context */ -	ERR_STREND   = 345,            /* warning: possible unterminated string */ -	ERR_MODRPLX  = 346,    /* modify/replace not allowed with external func */ -	ERR_MODFCN   = 347,                 /* modify not allowed with function */ -	ERR_MODFWD   = 348,     /* modify/replace not allowed with forward func */ -	ERR_MODOBJ   = 349,    /* modify can only be used with a defined object */ -	ERR_RPLSPEC  = 350,                 /* warning - replacing specialWords */ -	ERR_SPECNIL  = 351,        /* nil only allowed with modify specialWords */ -	ERR_BADLCL   = 353,   /* 'local' statement must precede executable code */ -	ERR_IMPPROP  = 354,          /* implied verifier '%s' is not a property */ -	ERR_BADTPLF  = 355,                    /* invalid command template flag */ -	ERR_NOTPLFLG  = 356,      /* flags are not allowed with old file format */ -	ERR_AMBIGBIN  = 357,          /* warning: operator '%s' could be binary */ -	ERR_PIA       = 358,          /* warning: possibly incorrect assignment */ -	ERR_BADSPECEXPR = 359,                /* invalid speculation evaluation */ - -	/* code generation errors */ -	ERR_OBJOVF   = 400,     /* object cannot grow any bigger - code too big */ -	ERR_NOLBL    = 401,                  /* no more temporary labels/fixups */ -	ERR_LBNOSET  = 402,                 /* (internal error) label never set */ -	ERR_INVLSTE  = 403,                /* invalid datatype for list element */ -	ERR_MANYDBG  = 404,  /* too many debugger line records (internal limit) */ - -	/* vocabulary setup errors */ -	ERR_VOCINUS  = 450,            /* vocabulary being redefined for object */ -	ERR_VOCMNPG  = 451,          /* too many vocwdef pages (internal limit) */ -	ERR_VOCREVB  = 452,                             /* redefining same verb */ -	ERR_VOCREVB2 = 453,             /* redefining same verb - two arguments */ - -	/* set-up errors */ -	ERR_LOCNOBJ  = 500,           /* location of object %s is not an object */ -	ERR_CNTNLST  = 501,                /* contents of object %s is not list */ -	ERR_SUPOVF   = 502,           /* overflow trying to build contents list */ -	ERR_RQOBJNF  = 503,                     /* required object %s not found */ -	ERR_WRNONF   = 504,                    /* warning - object %s not found */ -	ERR_MANYBIF  = 505,     /* too many built-in functions (internal error) */ - -	/* fio errors */ -	ERR_OPWGAM    = 600,                 /* unable to open game for writing */ -	ERR_WRTGAM    = 601,                      /* error writing to game file */ -	ERR_FIOMSC    = 602,             /* too many sc's for writing in fiowrt */ -	ERR_UNDEFF    = 603,                              /* undefined function */ -	ERR_UNDEFO    = 604,                                /* undefined object */ -	ERR_UNDEF     = 605,                         /* undefined symbols found */ -	ERR_OPRGAM    = 606,                 /* unable to open game for reading */ -	ERR_RDGAM     = 607,                         /* error reading game file */ -	ERR_BADHDR    = 608,    /* file has invalid header - not TADS game file */ -	ERR_UNKRSC    = 609,              /* unknown resource type in .gam file */ -	ERR_UNKOTYP   = 610,             /* unknown object type in OBJ resource */ -	ERR_BADVSN    = 611,    /* file saved by different incompatible version */ -	ERR_LDGAM     = 612,                  /* error loading object on demand */ -	ERR_LDBIG     = 613, /* object too big for load region (prob. internal) */ -	ERR_UNXEXT    = 614,                /* did not expect external function */ -	ERR_WRTVSN    = 615,     /* compiler cannot write the requested version */ -	ERR_VNOCTAB   = 616,        /* format version cannot be used with -ctab */ -	ERR_BADHDRRSC = 617,         /* invalid resource file header in file %s */ -	ERR_RDRSC     = 618,               /* error reading resource file "xxx" */ - -	/* character mapping errors */ -	ERR_CHRNOFILE = 700,           /* unable to load character mapping file */ - -	/* user interrupt */ -	ERR_USRINT    = 990,      /* user requested cancel of current operation */ - -	/* run-time errors */ -	ERR_STKOVF    = 1001,                                 /* stack overflow */ -	ERR_HPOVF     = 1002,                                  /* heap overflow */ -	ERR_REQNUM    = 1003,                         /* numeric value required */ -	ERR_STKUND    = 1004,                                /* stack underflow */ -	ERR_REQLOG    = 1005,                         /* logical value required */ -	ERR_INVCMP    = 1006,     /* invalid datatypes for magnitude comparison */ -	ERR_REQSTR    = 1007,                          /* string value required */ -	ERR_INVADD    = 1008,             /* invalid datatypes for '+' operator */ -	ERR_INVSUB    = 1009,      /* invalid datatypes for binary '-' operator */ -	ERR_REQVOB    = 1010,                           /* require object value */ -	ERR_REQVFN    = 1011,                      /* required function pointer */ -	ERR_REQVPR    = 1012,                 /* required property number value */ - -	/* non-error conditions:  run-time EXIT, ABORT, ASKIO, ASKDO */ -	ERR_RUNEXIT    = 1013,                     /* 'exit' statement executed */ -	ERR_RUNABRT    = 1014,                    /* 'abort' statement executed */ -	ERR_RUNASKD    = 1015,                    /* 'askdo' statement executed */ -	ERR_RUNASKI    = 1016,           /* 'askio' executed; int arg 1 is prep */ -	ERR_RUNQUIT    = 1017,                               /* 'quit' executed */ -	ERR_RUNRESTART = 1018,                              /* 'reset' executed */ -	ERR_RUNEXITOBJ = 1019,                            /* 'exitobj' executed */ - -	ERR_REQVLS     = 1020,                             /* list value required */ -	ERR_LOWINX     = 1021,              /* index value too low (must be >= 1) */ -	ERR_HIGHINX    = 1022,  /* index value too high (must be <= length(list)) */ -	ERR_INVTBIF    = 1023,              /* invalid type for built-in function */ -	ERR_INVVBIF    = 1024,             /* invalid value for built-in function */ -	ERR_BIFARGC    = 1025,           /* wrong number of arguments to built-in */ -	ERR_ARGC       = 1026,      /* wrong number of arguments to user function */ -	ERR_FUSEVAL    = 1027,     /* string/list not allowed for fuse/daemon arg */ -	ERR_BADSETF    = 1028,      /* internal error in setfuse/setdaemon/notify */ -	ERR_MANYFUS    = 1029,                                  /* too many fuses */ -	ERR_MANYDMN    = 1030,                                /* too many daemons */ -	ERR_MANYNFY    = 1031,                              /* too many notifiers */ -	ERR_NOFUSE     = 1032,                       /* fuse not found in remfuse */ -	ERR_NODMN      = 1033,                   /* daemon not found in remdaemon */ -	ERR_NONFY      = 1034,                  /* notifier not found in unnotify */ -	ERR_BADREMF    = 1035,    /* internal error in remfuse/remdaemon/unnotify */ -	ERR_DMDLOOP    = 1036,     /* load-on-demand loop: property not being set */ -	ERR_UNDFOBJ    = 1037,             /* undefined object in vocabulary tree */ -	ERR_BIFCSTR    = 1038,            /* c-string conversion overflows buffer */ -	ERR_INVOPC     = 1039,                                  /* invalid opcode */ -	ERR_RUNNOBJ    = 1040,     /* runtime error: property taken of non-object */ -	ERR_EXTLOAD    = 1041,           /* unable to load external function "%s" */ -	ERR_EXTRUN     = 1042,          /* error executing external function "%s" */ -	ERR_CIRCSYN    = 1043,                                /* circular synonym */ -	ERR_DIVZERO    = 1044,                                  /* divide by zero */ -	ERR_BADDEL     = 1045,      /* can only delete objects created with "new" */ -	ERR_BADNEWSC   = 1046,     /* superclass for "new" cannot be a new object */ -	ERR_VOCSTK     = 1047,              /* insufficient space in parser stack */ -	ERR_BADFILE    = 1048,                             /* invalid file handle */ - -	ERR_RUNEXITPRECMD = 1049,                       /* exited from preCommand */ - -	/* run-time parser errors */ -	ERR_PRS_SENT_UNK    = 1200,          /* sentence structure not recognized */ -	ERR_PRS_VERDO_FAIL  = 1201,                           /* verDoVerb failed */ -	ERR_PRS_VERIO_FAIL  = 1202,                           /* verIoVerb failed */ -	ERR_PRS_NO_VERDO    = 1203,             /* no verDoVerb for direct object */ -	ERR_PRS_NO_VERIO    = 1204,             /* no verIoVerb for direct object */ -	ERR_PRS_VAL_DO_FAIL = 1205,            /* direct object validation failed */ -	ERR_PRS_VAL_IO_FAIL = 1206,          /* indirect object validation failed */ - -	/* compiler/runtime/debugger driver errors */ -	ERR_USAGE     = 1500,                                /* invalid usage */ -	ERR_OPNINP    = 1501,                     /* error opening input file */ -	ERR_NODBG     = 1502,              /* game not compiled for debugging */ -	ERR_ERRFIL    = 1503,            /* unable to open error capture file */ -	ERR_PRSCXSIZ  = 1504,            /* parse pool + local size too large */ -	ERR_STKSIZE   = 1505,                         /* stack size too large */ -	ERR_OPNSTRFIL = 1506,            /* error opening string capture file */ -	ERR_INVCMAP   = 1507,                   /* invalid character map file */ - -	/* debugger errors */ -	ERR_BPSYM         = 2000,                 /* symbol not found for breakpoint */ -	ERR_BPPROP        = 2002,             /* breakpoint symbol is not a property */ -	ERR_BPFUNC        = 2003,             /* breakpoint symbol is not a function */ -	ERR_BPNOPRP       = 2004,              /* property is not defined for object */ -	ERR_BPPRPNC       = 2005,                            /* property is not code */ -	ERR_BPSET         = 2006,         /* breakpoint already set at this location */ -	ERR_BPNOTLN       = 2007,     /* breakpoint is not at a line (OPCLINE instr) */ -	ERR_MANYBP        = 2008,                            /* too many breakpoints */ -	ERR_BPNSET        = 2009,            /* breakpoint to be deleted was not set */ -	ERR_DBGMNSY       = 2010,  /* too many symbols in debug expression (int lim) */ -	ERR_NOSOURC       = 2011,                   /* unable to find source file %s */ -	ERR_WTCHLCL       = 2012,        /* illegal to assign to local in watch expr */ -	ERR_INACTFR       = 2013, /* inactive frame (expression value not available) */ -	ERR_MANYWX        = 2014,                      /* too many watch expressions */ -	ERR_WXNSET        = 2015,                              /* watchpoint not set */ -	ERR_EXTRTXT       = 2016,               /* extraneous text at end of command */ -	ERR_BPOBJ         = 2017,              /* breakpoint symbol is not an object */ -	ERR_DBGINACT      = 2018,                          /* debugger is not active */ -	ERR_BPINUSE       = 2019,                      /* breakpoint is already used */ -	ERR_RTBADSPECEXPR = 2020,                  /* invalid speculative expression */ -	ERR_NEEDLIN2      = 2021,     /* -ds2 information not found - must recompile */ - -	/* usage error messages */ -	ERR_TCUS1         = 3000,                          /* first tc usage message */ -	ERR_TCUSL         = 3024,                           /* last tc usage message */ -	ERR_TCTGUS1       = 3030,                         /* first tc toggle message */ -	ERR_TCTGUSL       = 3032, -	ERR_TCZUS1        = 3040,            /* first tc -Z suboptions usage message */ -	ERR_TCZUSL        = 3041, -	ERR_TC1US1        = 3050,            /* first tc -1 suboptions usage message */ -	ERR_TC1USL        = 3058, -	ERR_TCMUS1        = 3070,            /* first tc -m suboptions usage message */ -	ERR_TCMUSL        = 3076, -	ERR_TCVUS1        = 3080,                /* first -v suboption usage message */ -	ERR_TCVUSL        = 3082, -	ERR_TRUSPARM      = 3099, -	ERR_TRUS1         = 3100,                          /* first tr usage message */ -	ERR_TRUSL         = 3117, -	ERR_TRUSFT1       = 3118,                       /* first tr "footer" message */ -	ERR_TRUSFTL       = 3119,                        /* last tr "footer" message */ -	ERR_TRSUS1        = 3150,            /* first tr -s suboptions usage message */ -	ERR_TRSUSL        = 3157, -	ERR_TDBUSPARM     = 3199, -	ERR_TDBUS1        = 3200,                         /* first tdb usage message */ -	ERR_TDBUSL        = 3214,                          /* last tdb usage message */ - -	/* TR 16-bit MSDOS-specific usage messages */ -	ERR_TRUS_DOS_1   = 3300, -	ERR_TRUS_DOS_L   = 3300, - -	/* TR 32-bit MSDOS console mode usage messages */ -	ERR_TRUS_DOS32_1 = 3310, -	ERR_TRUS_DOS32_L = 3312, - -	/* TADS/Graphic errors */ -	ERR_GNOFIL       = 4001,                      /* can't find graphics file %s */ -	ERR_GNORM        = 4002,                               /* can't find room %s */ -	ERR_GNOOBJ       = 4003,                    /* can't find hot spot object %s */ -	ERR_GNOICN       = 4004                         /* can't find icon object %s */ -}; - -/* - *   Special error flag - this is returned from execmd() when preparseCmd - *   returns a command list.  This indicates to voc1cmd that it should try - *   the command over again, using the words in the new list. - */ -#define ERR_PREPRSCMDREDO  30000             /* preparseCmd returned a list */ -#define ERR_PREPRSCMDCAN   30001    /* preparseCmd returned 'nil' to cancel */ - -union erradef { -	int   erraint;		// integer argument -	char *errastr;		// text string argument -}; - -struct errdef { -	errdef *       errprv;               // previous error frame -	int            errcode;              // error code of exception being handled -	char           errfac[ERRFACMAX+1];  // facility of current error -	erradef        erraav[10];           // parameters for error -	int            erraac;               // count of parameters in argc -//    jmp_buf        errbuf;               // jump buffer for current error frame -}; - -#define ERRBUFSIZ 512 - -class TADS2; - -// seek location record for an error message by number -struct errmfdef { -	uint  errmfnum;   // error number -	size_t errmfseek; // seek location of this message -}; - -class errcxdef { -public: -	errdef   *errcxptr;               // current error frame -	void     *errcxlgc;               // context for error logging callback -	int       errcxofs;               // offset in argument buffer -	char      errcxbuf[ERRBUFSIZ];    // space for argument strings -	Common::SeekableReadStream *errcxfp;  // message file, if one is being used -	errmfdef *errcxseek;              // seek locations of messages in file -	uint      errcxsksz;              // size of errcxseek array -	size_t    errcxbase;              // offset in physical file of logical error file -	TADS2 *   errcxappctx;            // host application context -public: -	/** -	 *   Format an error message, sprintf-style, using arguments in an -	 *   erradef array (which is passed to the error-logging callback). -	 *   Returns the length of the output string, even if the actual -	 *   output string was truncated because the outbuf was too short. -	 *   (If called with outbufl == 0, nothing will be written out, but -	 *   the size of the buffer needed, minus the terminating null byte, -	 *   will be computed and returned.) -	 */ -	static int errfmt(char *outbuf, int outbufl, char *fmt, int argc, erradef *argv); - public: -	errcxdef() : errcxptr(nullptr), errcxlgc(nullptr), errcxofs(0), -		errcxseek(nullptr), errcxsksz(0), errcxbase(0), errcxappctx(nullptr) { -		Common::fill(&errcxbuf[0], &errcxbuf[ERRBUFSIZ], '\0'); -	} - -	/** -	 * Error logging method -	 */ -	void errcxlog(void *ctx0, char *fac, int err, int argc, erradef *argv); -}; - -// begin protected code -#define ERRBEGIN(ctx) \ -  { \ -	errdef fr_; \ -	if ((fr_.errcode = setjmp(fr_.errbuf)) == 0) \ -	{ \ -	  fr_.errprv = (ctx)->errcxptr; \ -	  (ctx)->errcxptr = &fr_; - -// end protected code, begin error handler -#define ERRCATCH(ctx, e) \ -	  assert(1==1 && (ctx)->errcxptr != fr_.errprv); \ -	  (ctx)->errcxptr = fr_.errprv; \ -	} \ -	else \ -	{ \ -	  assert(2==2 && (ctx)->errcxptr != fr_.errprv); \ -	  (e) = fr_.errcode; \ -	  (ctx)->errcxptr = fr_.errprv; - -// retrieve argument (int, string) in current error frame -#define errargint(argnum) (fr_.erraav[argnum].erraint) -#define errargstr(argnum) (fr_.erraav[argnum].errastr) - - -#define ERREND(ctx) \ -	} \ -  } - -// end protected code, begin cleanup (no handling; just cleaning up) -#define ERRCLEAN(ctx) \ -	  assert((ctx)->errcxptr != fr_.errprv); \ -	  (ctx)->errcxptr = fr_.errprv; \ -	} \ -	else \ -	{ \ -	  assert((ctx)->errcxptr != fr_.errprv); \ -	  (ctx)->errcxptr = fr_.errprv; - -#define ERRENDCLN(ctx) \ -	  errrse(ctx); \ -	} \ -  } - - - -// argument types for errors with arguments -#define ERRTINT  erraint -#define ERRTSTR  errastr - -// set argument count in error frame -#define errargc(ctx,cnt) ((ctx)->errcxptr->erraac=(cnt)) - -// enter string argument; returns pointer to argument used in errargv -#ifdef ERR_NO_MACRO -char *errstr(errcxdef *ctx, const char *str, int len); -#else /* ERR_NO_MACRO */ - -#define errstr(ctx,str,len) \ -  ((memcpy(&(ctx)->errcxbuf[(ctx)->errcxofs],str,(size_t)len), \ -   (ctx)->errcxofs += (len), \ -   (ctx)->errcxbuf[(ctx)->errcxofs++] = '\0'), \ -   &(ctx)->errcxbuf[(ctx)->errcxofs-(len)-1]) - -#endif /* ERR_NO_MACRO */ - -/* set argument in error frame argument vector */ -#define errargv(ctx,index,typ,arg) \ -  ((ctx)->errcxptr->erraav[index].typ=(arg)) - -// signal an error with argument count already set -#ifdef ERR_NO_MACRO -void errsign(errcxdef *ctx, int e, char *facility); -#else /* ERR_NO_MACRO */ -# ifdef DEBUG -void errjmp(jmp_buf buf, int e); -#  define errsign(ctx, e, fac) \ -   (strncpy((ctx)->errcxptr->errfac, fac, ERRFACMAX),\ -	(ctx)->errcxptr->errfac[ERRFACMAX]='\0',\ -	(ctx)->errcxofs=0, errjmp((ctx)->errcxptr->errbuf, e)) -# else /* DEBUG */ -#  define errsign(ctx, e, fac) \ -   (strncpy((ctx)->errcxptr->errfac, fac, ERRFACMAX),\ -	(ctx)->errcxptr->errfac[ERRFACMAX]='\0',\ -	(ctx)->errcxofs=0, longjmp((ctx)->errcxptr->errbuf, e)) -# endif /* DEBUG */ -#endif /* ERR_NO_MACRO */ - - -// signal an error with no arguments -#ifdef ERR_NO_MACRO -void errsigf(errcxdef *ctx, char *facility, int err); -#else /* ERR_NO_MACRO */ -#define errsigf(ctx, fac, e) (errargc(ctx,0),errsign(ctx,e,fac)) -#endif /* ERR_NO_MACRO */ - -// signal an error with one argument -#define errsigf1(ctx, fac, e, typ1, arg1) \ -  (errargv(ctx,0,typ1,arg1),errargc(ctx,1),errsign(ctx,e,fac)) - -// signal an error with two arguments -#define errsigf2(ctx, fac, e, typ1, arg1, typ2, arg2) \ -  (errargv(ctx,0,typ1,arg1), errargv(ctx,1,typ2,arg2), \ -   errargc(ctx,2), errsign(ctx,e,fac)) - -// resignal the current error - only usable within exception handlers -#ifdef ERR_NO_MACRO -void errrse1(errcxdef *ctx, errdef *fr); -# define errrse(ctx) errrse1(ctx, &fr_) -#else /* ERR_NO_MACRO */ - -// void errrse(errcxdef *ctx); -# define errrse(ctx) \ -  (errargc(ctx, fr_.erraac),\ -   memcpy((ctx)->errcxptr->erraav, fr_.erraav, \ -	(size_t)(fr_.erraac*sizeof(erradef))),\ -   errsign(ctx, fr_.errcode, fr_.errfac)) - -#endif /* ERR_NO_MACRO */ - -/** - *   For use in an error handler (ERRCATCH..ERREND) only: Copy the - *   parameters from the error currently being handled to the enclosing - *   frame.  This is useful when "keeping" an error being handled - i.e., - *   the arguments will continue to be used outside of the - *   ERRCATCH..ERREND code. - */ -#define errkeepargs(ctx) errcopyargs(ctx, &fr_) - -/** - *   copy the parameters for an error from another frame into the current - *   frame - this can be used when we want to be able to display an error - *   that occurred in an inner frame within code that is protected by a - *   new enclosing error frame - */ -#define errcopyargs(ctx, fr) \ -   (errargc((ctx), (fr)->erraac), \ -	memcpy((ctx)->errcxptr->erraav, (fr)->erraav, \ -		   (size_t)((fr)->erraac*sizeof(erradef)))) - -// log error that's been caught, using arguments already caught -#define errclog(ctx) \ - ((*(ctx)->errcxlog)((ctx)->errcxlgc,fr_.errfac,fr_.errcode,\ -  fr_.erraac,fr_.erraav)) - -// log an error that's been set up but not signalled yet -#define errprelog(ctx, err) \ - ((*(ctx)->errcxlog)((ctx)->errcxlgc,(ctx)->errcxptr->errfac,\ -   err,(ctx)->errcxptr->erraac,\ -   (ctx)->errcxptr->erraav)) - -// log an error (no signalling, just reporting) -#ifdef ERR_NO_MACRO -void errlogn(errcxdef *ctx, int err, char *facility); -#else /* ERR_NO_MACRO */ - -#define errlogn(ctx,err,fac) \ - ((ctx)->errcxofs=0,\ -  (*(ctx)->errcxlog)((ctx)->errcxlgc,fac,err,(ctx)->errcxptr->erraac,\ -  (ctx)->errcxptr->erraav)) - -#endif /* ERR_NO_MACRO */ - -// log an error with no arguments -#ifdef ERR_NO_MACRO -void errlogf(errcxdef *ctx, char *facility, int err); -#else /* ERR_NO_MACRO */ - -// void errlogf(errcxdef *ctx, char *facility, int err); -#define errlogf(ctx,fac,err) (errargc(ctx,0),errlogn(ctx,err,fac)) - -#endif /* ERR_NO_MACRO */ - -// log an error with one argument -#define errlogf1(ctx, fac, e, typ1, arg1) \ - (errargv(ctx,0,typ1,arg1),errargc(ctx,1),errlogn(ctx,e,fac)) - -// log an error with two arguments -#define errlogf2(ctx, fac, e, typ1, arg1, typ2, arg2) \ - (errargv(ctx,0,typ1,arg1),errargv(ctx,1,typ2,arg2),\ -  errargc(ctx,2),errlogn(ctx,e,fac)) - -/** - * For compatility with old facility-free mechanism, signal with facility "TADS" - */ -#define errsig(ctx, err) errsigf(ctx, "TADS", err) -#define errsig1(c, e, t, a) errsigf1(c,"TADS",e,t,a) -//#define errsig2(c, e, t1, a1, t2, a2) errsigf2(c,"TADS",e,t1,a1,t2,a2) -#define errlog(c, e) errlogf(c, "TADS", e) -#define errlog1(c, e, t, a) errlogf1(c,"TADS",e,t,a) -#define errlog2(c, e, t1, a1, t2, a2) errlogf2(c,"TADS",e,t1,a1,t2,a2) - -#define errsig2(c, e, t1, a1, t2, a2) error("Error occurred") - -// get the text of an error -void errmsg(errcxdef *ctx, char *outbuf, uint outbufl, uint err); - -// initialize error subsystem, opening error message file if necessary -void errini(errcxdef *ctx, Common::SeekableReadStream *fp); - -// allocate and initialize error context, free error context -errcxdef *lerini(); -void      lerfre(errcxdef *ctx); - -// error message structure - number + text -struct errmdef { -	uint   errmerr;		// error number -	char  *errmtxt;     // text of error message -}; - -} // End of namespace TADS2 -} // End of namespace TADS -} // End of namespace Glk - -#endif diff --git a/engines/glk/tads/tads2/lib.h b/engines/glk/tads/tads2/lib.h new file mode 100644 index 0000000000..605f072390 --- /dev/null +++ b/engines/glk/tads/tads2/lib.h @@ -0,0 +1,164 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_TADS_TADS2_LIB +#define GLK_TADS_TADS2_LIB + +#include "common/scummsys.h" +#include "common/algorithm.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* short-hand for various types */ + +typedef unsigned char uchar; +typedef unsigned short ushort; +typedef unsigned long  ulong; + +typedef byte  ub1; +typedef signed char    sb1; +typedef char           b1; +typedef unsigned int   ub2; +typedef signed int     sb2; +typedef int            b2; +typedef unsigned long  ub4; +typedef signed long    sb4; +typedef long           b4; +typedef int            eword; +typedef int32          int32_t; +typedef uint32         uint32_t; + + +/* maximum/minimum portable values for various types */ +#define UB4MAXVAL  0xffffffffUL +#define UB2MAXVAL  0xffffU +#define UB1MAXVAL  0xffU +#define SB4MAXVAL  0x7fffffffL +#define SB2MAXVAL  0x7fff +#define SB1MAXVAL  0x7f +#define SB4MINVAL  (-(0x7fffffff)-1) +#define SB2MINVAL  (-(0x7fff)-1) +#define SB1MINVAL  (-(0x7f)-1) + +/* clear a struture */ +#define CLRSTRUCT(x) memset(&(x), 0, (size_t)sizeof(x)) +#define CPSTRUCT(dst,src) memcpy(&(dst), &(src), (size_t)sizeof(dst)) + +/* TRUE and FALSE */ +#define TRUE true +#define FALSE false + + +/* bitwise operations */ +#define bit(va, bt) ((va) & (bt)) +#define bis(va, bt) ((va) |= (bt)) +#define bic(va, bt) ((va) &= ~(bt)) + +/* + *   noreg/NOREG - use for variables changed in error-protected code that + *   are used in error handling code.  Use 'noreg' on the declaration like + *   a storage class qualifier.  Use 'NOREG' as an argument call, passing + *   the addresses of all variables declared noreg.  For non-ANSI + *   compilers, a routine osnoreg(/o_ void *arg0, ... _o/) must be + *   defined. + */ +#ifdef OSANSI +# define noreg volatile +# define NOREG(arglist) +#else /* OSANSI */ +# define noreg +# define NOREG(arglist) osnoreg arglist ; +#endif /* OSANSI */ + +/*  + *   Linting directives.  You can define these before including this file + *   if you have a fussy compiler.   + */ +#ifdef LINT +# ifndef NOTREACHED +#  define NOTREACHED return +# endif +# ifndef NOTREACHEDV +#  define NOTREACHEDV(t) return((t)0) +# endif +# ifndef VARUSED +#  define VARUSED(v) varused(v) +# endif +void varused(); +#else /* LINT */ +# ifndef NOTREACHED +#  define NOTREACHED +# endif +# ifndef NOTREACHEDV +#  define NOTREACHEDV(t) +# endif +# ifndef VARUSED +#  define VARUSED(v) +# endif +#endif /* LINT */ + +/* conditionally compile code if debugging is enabled */ +#ifdef DEBUG +# define IF_DEBUG(x) x +#else /* DEBUG */ +# define IF_DEBUG(x) +#endif /* DEBUG */ + +#ifndef offsetof +# define offsetof(s_name, m_name) (size_t)&(((s_name *)0)->m_name) +#endif /* offsetof */ + +/* + *   Define our own version of isspace(), so that we don't try to interpret + *   anything outside of the normal ASCII set as spaces.  + */ +#define t_isspace(c) \ +    (((unsigned char)(c)) <= 127 && isspace((unsigned char)(c))) + + +/* round a size to worst-case alignment boundary */ +#define osrndsz(s) (((s)+3) & ~3) + +/* round a pointer to worst-case alignment boundary */ +#define osrndpt(p) ((uchar *)((((unsigned long)(p)) + 3) & ~3)) + +/* read unaligned portable unsigned 2-byte value, returning int */ +#define osrp2(p) READ_LE_UINT16(p) + +/* read unaligned portable signed 2-byte value, returning int */ +#define osrp2s(p) READ_LE_INT16(p) + +/* write int to unaligned portable 2-byte value */ +#define oswp2(p, i) WRITE_LE_UINT16(p, i) +#define oswp2s(p, i) WRITE_LE_INT16(p, i) + +/* read unaligned portable 4-byte value, returning unsigned long */ +#define osrp4(p) READ_LE_UINT32(p) +#define osrp4s(p) READ_LE_INT32(p) + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/line_source.h b/engines/glk/tads/tads2/line_source.h new file mode 100644 index 0000000000..a7e5454c60 --- /dev/null +++ b/engines/glk/tads/tads2/line_source.h @@ -0,0 +1,129 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* Line source definitions + * + * A line source is a mechanism for reading source text.  The tokenizer + * reads its input from a line source.  This is the basic class + * definition; individual line sources will define the functions and + * class data needed. + */ + +#ifndef GLK_TADS_TADS2_LINE_SOURCE +#define GLK_TADS_TADS2_LINE_SOURCE + +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/object.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + + +/** + * Line source superclass structure + */ +struct lindef { +    int   (*lingetp)(lindef *lin);                         /* get next line */ +    void  (*linclsp)(lindef *lin);                     /* close line source */ +    void  (*linppos)(lindef *lin, char *buf, uint buflen); +            /* write printable rep of position to buf (for error reporting) */ +    void  (*linglop)(lindef *lin, uchar *buf); +                       /* generate a line record for an OPCLINE instruction */ +    int   (*linwrtp)(lindef *lin, osfildef *fp); +            /* write line source information to binary file; TRUE ==> error */ +    void  (*lincmpp)(lindef *lin, uchar *buf); +          /* give location of compiled code for current line to line source */ +    void  (*linactp)(lindef *lin);                      /* activate for dbg */ +    void  (*lindisp)(lindef *lin);                           /* disactivate */ +    void  (*lintellp)(lindef *lin, uchar *pos);             /* get position */ +    void  (*linseekp)(lindef *lin, uchar *pos);                     /* seek */ +    int   (*linreadp)(lindef *lin, uchar *buf, uint siz);          /* fread */ +    void  (*linpaddp)(lindef *lin, uchar *pos, long delta); +                                       /* add an offset to a position value */ +    int   (*linqtopp)(lindef *lin, uchar *pos);                  /* at top? */ +    int   (*lingetsp)(lindef *lin, uchar *buf, uint siz);       /* get line */ +    void  (*linnamp)(lindef *lin, char *buf);            /* get source name */ +    void  (*linfindp)(lindef *lin, char *buf, objnum *objp, +                      uint *ofsp);              /* find nearest line record */ +    void  (*lingotop)(lindef *lin, int where);               /* seek global */ +    long  (*linofsp)(lindef *lin);            /* byte offset in line source */ +    void  (*linrenp)(lindef *lin, objnum oldnum, objnum newnum); +                                       /* renumber an object (for "modify") */ +    void  (*lindelp)(lindef *lin, objnum objn); +                                        /* delete an object (for "replace") */ +    ulong (*linlnump)(lindef *lin);          /* get the current line number */ +#define  LINGOTOP   OSFSK_SET                   /* go to top of line source */ +#define  LINGOEND   OSFSK_END                   /* go to end of line source */ +    lindef *linpar;                        /* parent of current line source */ +    lindef *linnxt;                       /* next line in line source chain */ +    int     linid;           /* serial number of line source (for debugger) */ +    char   *linbuf;                              /* pointer to current line */ +    ushort  linflg;                                                /* flags */ +#define  LINFEOF   0x01                    /* line source is at end of file */ +#define  LINFMORE  0x02             /* there's more to the line than linlen */ +#define  LINFDBG   0x04          /* debug record already generated for line */ +#define  LINFNOINC 0x08        /* ignore # directives from this line source */ +#define  LINFCMODE 0x10                  /* line source is parsed in C-mode */ +    ushort  linlen;                                   /* length of the line */ +    ushort  linlln;           /* length of line record generated by lingloc */ +}; + +/** + *   Maximum allowed value for linlln, in bytes.  This allows subsystems + *   that need to maintain local copies of seek locations to know how big + *   an area to allocate for them.   + */ +#define LINLLNMAX   20 + +/* macros to cover calls to functions */ +#define linget(lin) ((*((lindef *)(lin))->lingetp)((lindef *)(lin))) +#define lincls(lin) ((*((lindef *)(lin))->linclsp)((lindef *)(lin))) +#define linppos(lin, buf, buflen) \ + ((*((lindef *)(lin))->linppos)((lindef *)(lin), buf, buflen)) +#define linglop(lin, buf) ((*((lindef *)(lin))->linglop)(lin, buf)) +#define linwrt(lin, fp) ((*((lindef *)(lin))->linwrtp)(lin, fp)) +#define lincmpinf(lin, buf) ((*((lindef *)(lin))->lincmpp)(lin, buf)) +#define linactiv(lin) ((*((lindef *)(lin))->linactp)(lin)) +#define lindisact(lin) ((*((lindef *)(lin))->lindisp)(lin)) +#define lintell(lin, pos) ((*((lindef *)(lin))->lintellp)(lin, pos)) +#define linseek(lin, pos) ((*((lindef *)(lin))->linseekp)(lin, pos)) +#define linread(lin, buf, siz) ((*((lindef *)(lin))->linreadp)(lin, buf, siz)) +#define linpadd(lin, pos, delta) \ +  ((*((lindef *)(lin))->linpaddp)(lin, pos, delta)) +#define linqtop(lin, pos) ((*((lindef *)(lin))->linqtopp)(lin, pos)) +#define lingets(lin, buf, siz) ((*((lindef *)(lin))->lingetsp)(lin, buf, siz)) +#define linnam(lin, buf) ((*((lindef *)(lin))->linnamp)(lin, buf)) +#define linlnum(lin) ((*((lindef *)(lin))->linlnump)(lin)) +#define linfind(lin, buf, objp, ofsp) \ +  ((*((lindef *)(lin))->linfindp)(lin, buf, objp, ofsp)) +#define lingoto(lin, where) ((*((lindef *)(lin))->lingotop)(lin, where)) +#define linofs(lin) ((*((lindef *)(lin))->linofsp)(lin)) +#define linrenum(lin, oldnum, newnum) \ +  ((*((lindef *)(lin))->linrenp)(lin, oldnum, newnum)) +#define lindelnum(lin, objn) ((*((lindef *)(lin))->lindelp)(lin, objn)) + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/memory_cache.cpp b/engines/glk/tads/tads2/memory_cache.cpp new file mode 100644 index 0000000000..3bab3b034b --- /dev/null +++ b/engines/glk/tads/tads2/memory_cache.cpp @@ -0,0 +1,31 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/tads2/memory_cache.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/tads2/memory_cache.h b/engines/glk/tads/tads2/memory_cache.h new file mode 100644 index 0000000000..ec0383b7e1 --- /dev/null +++ b/engines/glk/tads/tads2/memory_cache.h @@ -0,0 +1,403 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * Memory cache manager + */ + +#ifndef GLK_TADS_TADS2_MEMORY_CACHE +#define GLK_TADS_TADS2_MEMORY_CACHE + +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/memory_cache_loader.h" +#include "glk/tads/tads2/memory_cache_swap.h" +#include "glk/tads/osfrobtads.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* if the os layer doesn't want long mcm macros, we don't either */ +#ifdef OS_MCM_NO_MACRO +# define MCM_NO_MACRO +#endif + +/* + *   mcmon - cache object number.  Each object in the cache is referenced + *   by an object number, which is a number of this type. + */ +typedef ushort mcmon; + +/* invalid object number - used to indicate N/A or no object */ +#define MCMONINV ((mcmon)~0) + +/* invalid page number */ +#define MCMPINV ((uint)~0) + +union mcmodefloc { +	mcsseg  mcmolocs;                            /* swap segment handle */ +	mclhd   mcmolocl;                        /* load file object handle */ +}; + +/* + *   mcmodef - cache object definition.  Each cache object is managed by + *   this structure.  Pointers for a doubly-linked list are provided; + *   these are used to maintain a recent-use list for objects in memory, + *   and to maintain a free list for cache object entries not in use.  A + *   set of flag bits is provided, as is the size of the object and its + *   location (which is a memory pointer for objects in memory, a swap + *   file handle for swapped-out objects, or a load-file handle for + *   objects that are unloaded).  + */ +struct mcmodef { +    uchar  *mcmoptr;               /* memory pointer (if object is present) */ +	mcmodefloc mcmoloc;                       /* object's external location */ +#define  mcmoswh  mcmoloc.mcmolocs +#define  mcmoldh  mcmoloc.mcmolocl +    mcmon   mcmonxt;                            /* next object in this list */ +    mcmon   mcmoprv;                        /* previous object in this list */ +    ushort  mcmoflg;                         /* flags for this cache object */ +#define  MCMOFDIRTY  0x01                     /* object has been written */ +#define  MCMOFNODISC 0x02       /* not in load file (can't be discarded) */ +#define  MCMOFLOCK   0x04                            /* object is locked */ +#define  MCMOFPRES   0x08                 /* object is present in memory */ +#define  MCMOFLRU    0x10                      /* object is in LRU chain */ +#define  MCMOFPAGE   0x20              /* object is a cache manager page */ +#define  MCMOFNOSWAP 0x40                /* object cannot be swapped out */ +#define  MCMOFFREE   0x80      /* entry refers to a free block of memory */ +#define  MCMOFREVRT 0x100           /* call revert callback upon loading */ +    uchar   mcmolcnt;                                         /* lock count */ +    ushort  mcmosiz;                                  /* size of the object */ +}; + +/* heap header - allocate one of these in each heap */ +struct mcmhdef { +    mcmhdef *mcmhnxt;                            /* next heap in this chain */ +}; + +/* GLOBAL cache manager context:  tracks cache manager state */ +struct mcmcx1def { +    mcmodef  **mcmcxtab;                        /* page table for the cache */ +    errcxdef  *mcmcxerr;                          /* error handling context */ +    mcmhdef   *mcmcxhpch;                             /* heap chain pointer */ +    mcscxdef   mcmcxswc;                            /* swap manager context */ +    mclcxdef   mcmcxldc;                                  /* loader context */ +    ulong      mcmcxmax; /* maximum amount of actual heap we can ever alloc */ +    mcmon      mcmcxlru;      /* least recently used object still in memory */ +    mcmon      mcmcxmru;                       /* most recently used object */ +    mcmon      mcmcxfre;                               /* head of free list */ +    mcmon      mcmcxunu;                             /* head of unused list */ +    ushort     mcmcxpage;                      /* last page table slot used */ +    ushort     mcmcxpgmx;        /* maximum number of pages we can allocate */ +    void     (*mcmcxcsw)(mcmcx1def *, mcmon, mcsseg, mcsseg); +                         /* change swap handle in object to new swap handle */ +}; + +/* CLIENT cache manager context: used by client to request mcm services */ +struct mcmcxdef { +    mcmcx1def *mcmcxgl;                     /* global cache manager context */ +    uint       mcmcxflg;                                           /* flags */ +    uint       mcmcxmsz;                   /* maximum size of mapping table */ +    void     (*mcmcxldf)(void *ctx, mclhd handle, uchar *ptr, +                         ushort siz);           /* callback to load objects */ +    void      *mcmcxldc;                       /* context for load callback */ +    void     (*mcmcxrvf)(void *ctx, mcmon objn);           /* revert object */ +    void      *mcmcxrvc;                     /* context for revert callback */ +    mcmon     *mcmcxmtb[1];                                /* mapping table */ +}; + +/* context flags */ +#define MCMCXF_NO_PRP_DEL   0x0001  /* PRPFDEL is invalid in this game file */ + +/* convert from a client object number to a global object number */ +/* mcmon mcmc2g(mcmcxdef *ctx, mcmon objn); */ +#define mcmc2g(ctx, objn) ((ctx)->mcmcxmtb[(objn)>>8][(objn)&255]) + +/* + *   FREE LIST: this is a list, headed by context->mcmcxfre and chained + *   forward and back by mcmonxt and mcmoprv, consisting of free memory + *   blocks.  These refer to blocks in the heap that are not used by any + *   client objects.  + */ +/* + *   UNUSED LIST: this is a list, headed by context->mcmcxunu and chained + *   forward by mcmonxt (not back, because it's never necessary to take + *   anything out of the list except at the head, nor to search the list + *   backwards), of unused cache object entries.  These entries are not + *   associated with any client object or with any heap memory.  This list + *   is used to get a new cache object header, and deleted cache objects + *   are placed onto this list.   + */ +/* + *   LRU LIST: this is a list of in-memory blocks in ascending order of + *   recency of use by the client.  Each time a client unlocks a block, + *   the block is moved to the most recent position in the list (the end + *   of the list).  To make it fast to add a new object, we keep a pointer + *   to the end of the list as well as to the beginning.  The start of the + *   list is at context->mcmcxlru, and is the least recently unlocked + *   block still in memory.  The end of the list is at context->mcmcxmru, + *   and is the most recently unlocked block.   + */ + +/* + *   initialize the cache manager, returning a context for cache manager + *   operations; a null pointer is returned if insufficient heap memory is + *   available for initialization.  The 'max' argument specifies the + *   maximum amount of actual low-level heap memory that the cache manager + *   can ever allocate on behalf of this context (of course, it can + *   overcommit the heap through swapping).  If 'max' is less than the + *   size of a single heap allocation, it is adjusted upwards to that + *   minimum.   + */ +mcmcx1def *mcmini(ulong max, uint pages, ulong swapsize, +                  osfildef *swapfp, char *swapfilename, errcxdef *errctx); + +/* terminate the cache manager - frees the structure and all cache memory */ +void mcmterm(mcmcx1def *ctx); + +/* allocate a client context */ +mcmcxdef *mcmcini(mcmcx1def *globalctx, uint pages, +                  void (*loadfn)(void *, mclhd, uchar *, ushort), +                  void *loadctx, +                  void (*revertfn)(void *, mcmon), void *revertctx); + +/* terminate a client context - frees the structure memory */ +void mcmcterm(mcmcxdef *ctx); + + +/* + *   Lock a cache object, bringing it into memory if necessary.  Returns + *   a pointer to the memory containing the object.  A null pointer is + *   returned in case of error.  The object remains fixed in memory at the + *   returned location until unlocked.  Locks are stacked; that is, if + *   an object is locked twice in succession, it needs to be unlocked + *   twice in succession before it is actually unlocked. + */ +#ifdef MCM_NO_MACRO +uchar *mcmlck(mcmcxdef *ctx, mcmon objnum); +#else /* MCM_NO_MACRO */ + +/* uchar *mcmlck(mcmcxdef *ctx, mcmon objnum); */ +#define mcmlck(ctx,num) \ + ((mcmobje(ctx,num)->mcmoflg & MCMOFPRES ) ? \ + ((mcmobje(ctx,num)->mcmoflg|=MCMOFLOCK), \ +  ++(mcmobje(ctx,num)->mcmolcnt), mcmobje(ctx,num)->mcmoptr) \ + : mcmload(ctx,num)) + +#endif /* MCM_NO_MACRO */ + +/* + *   Unlock a cache object, allowing it to be moved and swapped. + *   Unlocking an object moves it to the end (i.e., most recently used) + *   position on the LRU chain, making it the least favorable to swap out + *   or discard.  This happens at unlock time (rather than lock time) + *   because presumably the client has been using the object the entire + *   time it was locked. For this reason, and to keep memory unfragmented + *   as much as possible, objects should not be kept locked except when + *   actually in use.  Note that locks nest; if an object is locked three + *   times without an intervening unlock, it must be unlocked three times + *   in a row.  An object can be unlocked even if it's not locked; doing + *   so has no effect. + */ +#ifdef MCM_NO_MACRO +void mcmunlck(mcmcxdef *ctx, mcmon objnum); +#else /* MCM_NO_MACRO */ + +/* void mcmunlck(mcmcxdef *ctx, mcmon objnum); */ +#define mcmunlck(ctx,obj) \ + ((mcmobje(ctx,obj)->mcmoflg & MCMOFLOCK) ? \ +  (--(mcmobje(ctx,obj)->mcmolcnt) ? (void)0 : \ +  ((mcmobje(ctx,obj)->mcmoflg&=(~MCMOFLOCK)), \ +    mcmuse((ctx)->mcmcxgl,mcmc2g(ctx,obj)))) : (void)0) + +#endif /* MCM_NO_MACRO */ + +/* + *   Allocate a new cache object.  The new object is locked upon return. + *   A pointer to the memory for the new object is returned, and + *   the object number is returned at *nump.  A null pointer is returned + *   if the object cannot be allocated. + */ +/* uchar *mcmalo(mcmcxdef *ctx, ushort siz, mcmon *nump); */ +#define mcmalo(ctx, siz, nump) mcmalo0(ctx, siz, nump, MCMONINV, FALSE) + +/* + *   Reserve space for an object, giving it a particular client object + *   number.  This doesn't actually allocate any space for the object, but + *   just sets it up so that it can be loaded by the client when it's + *   needed.   + */ +void mcmrsrv(mcmcxdef *ctx, ushort siz, mcmon clinum, mclhd loadhd); + +/* + *   Allocate a new cache object, and associate it with a particular + *   client object number.  An error is signalled if the client object + *   number is already in use. + */ +/* uchar *mcmalonum(mcmcxdef *ctx, ushort siz, mcmon num); */ +#define mcmalonum(ctx, siz, num) mcmalo0(ctx, siz, (mcmon *)0, num, FALSE) + +/* + *   Reallocate an existing object.  The object's size is increased + *   or reduced according to newsize.  The object is locked if it is + *   not already, and the address of the object's memory is returned. + *   Note that the object can move when reallocated, even if it was + *   locked before the call. + */ +uchar *mcmrealo(mcmcxdef *ctx, mcmon objnum, ushort newsize); + +/* + *   Touch a cache object (rendering it dirty).  When an object is + *   written, the client must touch it to ensure that the version in + *   memory is not discarded.  The cache manager attempts to optimize + *   activity by not writing objects that can be reconstructed from the + *   load or swap file.  Touching the object informs the cache manager + *   that the object is different from any version it has in a swap or + *   load file.   + */ +/* void mcmtch(mcmcxdef *ctx, mcmon objnum); */ +#define mcmtch(ctx,obj) \ +  (mcmobje(ctx,obj)->mcmoflg |= MCMOFDIRTY) +/* was: (mcmobje(ctx,obj)->mcmoflg |= (MCMOFDIRTY | MCMOFNODISC)) */ + +/* get size of a cache manager object - object need not be locked */ +/* ushort mcmobjsiz(mcmcxdef *ctx, mcmon objn); */ +#define mcmobjsiz(ctx, objn) (mcmobje(ctx, objn)->mcmosiz) + +/* determine if object has ever been touched */ +/* int mcmobjdirty(mcmcxdef *ctx, mcmon objn); */ +#define mcmobjdirty(ctx, objn) \ + (mcmobje(ctx, objn)->mcmoflg & (MCMOFDIRTY | MCMOFNODISC)) + +/* get object's memory pointer - object must be locked for valid result */ +/* uchar *mcmobjptr(mcmcxdef *ctx, mcmon objn); */ +#define mcmobjptr(ctx, objn) (mcmobje(ctx, objn)->mcmoptr) + +/* + *   Free an object.  The memory occupied by the object is discarded, and + *   the object may no longer be referenced.   + */ +void mcmfre(mcmcxdef *ctx, mcmon obj); + +/* + *   "Revert" an object - convert it back to original state.  This + *   routine just invokes a client callback to do the actual reversion + *   work.  The callback is called immediately if the object is already + *   present in memory, but is deferred until the object is loaded/swapped + *   in if the object is not in memory.  + */ +/* void mcmrevert(mcmcxdef *ctx, mcmon objn); */ +#define mcmrevert(ctx, objn) \ + ((mcmobje(ctx, objn)->mcmoflg & MCMOFPRES) ? \ +  ((*(ctx)->mcmcxrvf)((ctx)->mcmcxrvc, objn), DISCARD 0) \ +  : DISCARD (mcmobje(ctx, objn)->mcmoflg |= MCMOFREVRT)) + +/* get current size of object cache */ +ulong mcmcsiz(mcmcxdef *ctx); + +/* change an object's swap handle (used by swapper) */ +/* void mcmcsw(mcmcx1def *ctx, ushort objn, mcsseg swapn, mcsseg oldswn); */ +#define mcmcsw(ctx, objn, swapn, oldswapn) \ +   ((*(ctx)->mcmcxcsw)(ctx, objn, swapn, oldswapn)) + +/* ------------------------------- PRIVATE ------------------------------- */ + +/* Unlock an object by its global handle */ +#ifdef MCM_NO_MACRO +void mcmgunlck(mcmcx1def *ctx, mcmon objnum); +#else /* MCM_NO_MACRO */ + +/* void mcmgunlck(mcmcx1def *ctx, mcmon objnum); */ +#define mcmgunlck(ctx,obj) \ + ((mcmgobje(ctx,obj)->mcmoflg & MCMOFLOCK) ? \ +  (--(mcmgobje(ctx,obj)->mcmolcnt) ? (void)0 : \ +    ((mcmgobje(ctx,obj)->mcmoflg&=(~MCMOFLOCK)), mcmuse(ctx,obj))) : \ +  (void)0) + +#endif /* MCM_NO_MACRO */ + +/* real memory allocator; clients use cover macros */ +uchar *mcmalo0(mcmcxdef *ctx, ushort siz, mcmon *nump, mcmon clinum, +               int noclitrans); + +/* free an object by global object number */ +void mcmgfre(mcmcx1def *ctx, mcmon obj); + +/* "use" an object (move to most-recent position in LRU chain) */ +void mcmuse(mcmcx1def *ctx, mcmon n); + +/* + *   Load or swap in a cache object which is currently unloaded, locking + *   it before returning.  Returns a pointer to the memory containing + *   the object, or a null pointer in case of error.  The object + *   remains fixed in memory at the returned location until unlocked. + */ +uchar *mcmload(mcmcxdef *ctx, mcmon objnum); + +/* + *   Size of each chunk of memory we'll request from the heap manager. + *   To cut down on wasted memory from the heap manager, we'll always make + *   our requests in this large size, then sub-allocate out of these + *   chunks.  + */ +#define MCMCHUNK  32768 + +/* + *   number of cache entries in a page - make this a power of 2 to keep + *   the arithmetic to find a cache object entry simple  + */ +#define MCMPAGECNT 256 + +/* + *   size of a page, in bytes + */ +#define MCMPAGESIZE (MCMPAGECNT * sizeof(mcmodef)) + +/* + *   When allocating memory, and we find a free block satisfying the + *   request, we will split the free block if doing so would result in + *   enough space in the second block.  MCMSPLIT specifies the minimum + *   size left over that will allow a split to occur. + */ +#define MCMSPLIT 64 + +/* get an object cache entyr given a GLOBAL object number */ +#define mcmgobje(ctx,num) (&((ctx)->mcmcxtab[(num)>>8][(num)&255])) + +/* get an object cache entry given a CLIENT object number */ +/* mcmodef *mcmobje(mcmcxdef *ctx, mcmon objnum) */ +#define mcmobje(ctx,num) mcmgobje((ctx)->mcmcxgl,mcmc2g(ctx,num)) + +/* allocate a block that will be locked for its entire lifetime */ +void *mcmptralo(mcmcxdef *ctx, ushort siz); + +/* free a block allocated with mcmptralo */ +void mcmptrfre(mcmcxdef *ctx, void *block); + +/* change an object's swap handle */ +void mcmcswf(mcmcx1def *ctx, mcmon objn, mcsseg swapn, mcsseg oldswapn); + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/memory_cache_heap.cpp b/engines/glk/tads/tads2/memory_cache_heap.cpp new file mode 100644 index 0000000000..fae3c5e080 --- /dev/null +++ b/engines/glk/tads/tads2/memory_cache_heap.cpp @@ -0,0 +1,51 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/tads2/memory_cache_heap.h" +#include "glk/tads/tads2/error.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* global to keep track of all allocations */ +IF_DEBUG(ulong mchtotmem;) + +uchar *mchalo(errcxdef *ctx, size_t siz, char *comment) { +    uchar *ret; + +    VARUSED(comment); +    IF_DEBUG(mchtotmem += siz;) + +    ret = (uchar *)osmalloc(siz); +    if (ret) +        return(ret); +    else { +        errsig(ctx, ERR_NOMEM); +        NOTREACHEDV(uchar *); +        return 0; +    } +} + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/tads2/memory_cache_heap.h b/engines/glk/tads/tads2/memory_cache_heap.h new file mode 100644 index 0000000000..269cdec3ad --- /dev/null +++ b/engines/glk/tads/tads2/memory_cache_heap.h @@ -0,0 +1,60 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * Memory cache heap manager + * + * This is the low-level heap manager, which maintains a list of non-relocatable, + * non-swappable blocks of memory.  The cache manager uses the heap manager for + * its basic storage needs. + */ + +#ifndef GLK_TADS_TADS2_MEMORY_CACHE_HEAP +#define GLK_TADS_TADS2_MEMORY_CACHE_HEAP + +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/error_handling.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/** + *   Allocate a block of memory; returns pointer to the block. + *   An out-of-memory error is signalled if insufficient memory + *   is available.  The comment is for debugging purposes only. + */ +uchar *mchalo(errcxdef *ctx, size_t siz, char *comment); + +/* allocate a structure */ +#define MCHNEW(errctx, typ, comment) \ + ((typ *)mchalo(errctx, sizeof(typ), comment)) + +/* free a block of memory */ +/* void mchfre(uchar *ptr); */ +#define mchfre(ptr) (osfree(ptr)) + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/memory_cache_loader.cpp b/engines/glk/tads/tads2/memory_cache_loader.cpp new file mode 100644 index 0000000000..f403299167 --- /dev/null +++ b/engines/glk/tads/tads2/memory_cache_loader.cpp @@ -0,0 +1,31 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/tads2/memory_cache_loader.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/tads2/memory_cache_loader.h b/engines/glk/tads/tads2/memory_cache_loader.h new file mode 100644 index 0000000000..c7ec22db57 --- /dev/null +++ b/engines/glk/tads/tads2/memory_cache_loader.h @@ -0,0 +1,57 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * Memory cache swap manager + * + * The cache swap manager provides swap file services to the memory + * cache manager.  The cache manager calls the swap manager to write + * objects to the swap file and read in previously swapped-out objects. + */ + +#ifndef GLK_TADS_TADS2_MEMORY_CACHE_LOADER +#define GLK_TADS_TADS2_MEMORY_CACHE_LOADER + +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/error_handling.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/** + * Loader context + */ +struct mclcxdef { +    errcxdef *mclcxerr;			/* error handling context */ +}; + +/** + * Loader handle + */ +typedef ulong mclhd;			/* essentially a seek address */ + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/memory_cache_swap.cpp b/engines/glk/tads/tads2/memory_cache_swap.cpp new file mode 100644 index 0000000000..114a7258e9 --- /dev/null +++ b/engines/glk/tads/tads2/memory_cache_swap.cpp @@ -0,0 +1,31 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/tads2/memory_cache_swap.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/tads2/memory_cache_swap.h b/engines/glk/tads/tads2/memory_cache_swap.h new file mode 100644 index 0000000000..f1b9849aec --- /dev/null +++ b/engines/glk/tads/tads2/memory_cache_swap.h @@ -0,0 +1,122 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * Memory cache swap manager + * + * The cache swap manager provides swap file services to the memory + * cache manager.  The cache manager calls the swap manager to write + * objects to the swap file and read in previously swapped-out objects. + */ + +#ifndef GLK_TADS_TADS2_MEMORY_CACHE_SWAP +#define GLK_TADS_TADS2_MEMORY_CACHE_SWAP + +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/error_handling.h" +#include "glk/tads/osfrobtads.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* Forward declarations */ +struct mcmcx1def; + +/** + * swap segment descriptor + */ +struct mcsdsdef { +    ulong    mcsdsptr;                         /* seek pointer in swap file */ +    ushort   mcsdssiz;                         /* size of this swap segment */ +    ushort   mcsdsosz;                 /* size of object written to segment */ +    uint     mcsdsobj;                                  /* client object ID */ +    ushort   mcsdsflg;                                             /* flags */ +#define      MCSDSFINUSE   0x01                        /* segment is in use */ +}; + +/** + * mcsseg - swap segment handle.  All swap-file segments are addressed + * through this handle type.   + */ +typedef ushort mcsseg; + +/** + * Swap manager context + */ +struct mcscxdef { +    osfildef   *mcscxfp;                                /* swap file handle */ +    char       *mcscxfname;                            /* name of swap file */ +    errcxdef   *mcscxerr;                         /* error handling context */ +    ulong       mcscxtop;              /* top of swap file allocated so far */ +    ulong       mcscxmax;        /* maximum size of swap file we're allowed */ +    mcsdsdef  **mcscxtab;                     /* swap descriptor page table */ +    mcsseg      mcscxmsg;               /* maximum segment allocated so far */ +    mcmcx1def *mcscxmem;                   /* memory manager context */ +}; + +#define MCSSEGINV ((mcsseg)~0)      /* invalid segment ID - error indicator */ + +/* initialize swapper - returns 0 for success, other for error */ +void mcsini(struct mcscxdef *ctx, struct mcmcx1def *gmemctx, ulong maxsiz, +            osfildef *fp, char *swapfilename, struct errcxdef *errctx); + +/* close swapper (release memory areas) */ +void mcsclose(struct mcscxdef *ctx); + +/** + *   Swap an object out.  The caller specifies the location and size of + *   the object, as well as a unique handle (arbitrary, up to the caller; + *   the only requirement is that it be unique among all caller objects + *   and always the same for a particular caller's object) and the + *   previous swap handle if the object ever had one.  If the object is + *   not dirty (it hasn't been written since being swapped in), and the + *   swap manager hasn't reused the swap slot, the swap manager doesn't + *   need to write the memory, since it already has a copy on disk; + *   instead, it can just mark the slot as back in use.  If the caller + *   doesn't wish to take advantage of this optimization, always pass in + *   dirty == TRUE, which will force a write regardless of the object ID. + */ +mcsseg mcsout(struct mcscxdef *ctx, uint objid, uchar *objptr, +              ushort objsize, mcsseg oldswapseg, int dirty); +             +/* Swap an object in */ +void mcsin(struct mcscxdef *ctx, mcsseg swapseg, uchar *objptr, ushort size); + + +/* number of page pointers in page table (max number of pages) */ +#define MCSPAGETAB 256 + +/* number of swap descriptors in a page */ +#define MCSPAGECNT 256 + +/* find swap descriptor corresponding to swap segment number */ +#define mcsdsc(ctx,seg) (&(ctx)->mcscxtab[(seg)>>8][(seg)&255]) + +/* write out a swap segment */ +void mcswrt(mcscxdef *ctx, mcsdsdef *desc, uchar *buf, ushort bufl); + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/object.cpp b/engines/glk/tads/tads2/object.cpp new file mode 100644 index 0000000000..2ba1161d86 --- /dev/null +++ b/engines/glk/tads/tads2/object.cpp @@ -0,0 +1,35 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/tads2/object.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + + + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/object.h b/engines/glk/tads/tads2/object.h new file mode 100644 index 0000000000..5e2499ccc2 --- /dev/null +++ b/engines/glk/tads/tads2/object.h @@ -0,0 +1,396 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_TADS_TADS2_OBJECT +#define GLK_TADS_TADS2_OBJECT + +#include "glk/tads/tads.h" +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/memory_cache.h" +#include "glk/tads/tads2/property.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/** + * object number + */ +typedef ushort objnum; + +/** + * For non-class objects, we'll leave some space free in the object so + * that a few properties can be added without having to resize the + * object.  Class objects will probably never have anything added, so + * there's no need for extra space.   + */ +#define OBJEXTRA 64 + +#   define   OBJFCLS   0x01                            /* object is a class */ + +/** + * The object structure is actually laid out portably, using unaligned + * 2-byte arrays, stored least significant byte first, for each ushort + * (including the objnum array for the superclasses).  The actual + * entries are at these offsets on all machines:  + * + *      objws      0 + *      objflg     2 + *      objnsc     4 + *      objnprop   6 + *      objfree    8 + *      objrst     10 + *      objstat    12 + *      objsc[0]   14 + *      objsc[1]   16 + *      etc + *       + * If the OBJFINDEX flag is set, the object has a property index. + * The index occurs after the last superclass (so it's where the + * property data would go if there were no index), and the property + * data follows.  Each index entry consists of a pair of two-byte + * entries:  the first is the property number, and the second is + * its offset within the object.  For performance reasons, an index + * is only built on a class object -- whenever a property is changed + * within an object, the entire index must be rebuilt, because the + * locations of many properties within the object can be changed by + * a single property change in the object.  The index is ordered by + * property number, so it can be searched using a binary search. + * Furthermore, "ignored" properties are excluded from the index; + * only the active instance of a particular property is stored. + * The index must be maintained by all routines that can change + * property information:  setp, delp, revert, etc. + *  + * Preceding the index table is a two-byte entry that gives the + * offset of the properties.  Since the properties immediately + * follow the index, this can be used to deduce how large a space + * is available for the index itself. + */ +typedef uchar objdef; +#define OBJDEFSIZ 14   /* "sizeof(objdef)" - size of object header w/o sc's */ + +/* object flags */ +#define OBJFCLASS  1                                   /* object is a class */ +#define OBJFINDEX  2                         /* object has a property index */ +#define OBJFMOD    4      /* object has been modified by a newer definition */ + +/* undo context */ +struct objucxdef { +    mcmcxdef *objucxmem;                           /* cache manager context */ +    errcxdef *objucxerr;                                   /* error context */ +    ushort    objucxsiz;                         /* size of the undo buffer */ +    ushort    objucxhead;                  /* head (position of next write) */ +    ushort    objucxtail;               /* tail (position of oldest record) */ +    ushort    objucxprv;                           /* previous head pointer */ +    ushort    objucxtop;                      /* highest head value written */ +    void    (*objucxcun)(void *ctx, uchar *data); +                                              /* apply a client undo record */ +    ushort  (*objucxcsz)(void *ctx, uchar *data); +                                        /* get size of a client undo record */ +    void     *objucxccx;                             /* client undo context */ +    uchar     objucxbuf[1];                                  /* undo buffer */ +}; + +/* + *   Undo records are kept in a circular buffer allocated as part of an + *   undo context.  Offsets within the buffer are kept for the head, tail, + *   and previous head records.  The head always points to the byte at + *   which the next undo record will be written.  The previous head points + *   to the most recently written undo record; it contains a back link to + *   the undo record before that, and so forth back through the entire + *   chain.  (These reverse links are necessary because undo records vary + *   in size depending on the data contained within.)  The tail points to + *   the oldest undo record that's still in the buffer.  Conceptually, the + *   head is always "above" the tail in the buffer; since the buffer is + *   circular, the tail may have a higher address, but this just means + *   that the buffer wraps around at the top.  When the head bumps into + *   the tail (i.e., the head address is physically below or equal to the + *   tail address, and the head is then advanced so that its address + *   becomes higher than the tail's), the tail is advanced by discarding + *   as many of the least recent undo records as necessary to make room + *   for the new head position.  When the head and the previous head point + *   to the same place, we have no undo records in the buffer.   + */ +/** + *   The first byte of an undo record specifies what action is to be + *   undone.  If a property was added, it is undone merely by deleting the + *   property.  If a property was changed, it is undone by setting the + *   property back to its old value.  An additional special flag indicates + *   a "savepoint."  Normally, all changes back to a savepoint will be + *   undone.   + */ +#define OBJUADD    1             /* a property was added (undo by deleting) */ +#define OBJUCHG    2   /* a property was changed (change back to old value) */ +#define OBJUSAV    3          /* savepoint marker (no property information) */ +#define OBJUOVR    4     /* override original property (set orig to IGNORE) */ +#define OBJUCLI    5                /* client undo record (any client data) */ + +/* + *   After the control byte (OBJUxxx), the object number, property + *   number, datatype, and data value will follow; some or all of these + *   may be omitted, depending on the control byte.  + */ + +/* get object flags */ +#define objflg(o) ((ushort)osrp2(((char *)(o)) + 2)) + +/* get object flags */ +#define objsflg(o, val) oswp2(((char *)(o)) + 2, val) + +/* given an object pointer, get a pointer to the first prpdef */ +/* prpdef *objprp(objdef *objptr); */ +#define objprp(o) ((prpdef *)(objsc(o) + 2*objnsc(o))) + +/* given an object pointer, get number of properties in the prpdef */ +/* int objnprop(objdef *objptr); */ +#define objnprop(o) ((ushort)osrp2(((char *)(o)) + 6)) + +/* set number of properties */ +/* void objsnp(objdef *objptr, int newnum); */ +#define objsnp(o,n) oswp2(((char *)(o)) + 6, n) + +/* given an object pointer, get offset of free space */ +/* int objfree(objdef *objptr); */ +#define objfree(o) ((ushort)osrp2(((char *)(o)) + 8)) + +/* set free space pointer */ +/* void objsfree(objdef *objptr, int newfree); */ +#define objsfree(o,n) oswp2(((char *)(o)) + 8, n) + +/* get number of static properties */ +/* ushort objstat(objdef *objptr); */ +#define objstat(o) ((ushort)osrp2(((char *)(o)) + 10)) + +/* set number of static properties */ +/* void objsetst(objdef *objptr, int newstat); */ +#define objsetst(o,n) oswp2(((char *)(o)) + 10, n) + +/* get reset size (size of static properties) */ +/* ushort objrst(objdef *objptr); */ +#define objrst(o) ((ushort)osrp2(((char *)(o)) + 12)) + +/* set reset size */ +/* void objsetrst(objdef *objptr, uint newrst); */ +#define objsetrst(o,n) oswp2(((char *)(o)) + 12, n) +  +/* given an object pointer, get first superclass pointer */ +/* uchar *objsc(objdef *objptr); */ +#define objsc(o) (((uchar *)(o)) + OBJDEFSIZ) + +/* given an object pointer, get number of superclasses */ +/* int objnsc(objdef *objptr); */ +#define objnsc(o) ((ushort)osrp2(((char *)(o)) + 4)) + +/* set number of superclasses */ +/* void objsnsc(objdef *objptr, int num); */ +#define objsnsc(o,n) oswp2(((char *)(o)) + 4, n) + +/* given a prpdef, get the next prpdef */ +/* prpdef *objpnxt(prpdef *p); */ +#define objpnxt(p) \ + ((prpdef *)(((uchar *)(p)) + PRPHDRSIZ + prpsize(p))) + +/* get pointer to free prpdef */ +/* prpdef *objpfre(objdef *objptr); */ +#define objpfre(o) ((prpdef *)(((uchar *)(o)) + objfree(o))) + +/* given a prpdef and an object pointer, compute the prpdef offset */ +/* uint objpofs(objdef *objptr, prpdef *propptr); */ +#define objpofs(o,p) ((uint)((p) ? (((uchar *)(p)) - ((uchar *)(o))) : 0)) + +/* given an object pointer and a property offset, get prpdef pointer */ +/* prpdef *objofsp(objdef *objptr, uint propofs); */ +#define objofsp(o,ofs) ((prpdef *)((ofs) ? (((uchar *)(o)) + (ofs)) : 0)) + +/* + *   Get the first superclass of an object.  If it doesn't have any + *   superclasses, return invalid. + */ +objnum objget1sc(mcmcxdef *ctx, objnum objn); + +/* + *   Get an object's property WITHOUT INHERITANCE.  If the object has the + *   indicated property set, the byte OFFSET of the prpdef within the + *   object is returned.  The offset will remain valid until any type of + *   operation that sets a property in the object (such as objdelp, + *   objsetp, or an undo operation).  An offset of zero means that the + *   property was not set in the object. + */ +uint objgetp(mcmcxdef *ctx, objnum objn, prpnum prop, +             dattyp *typptr); + +/* + *   Get the *ending* offset of the given property's value, without any + *   inheritance.  Returns the byte offset one past the end of the + *   property's data.   + */ +uint objgetp_end(mcmcxdef *ctx, objnum objn, prpnum prop); + +/* + *   Get a property of an object, either from the object or from a + *   superclass (inherited).  If the inh flag is TRUE, we do not look + *   at all in the object itself, but restrict our search to inherited + *   properties only.  We return the byte OFFSET of the prpdef within + *   the object in which the prpdef is found; the superclass object + *   itself is NOT locked upon return, but we will NOT unlock the + *   object passed in.  If the offset is zero, the property was not + *   found.  The offset returned is valid until any operation that + *   sets a property in the object (such as objdelp, objsetp, or an + *   undo operation). + */ +uint objgetap(mcmcxdef *ctx, noreg objnum objn, prpnum prop, +              objnum *orn, int inh); + +/* + *   expand an object by a requested amount, returning a pointer to the + *   object's new location if it must be moved.  The object will be + *   unlocked and relocked by this call.  On return, the actual amount + *   of space ADDED to the object will be returned. + */ +objdef *objexp(mcmcxdef *ctx, objnum obj, ushort *siz); + +/* + *   Set an object's property, deleting the original value of the + *   property if it existed.  If an undo context is provided, write an + *   undo record for the change; if the undo context pointer is null, no + *   undo information is retained.  + */ +void objsetp(mcmcxdef *ctx, objnum obj, prpnum prop, +             dattyp typ, void *val, objucxdef *undoctx); + +/*  + *   Delete a property.  If mark_only is true, we'll only mark the + *   property as deleted without actually reclaiming its space; this is + *   necessary when removing a code property (type DAT_CODE) any time + *   other code properties may follow, because p-code is not entirely + *   self-relative and thus can't always be relocated within an object.  + */ +void objdelp(mcmcxdef *mctx, objnum objn, prpnum prop, int mark_only); + +/* + *   Set up for emitting code into an object.  Writes a property header + *   of type 'code', and returns the offset of the next free byte in the + *   object.  Call objendemt when done.  The datatype argument is + *   provided so that list generation can be done through the same + *   mechanism, since parser lists must be converted to run-time + *   lists via the code generator. + */ +uint objemt(mcmcxdef *ctx, objnum objn, prpnum prop, dattyp typ); + +/* done emitting code into property, finish setting object info */ +void objendemt(mcmcxdef *ctx, objnum objn, prpnum prop, uint endofs); + +/* + *   Determine if undo records should be kept.  Undo records should be + *   kept only if a savepoint is present in the undo log.  If no savepoint + *   is present, adding undo records would be useless, since it will not + *   be possible to apply the undo information.  + */ +int objuok(objucxdef *undoctx); + +/* + *   Reserve space in an undo buffer, deleting old records as needed. + *   Returns a pointer to the reserved space.  + */ +uchar *objures(objucxdef *undoctx, uchar cmd, ushort siz); + +/* advance the tail pointer in an undo buffer over the record it points to */ +void objutadv(objucxdef *undoctx); + +/* apply one undo record, and remove it from undo list */ +void obj1undo(mcmcxdef *mctx, objucxdef *undoctx); + +/* + *   Undo back to the most recent savepoint.  If there is no savepoint in + *   the undo list, NOTHING will be undone.  This prevents reaching an + *   inconsistent state in which some, but not all, of the operations + *   between two savepoints are undone: either all operations between two + *   savepoints will be undone, or none will.  + */ +void objundo(mcmcxdef *mctx, objucxdef *undoctx); + +/* set an undo savepoint */ +void objusav(objucxdef *undoctx); + +/* initialize undo context */ +objucxdef *objuini(mcmcxdef *memctx, ushort undosiz, +                   void (*undocb)(void *ctx, uchar *data), +                   ushort (*sizecb)(void *ctx, uchar *data), +                   void *callctx); + +/* free the undo context - releases memory allocated by objuini() */ +void objuterm(objucxdef *undoctx); + +/* discard all undo context (for times such as restarting) */ +void objulose(objucxdef *undoctx); + +/* + *   Allocate and initialize a new object.  The caller specifies the + *   number of superclasses to be allocated, and the amount of space (in + *   bytes) for the object's property data.  The caller must fill in the + *   superclass array.  Upon return, the object is allocated and locked, + *   and is initialized with no properties.  A pointer to the object's + *   memory is returned, and *objnptr receives the object number. + */ +objdef *objnew(mcmcxdef *mctx, int sccnt, ushort propspace, +               objnum *objnptr, int classflg); +             +/* initialize an already allocated object */ +void objini(mcmcxdef *mctx, int sccnt, objnum objn, int classflg); + +/* + *   Add space for additional superclasses to an object.  The object can + *   already have some properties set (if it doesn't, it can just be + *   reinitialized). + */ +void objaddsc(mcmcxdef *mctx, int sccnt, objnum objn); + +/* + *   Delete an object's properties and superclasses.  The 'mindel' + *   parameter specifies the minimum property number to be deleted. + *   Properties below this are considered "system" properties that are not + *   to be deleted.  This could be used by a development environment to + *   store the source for an object as a special system property in the + *   object; when the object is recompiled, all of the object's properties + *   and superclasses must be deleted except the source property, which is + *   retained even after recompilation.  + */ +void objclr(mcmcxdef *mctx, objnum objn, prpnum mindel); + +/* Build or rebuild an object's property index */ +void objindx(mcmcxdef *mctx, objnum objn); + +/* set up just-compiled object: mark static part and original properties */ +void objcomp(mcmcxdef *mctx, objnum objn, int for_debug); + +/* revert an object to original post-compilation state */ +void objrevert(void *mctx, mcmon objn); + +/* reset 'ignore' flags for a newly reconstructed object */ +void objsetign(mcmcxdef *mctx, objnum objn); + + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/opcode_defs.h b/engines/glk/tads/tads2/opcode_defs.h new file mode 100644 index 0000000000..32519ae8f7 --- /dev/null +++ b/engines/glk/tads/tads2/opcode_defs.h @@ -0,0 +1,218 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_TADS_TADS2_OPCODE_DEFS +#define GLK_TADS_TADS2_OPCODE_DEFS + +/* + * Opcode definitions + * + * Lifted largely from the old TADS, since the basic run - time interpreter's + * operation is essentially the same. + */ + +#include "glk/tads/tads.h" +#include "glk/tads/tads2/data.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +#define OPCPUSHNUM  1       /* push a constant numeric value */ +#define OPCPUSHOBJ  2       /* push an object */ +#define OPCNEG      3       /* unary negation */ +#define OPCNOT      4       /* logical negation */ +#define OPCADD      5       /* addition/list concatenation */ +#define OPCSUB      6       /* subtraction/list difference */ +#define OPCMUL      7       /* multiplication */ +#define OPCDIV      8       /* division */ +#define OPCAND      9       /* logical AND */ +#define OPCOR       10      /* logical OR */ +#define OPCEQ       11      /* equality */ +#define OPCNE       12      /* inequality */ +#define OPCGT       13      /* greater than */ +#define OPCGE       14      /* greater or equal */ +#define OPCLT       15      /* less than */ +#define OPCLE       16      /* less or equal */ +#define OPCCALL     17      /* call a function */ +#define OPCGETP     18      /* get property */ +#define OPCGETPDATA 19      /* get a property, allowing only data values */ +#define OPCGETLCL   20      /* get a local variable's value */ +#define OPCPTRGETPDATA 21   /* get property via pointer; only allow data */    +#define OPCRETURN   22      /* return without a value */ +#define OPCRETVAL   23      /* return a value */ +#define OPCENTER    24      /* enter a function */ +#define OPCDISCARD  25      /* discard top of stack */ +#define OPCJMP      26      /* unconditional jump */ +#define OPCJF       27      /* jump if false */ +#define OPCPUSHSELF 28      /* push current object */ +#define OPCSAY      29      /* implicit printout for doublequote strings */ +#define OPCBUILTIN  30      /* call a built-in function */ +#define OPCPUSHSTR  31      /* push a string */ +#define OPCPUSHLST  32      /* push a list */ +#define OPCPUSHNIL  33      /* push the NIL value */ +#define OPCPUSHTRUE 34      /* push the TRUE value */ +#define OPCPUSHFN   35      /* push the address of a function */ +#define OPCGETPSELFDATA 36  /* push property of self; only allow data */ + +#define OPCPTRCALL  38          /* call function pointed to by top of stack */ +#define OPCPTRINH   39          /* inherit pointer to property (stack=prop) */ +#define OPCPTRGETP  40          /* get property by pointer (stack=obj,prop) */ + +#define OPCPASS     41      /* pass to inherited handler */ +#define OPCEXIT     42      /* exit turn, but continue with fuses/daemons */ +#define OPCABORT    43      /* abort turn, skipping fuses/daemons */ +#define OPCASKDO    44      /* ask for a direct object */ +#define OPCASKIO    45      /* ask for indirect object and set preposition */ + +/* explicit superclass inheritance opcodes */ +#define OPCEXPINH   46      /* "inherited <superclass>.<property>" */ +#define OPCEXPINHPTR 47     /* "inherited <superclass>.<prop-pointer>" */ + +/* + *   Special opcodes for peephole optimization.  These are essentially + *   pairs of operations that occur frequently so have been collapsed into + *   a single instruction. + */ +#define OPCCALLD    48      /* call function and discard value */ +#define OPCGETPD    49      /* evaluate property and discard any value */ +#define OPCBUILTIND 50      /* call built-in function and discard value */ + +#define OPCJE       51      /* jump if equal */ +#define OPCJNE      52      /* jump if not equal */ +#define OPCJGT      53      /* jump if greater than */ +#define OPCJGE      54      /* jump if greater or equal */ +#define OPCJLT      55      /* jump if less than */ +#define OPCJLE      56      /* jump if less or equal */ +#define OPCJNAND    57      /* jump if not AND */ +#define OPCJNOR     58      /* jump if not OR */ +#define OPCJT       59      /* jump if true */ + +#define OPCGETPSELF 60      /* get property of the 'self' object */ +#define OPCGETPSLFD 61      /* get property of 'self' and discard result */ +#define OPCGETPOBJ  62      /* get property of a given object */ +                            /*  note: differs from GETP in that object is */ +                            /*  encoded into the instruction */ +#define OPCGETPOBJD 63      /* get property of an object and discard result */ +#define OPCINDEX    64      /* get an indexed entry from a list */ + +#define OPCPUSHPN   67      /* push a property number */ + +#define OPCJST      68      /* jump and save top-of-stack if true */ +#define OPCJSF      69      /* jump and save top-of-stack if false */ +#define OPCJMPD     70      /* discard stack and then jump unconditionally */ + +#define OPCINHERIT  71      /* inherit a property from superclass */ +#define OPCCALLEXT  72      /* call external function */ +#define OPCDBGRET   73      /* return to debugger (no stack frame leaving) */ + +#define OPCCONS     74      /* construct list from top two stack elements */ +#define OPCSWITCH   75      /* switch statement */ + +#define OPCARGC     76      /* get argument count */ +#define OPCCHKARGC  77      /* check actual arguments against formal count */ + +#define OPCLINE     78      /* line record */ +#define OPCFRAME    79      /* local variable frame record */ +#define OPCBP       80      /* breakpoint - replaces an OPCLINE instruction */ +#define OPCGETDBLCL 81                                /* get debugger local */ +#define OPCGETPPTRSELF 82                 /* get property pointer from self */ +#define OPCMOD      83                                            /* modulo */ +#define OPCBAND     84                                        /* binary AND */ +#define OPCBOR      85                                         /* binary OR */ +#define OPCXOR      86                                        /* binary XOR */ +#define OPCBNOT     87                                   /* binary negation */ +#define OPCSHL      88                                    /* bit shift left */ +#define OPCSHR      89                                   /* bit shift right */ + +#define OPCNEW      90                                 /* create new object */ +#define OPCDELETE   91                                     /* delete object */ + + +/* ----- opcodes 192 and above are reserved for assignment operations ----- */ + +/* +ASSIGNMENT OPERATIONS +    When (opcode & 0xc0 == 0xc0), we have an assignment operation. +    (Note that this means that opcodes from 0xc0 up are all reserved +    for assignment operations.)  The low six bits of the opcode +    specify exactly what kind of operation is to be performed: +     +    bits 0-1:  specifies destination type: +               00    2-byte operand is local number +               01    2-byte operand is property to set in obj at tos +               10    tos is index, [sp-1] is list to be indexed and set +               11    tos is property pointer, [sp-1] is object +     +    bits 2-4:  specifies assignment operation: +               000   := (direct assignment) +               001   += (add tos to destination) +               010   -= (subtract tos from destination) +               011   *= (multiply destination by tos) +               100   /= (divide destination by tos) +               101   ++ (increment tos) +               110   -- (decrement tos) +               111   *reserved* +                +    bit 5:     specifies what to do with value computed by assignment +               0     leave on stack (implies pre increment/decrement) +               1     discard (implies post increment/decrement) +*/ +#define OPCASI_MASK      0xc0                     /* assignment instruction */ + +#define OPCASIDEST_MASK  0x03              /* mask to get destination field */ +#define OPCASILCL        0x00                          /* assign to a local */ +#define OPCASIPRP        0x01               /* assign to an object.property */ +#define OPCASIIND        0x02             /* assign to an element of a list */ +#define OPCASIPRPPTR     0x03                /* assign property via pointer */ + +#define OPCASITYP_MASK   0x1c          /* mask to get assignment type field */ +#define OPCASIDIR        0x00                          /* direct assignment */ +#define OPCASIADD        0x04                             /* assign and add */ +#define OPCASISUB        0x08                        /* assign and subtract */ +#define OPCASIMUL        0x0c                        /* assign and multiply */ +#define OPCASIDIV        0x10                          /* assign and divide */ +#define OPCASIINC        0x14                                  /* increment */ +#define OPCASIDEC        0x18                                  /* decrement */ +#define OPCASIEXT        0x1c                     /* other - extension flag */ + +/* extended assignment flags - next byte when OPCASIEXT is used */ +#define OPCASIMOD        1                             /* modulo and assign */ +#define OPCASIBAND       2                         /* binary AND and assign */ +#define OPCASIBOR        3                          /* binary OR and assign */ +#define OPCASIXOR        4                         /* binary XOR and assign */ +#define OPCASISHL        5                         /* shift left and assign */ +#define OPCASISHR        6                        /* shift right and assign */ + + +#define OPCASIPRE_MASK   0x20                    /* mask for pre/post field */ +#define OPCASIPOST       0x00                       /* increment after push */ +#define OPCASIPRE        0x20                      /* increment before push */ + +/* some composite opcodes for convenience */ +#define OPCSETLCL (OPCASI_MASK | OPCASILCL | OPCASIDIR) + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/os.cpp b/engines/glk/tads/tads2/os.cpp index 15a291a87b..e51a4d938e 100644 --- a/engines/glk/tads/tads2/os.cpp +++ b/engines/glk/tads/tads2/os.cpp @@ -26,6 +26,7 @@ namespace Glk {  namespace TADS {  namespace TADS2 { +#if 0  OS::OS(OSystem *syst, const GlkGameDescription &gameDesc) : TADS(syst, gameDesc),  		status_mode(0) {  	Common::fill(&status_left[0], &status_left[OSS_STATUS_STRING_LEN], '\0'); @@ -345,6 +346,7 @@ int OS::oss_getc_from_window(winid_t win) {  	buffered_char = oss_convert_keystroke_to_tads(ev.val1);  	return 0;  } +#endif  } // End of namespace TADS2  } // End of namespace TADS diff --git a/engines/glk/tads/tads2/os.h b/engines/glk/tads/tads2/os.h index 45ab804f95..5dcc83c935 100644 --- a/engines/glk/tads/tads2/os.h +++ b/engines/glk/tads/tads2/os.h @@ -20,218 +20,5136 @@   *   */ +/* Portable interfaces to OS-specific functions + * + * This file defines interfaces to certain functions that must be called + * from portable code, but which must have system-specific implementations. + */ +  #ifndef GLK_TADS_TADS2_OS  #define GLK_TADS_TADS2_OS -#include "glk/tads/tads.h" -#include "glk/tads/tads2/types.h" +#include "common/system.h" +#include "glk/tads/osfrobtads.h" +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/appctx.h"  namespace Glk {  namespace TADS {  namespace TADS2 { -/** - * Operating system compatibility layer - */ -class OS : public TADS { -protected: -	char status_left[OSS_STATUS_STRING_LEN]; -	char status_right[OSS_STATUS_STRING_LEN]; -	int status_mode; -protected: -	/** -	 * Constructor -	 */ -	OS(OSystem *syst, const GlkGameDescription &gameDesc); - -	/** -	 * Terminates the game -	 */ -	void os_terminate(int rc); - -	/** -	 * \defgroup Type Conversions -	 * @{ -	 */ - -	/** -	 * Change a TADS prompt type (OS_AFP_*) into a Glk prompt type. -	 */ -	uint oss_convert_prompt_type(int type); -	 -	/** -	 * Change a TADS file type (OSFT*) into a Glk file type. -	 */ -	uint oss_convert_file_type(int type); - -	/** -	 * Change a fileref ID (frefid_t) to a special string and put it in the -	 * buffer which is passed to it. The string is given by -	 *   OSS_FILEREF_STRING_PREFIX + 'nnnnn' + OSS_FILEREF_STRING_SUFFIX -	 * where 'nnnnn' is the frefid_t pointer converted into a string of decimal -	 * numbers. This is only really practical for 32-bit pointers; if we use -	 * 64-bit pointers I'll have to start using a hash table or use hex -	 * numbers. -	 */ -	uint oss_convert_fileref_to_string(frefid_t file_to_convert, char *buffer, int buf_len); - -	/** -	 * Turn a filename or a special fileref string into an actual fileref. -	 * Notice that, since Glk doesn't know paths, we take this opportunity to -	 * call oss_check_path, which should do the OS-dependent path changing -	 * in the event that the filename contains path information -	 */ -	frefid_t oss_convert_string_to_fileref(char *buffer, uint usage); - -	/** -	 * Tell us if the passed string is a hashed fileref or not -	 */ -	bool oss_is_string_a_fileref(char *buffer); - -	/** -	 * Change a Glk key into a TADS one, using the CMD_xxx codes -	 */ -	unsigned char oss_convert_keystroke_to_tads(uint key); - -	/**@}*/ - -	/** -	 * \defgroup Directory/File methods -	 * @{ -	 */ - -	/** -	 * If a filename contains path information, change dirs to that path. -	 * Returns true if the path was fiddled with -	 */ -	bool oss_check_path(char *filename); - -	/** -	 * In case we changed directories in oss_check_path, change back to the -	 * original executable directory -	 */ -	void oss_revert_path(); - -	/** -	 * Open a stream, given a string, usage, and a filemode. tadsusage is the -	 * TADS filemode (OSFT*); tbusage is either fileusage_TextMode or -	 * fileusage_BinaryMode (from Glk). -	 */ -	osfildef *oss_open_stream(char *buffer, uint tadsusage, uint tbusage, -		uint fmode, uint rock); - -	/** -	 * Get a pointer to the root name portion of a filename.  This is the part -	 * of the filename after any path or directory prefix.  For example, on -	 * Unix, given the string "/home/mjr/deep.gam", this function should return -	 * a pointer to the 'd' in "deep.gam".  If the filename doesn't appear to -	 * have a path prefix, it should simply return the argument unchanged. -	 */ -	const char *os_get_root_name(const char *buf) const { return buf; } - -	/** -	 * Open a file for access -	 */ -	osfildef *osfoprb(const char *fname, uint typ = 0); - -	/** -	 * Receive notification that a character mapping file has been loaded.  We -	 * don't need to do anything with this information, since we we're relying -	 * on the Glk layer and ScummVM backend to handle all that -	 */ -	void os_advise_load_charmap(const char *id, const char *ldesc, const char *sysinfo) { -		// No implementation needed -	} - -	/** -	 * Generate a filename for a character mapping table.  On Windows, the -	 * filename is always simply "win" plus the internal ID plus ".tcp". -	 */ -	void os_gen_charmap_filename(char *filename, const char *internal_id, -		const char *argv0); - -	/**@}*/ - -	/** -	 * \defgroup The main text area print routines -	 * @{ -	 */ - -	/** -	 * Process hilighting codes while printing a string -	 */ -	void oss_put_string_with_hilite(winid_t win, const char *str, size_t len); - -	/** -	 * Status line handling -	 */ -	void oss_draw_status_line(); - -	void oss_change_status_string(char *dest, const char *src, size_t len); -	void oss_change_status_left(const char *str, size_t len); -	void oss_change_status_right(const char *str); -	int os_get_status() const { return status_mode; } - -	/** -	 * Flush the output -	 */ -	void os_flush(); - -	/** -	 * Print a null terminated string -	 */ -	void os_printz(const char *str) { os_print(str, strlen(str)); } - -	/** -	 * Print a string -	 */ -	void os_print(const char *str, size_t len); - -	/**@}*/ - -	/** -	 * \defgroup Memory routines -	 * @{ -	 */ - -	/** -	 * Compare two strings -	 */ -	int memicmp(const char *s1, const char *s2, int len); - -	/**@}*/ - -	/** -	 * \defgroup Input routines -	 * @{ -	 */ - -	/** -	 * Wait for a key to be hit -	 */ -	void os_waitc(void) { os_getc(); } - -	/** -	 * Get a character from the keyboard. For extended characters, return 0, -	 * then return the extended key at the next call to this function -	 */ -	int os_getc() { -		return oss_getc_from_window(story_win); -	} - -	/** -	 * Accept a keystroke in the passed window -	 */ -	int oss_getc_from_window(winid_t win); - - -	/** -	 * Print a message (with os_print) and wait for a key -	 */ -	void os_expause(); - -	/**@}*/ + +/* ------------------------------------------------------------------------ */ +/* + *   A note on character sets: + *    + *   Except where noted, all character strings passed to and from the + *   osxxx functions defined herein use the local operating system + *   representation.  On a Windows machine localized to Eastern Europe, + *   for example, the character strings passed to and from the osxxx + *   functions would use single-byte characters in the Windows code page + *   1250 representation. + *    + *   Callers that use multiple character sets must implement mappings to + *   and from the local character set when calling the osxxx functions. + *   The osxxx implementations are thus free to ignore any issues related + *   to character set conversion or mapping. + *    + *   The osxxx implementations are specifically not permitted to use + *   double-byte Unicode as the native character set, nor any other + *   character set where a null byte could appear as part of a non-null + *   character.  In particular, callers may assume that null-terminated + *   strings passed to and from the osxxx functions contain no embedded + *   null bytes.  Multi-byte character sets (i.e., character sets with + *   mixed single-byte and double-byte characters) may be used as long as + *   a null byte is never part of any multi-byte character, since this + *   would guarantee that a null byte could always be taken as a null + *   character without knowledge of the encoding or context.   + */ + +/* ------------------------------------------------------------------------ */ +/* + *   "Far" Pointers.  Most platforms can ignore this.  For platforms with + *   mixed-mode addressing models, where pointers of different sizes can + *   be used within a single program and hence some pointers require + *   qualification to indicate that they use a non-default addressing + *   model, the keyword OSFAR should be defined to the appropriate + *   compiler-specific extension keyword. + *    + *   If you don't know what I'm talking about here, you should just ignore + *   it, because your platform probably doesn't have anything this + *   sinister.  As of this writing, this applies only to MS-DOS, and then + *   only to 16-bit implementations that must interact with other 16-bit + *   programs via dynamic linking or other mechanisms.   + */ + + +/* ------------------------------------------------------------------------ */ +/* + *   ANSI C99 exact-size integer types. + *    + *   C99 defines a set of integer types with exact bit sizes, named intXX_t + *   for a signed integer with XX bits, and uintXX_t for unsigned.  XX can be + *   8, 16, 32, or 64.  TADS uses the 16- and 32-bit sizes, so each platform + *   is responsible for defining the following types: + *    + *.    int16_t   - a signed integer type storing EXACTLY 16 bits + *.    uint16_t  - an unsigned integer type storing EXACTLY 16 bits + *.    int32_t   - a signed integer type storing EXACTLY 32 bits + *.    uint32_t  - an unsigned integer type storing EXACTLY 32 bits + *    + *   Many modern compilers provide definitions for these types via the + *   standard header stdint.h.  Where stdint.h is provided, the platform code + *   can merely #include <stdint.h>. + *    + *   For compilers where stdint.h isn't available, you must provide suitable + *   typedefs.  Note that the types must be defined with the exact bit sizes + *   specified; it's not sufficient to use a bigger type, because we depend + *   in some cases on overflow and sign extension behavior at the specific + *   bit size. + */ + + +/* ------------------------------------------------------------------------ */ +/* + *   Thread-local storage (TLS). + *    + *   When TADS is compiled with threading support, it requires some variables + *   to be "thread-local".  This means that the variables have global scope + *   (so they're not stored in "auto" variables on the stack), but each + *   thread has a private copy of each such variable. + *    + *   Nearly all systems that support threads also support thread-local + *   storage.  Like threading support itself, though, TLS support is at + *   present implemented only in non-portable OS APIs rather than standard C + *   language features.  TLS is a requirement if TADS is compiled with + *   threading, but it's not needed for non-threaded builds.  TADS only + *   requires threading at present (version 3.1) for its network features; + *   since these features are optional, systems that don't have threading and + *   TLS support will simply need to disable the network features, which will + *   allow all of the threading and TLS definitions in osifc to be omitted. + *    + *   There appear to be two common styles of TLS programming models.  The + *   first provides non-standard compiler syntax for declarative creation of + *   thread-local variables.  The Microsoft (on Windows) and Gnu compilers + *   (on Linux and Unix) do this: they provide custom storage class modifiers + *   for declaring thread locals (__declspec(thread) for MSVC, __thread for + *   gcc).  Compilers that support declarative thread locals handle the + *   implementation details through code generation, so the program merely + *   needs to add the special TLS storage class qualifier to an otherwise + *   ordinary global variable declaration, and then can access the thread + *   local as though it were an ordinary global. + *    + *   The second programming model is via explicit OS API calls to create, + *   initialize, and access thread locals.  pthreads provides such an API, as + *   does Win32.  In fact, when you use the declarative syntax with MSVC or + *   gcc, the compiler generates the appropriate API calls, but the details + *   are transparent to the program; in contrast, when using pthreads + *   directly, the program must actively call the relevant APIs. + *    + *   It's probably the case that every system that has compiler-level support + *   for declarative thread local creation also has procedural APIs, so the + *   simplest way to abstract the platform differences would be to do + *   everything in terms of APIs.  However, it seems likely that compilers + *   with declarative syntax might be able to generate more efficient code, + *   since optimizers always benefit from declarative information.  So we'd + *   like to use declarative syntax whenever it's available, but fall back on + *   explicit API calls when it's not.  So our programming model is a union + *   of the two styles: + *    + *   1. For each thread local, declare the thread local: + *.      OS_DECL_TLS(char *, my_local); + *    + *   2. At main program startup (for the main thread only), initialize each + *   thread local: + *.      os_tls_create(my_local); + *    + *   3. Never get or set the value of a thread local directly; instead, use + *   the get/set functions: + *.      char *x = os_tls_get(char *, my_local); + *.      os_tls_set(my_local, "hello"); + *    + *   One key feature of this implementation is that each thread local is + *   stored as a (void *) value.  We do it this way to allow a simple direct + *   mapping to the pthreads APIs, since that's going to be the most common + *   non-declarative implementation.  This means that a thread local variable + *   can contain any pointer type, but *only* a pointer type.  The standard + *   pattern for dealing with anything more ocmplex is the same as in + *   pthreads: gather up the data into a structure, malloc() an instance of + *   that structure at entry to each thread (including the main thread), and + *   os_tls_set() the variable to contain a pointer to that structure.  From + *   then on, use os_tls_set(my_struct *, my_local)->member to access the + *   member variables in the structure.  And finally, each thread must delete + *   the structure at thread exit. + */ + +/*    + *    + *   Declare a thread local. + *    + *   - For compilers that support declarative TLS variables, the local OS + *   headers should use the compiler support by #defining OS_DECL_TLS to the + *   appropriate local declarative keyword. + *    + *   - For systems without declarative TLS support but with TLS APIs, the + *   global declared by this macro actually stores the slot ID (what pthreads + *   calls the "key") for the variable.  This macro should therefore expand + *   to a declaration of the appropriate API type for a slot ID; for example, + *   on pthreads, #define OS_DECL_TLS(t, v) pthread_key_t v. + *    + *   - For builds with no thread support, simply #define this to declare the + *   variable as an ordinary global: #define OS_DECL_TLS(t, v) t v. + */ +/* #define OS_DECL_TLS(typ, varname)  __thread typ varname */ + +/* + *   For API-based systems without declarative support in the compiler, the + *   main program startup code must explicitly create a slot for each thread- + *   local variable by calling os_tls_create().  The API returns a slot ID, + *   which is shared among threads and therefore can be stored in an ordinary + *   global variable.  OS_DECL_TLS will have declared the global variable + *   name in this case as an ordinary global of the slot ID type.  The + *   os_tls_create() macro should therefore expand to a call to the slot + *   creation API, storing the new slot ID in the global. + *    + *   Correspondingly, before the main thread exits, it should delete each + *   slot it created, b calling os_tls_delete(). + *    + *   For declarative systems, there's no action required here, so these + *   macros can be defined to empty. + */ +/* #define os_tls_create(varname)  pthread_key_create(&varname, NULL) */ +/* #define os_tls_delete(varname)  pthread_key_delete(varname) */ + + +/* + *   On API-based systems, each access to get or set the thread local + *   requires an API call, using the slot ID stored in the actual global to + *   get the per-thread instance of the variable's storage. + *.    #define os_tls_get(typ, varname) ((typ)pthread_getspecific(varname)) + *.    #define os_tls_set(varname, val) pthread_setspecific(varname, val) + *    + *   On declarative systems, the global variable itself is the thread local, + *   so get/set can be implemented as direct access to the variable. + *.    #define os_tls_get(typ, varname) varname + *.    #define os_tls_set(varname, val) varname = (val) + */ + +/* + *   Common TLS definitions - declarative thread locals + *    + *   For systems with declarative TLS support in the compiler, the OS header + *   can #define OS_DECLARATIVE_TLS to pick up suitable definitions for the + *   os_tls_xxx() macros.  The OS header must separately define OS_DECL_TLS + *   as appropriate for the local system. + */ +#ifdef OS_DECLARATIVE_TLS +#define os_tls_create(varname) +#define os_tls_delete(varname) +#define os_tls_get(typ, varname) varname +#define os_tls_set(varname, val) varname = (val) +#endif + +/* + *   Common TLS definitions - pthreads + *    + *   For pthreads systems without declarative TLS support in the compiler, + *   the OS header can simply #define OS_PTHREAD_TLS to pick up the standard + *   definitions below.  + */ +#ifdef OS_PTHREAD_TLS +#include <pthread.h> +#define OS_DECL_TLS(typ, varname) pthread_key_t varname +#define os_tls_create(varname) pthread_key_create(&varname, NULL) +#define os_tls_delete(varname) pthread_key_delete(varname) +#define os_tls_get(typ, varname) ((typ)pthread_getspecific(varname)) +#define os_tls_set(varname, val) pthread_setspecific(varname, val) +#endif + + +/* ------------------------------------------------------------------------ */ +/* + *   <time.h> definitions. + *    + *   os_time() should act like Unix time(), returning the number of seconds + *   elapsed since January 1, 1970 at midnight UTC. + *    + *   The original Unix <time.h> package defined time_t as a 32-bit signed + *   int, and many subsequent C compilers on other platforms followed suit. + *   A signed 32-bit time_t has the well-known year-2038 problem; some later + *   C compilers tried to improve matters by using an unsigned 32-bit time_t + *   instead, but for many purposes this is even worse since it can't + *   represent any date before 1/1/1970.  *Most* modern compilers solve the + *   problem once and for all (for 300 billion years in either direction of + *   1/1/1970, anyway - enough to represent literally all of eternity in most + *   current cosmological models) by defining time_t as a signed 64-bit int. + *   But some compilers stubbornly stick to the old 32-bit time_t even in + *   newer versions, for the sake of compatibility with older code that might + *   be lax about mixing time_t's with ordinary int's.  E.g., MSVC2003 does + *   this.  Fortunately, some of these compilers (such as MSVC2003 again) + *   also define a parallel, transitional set of 64-bit time functions that + *   you can use by replacing all references to the standard time_t and + *   related names with the corresponding 64-bit names. + *    + *   We'd really like to use a 64-bit time_t wherever we can - the TADS + *   release cycle can be a bit slow, and we don't want 2038 to sneak up on + *   us and catch us unawares.  So for those compilers that offer a choice of + *   32 or 64 bits, we'd like to select the 64 bit version.  To facilitate + *   this, we define covers here for the time.h types and functions that we + *   use.  On platforms where the regular time_t is already 64 bits, or where + *   there's no 64-bit option at all, you can simply do nothing - the + *   defaults defined here use the standard time_t typedef and functions, so + *   that's what you'll get if you don't define these in the OS-specific + *   headers for your platform.  For compilers that provide both a 32-bit + *   time_t and a 64-bit other_time_t, the OS headers should #define these + *   macros in terms of those compiler-specific 64-bit names. + */ +#ifndef os_time_t +# define os_time_t        TimeDate +# define os_gmtime(t)     gmtime(t) +# define os_localtime(t)  localtime(t) +# define os_time(t)       time(t) +#endif + +/* + *   Initialize the time zone.  This routine is meant to take care of any + *   work that needs to be done prior to calling localtime() and other + *   time-zone-dependent routines in the run-time library.  For DOS and + *   Windows, we need to call the run-time library routine tzset() to set up + *   the time zone from the environment; most systems shouldn't need to do + *   anything in this routine.  It's sufficient to call this once during the + *   process lifetime, since it's meant to perform static initialization that + *   lasts as long as the process is running. + */ +#ifndef os_tzset +void os_tzset(void); +#endif + +/* + *   Higher-precision time.  This retrieves the same time information as + *   os_time() (i.e., the elapsed time since the standard Unix Epoch, January + *   1, 1970 at midnight UTC), but retrieves it with the highest precision + *   available on the local system, up to nanosecond precision.  If less + *   precision is available, that's fine; just return the time to the best + *   precision available, but expressed in terms of the number of + *   nanoseconds.  For example, if you can retrieve milliseconds, you can + *   convert that to nanoseconds by multiplying by 1,000,000. + *    + *   On return, fills in '*seconds' with the number of whole seconds since + *   the Epoch, and fills in '*nanoseconds' with the fractional portion, + *   expressed in nanosceconds.  Note that '*nanoseconds' is merely the + *   fractional portion of the time, so 0 <= *nanoseconds < 1000000000. + */ +void os_time_ns(os_time_t *seconds, long *nanoseconds); + +/* + *   Get the local time zone name, as a location name in the IANA zoneinfo + *   database.  For example, locations using US Pacific Time should return + *   "America/Los_Angeles". + *    + *   Returns true if successful, false if not.  If the local operating system + *   doesn't have a way to obtain this information, or if it's not available + *   in the local machine's configuration, this returns false. + *    + *   The zoneinfo database is also known as the Olson or TZ (timezone) + *   database; it's widely used on Unix systems as the definitive source of + *   local time zone settings.  See http://www.iana.org/time-zones for more + *   information. + *    + *   On many Unix systems, the TZ environment variable contains the zoneinfo + *   zone name when its first character is ':'.  Windows uses a proprietary + *   list of time zone names that can be mapped to zoneinfo names via a + *   hand-coded list (such a list is maintained in the Unicode CLDR; our + *   Windows implementation uses the CLDR list to generate the mapping). + *   MacOS X uses zoneinfo keys directly; /etc/localtime is a link to the + *   zoneinfo file for the local zone as set via the system preferences. + *    + *   os_tzset() must be invoked at some point before this routine is called. + */ +int os_get_zoneinfo_key(char *buf, size_t buflen); + +/* + *   Get a description of the local time zone.  Fills in '*info' with the + *   available information.  Returns true on success, false on failure. + *    + *   See osstzprs.h/.c for a portable implementation of a parser for + *   POSIX-style TZ strings.  That can serve as a full implementation of this + *   function for systems that use the POSIX TZ environment variable syntax + *   to specify the timezone.  (That routine simply parses a string from any + *   source, so it can be used to parse the TZ syntax even on systems where + *   the string comes from somewhere other than the TZ environment variable.) + *    + *   os_tzset() must be invoked at some point before this routine is called. + *    + *   The following two structures are used for the return information: + *    + *   os_tzrule_t - Timezone Rule structure.  This describes a rule for an + *   annual transition between daylight savings time and standard time in a + *   time zone.  Most timezones that have recurring standard/daylight changes + *   require two of these rules, one for switching to daylight time in the + *   spring and one for switching to standard time in the fall. + *    + *   os_tzinfo_t - Timezone Information structure.  This describes a + *   timezone's clock settings, name(s), and rules for recurring annual + *   changes between standard time and daylight time, if applicable. + */ +struct os_tzrule_t +{ +    /*  +     *   Day of year, 1-365, NEVER counting Feb 29; set to 0 if not used. +     *   Corresponds to the "J" format in Unix TZ strings.  (Called "Julian +     *   day" in the POSIX docs, thus the "J", even though it's a bit of a +     *   misnomer.)(Because of the invariance of the mapping from J-number to +     *   date, this is just an obtuse way of specifying a month/day date. +     *   But even so, we'll let the OS layer relay this back to us in +     *   J-number format and count on the portable caller to work out the +     *   date, rather than foisting that work on each platform +     *   implementation.) +     */ +    int jday; + +    /* +     *   Day of year, 1-366, counting Feb 29 on leap years; set to 0 if not +     *   used; ignored if 'jday' is nonzero.  This corresponds to the Julian +     *   day sans "J" in TZ strings (almost - that TZ format uses 0-365 as +     *   its range, so bump it up by one when parsing a TZ string).  This +     *   format is even more obtuse than the J-day format, in that it doesn't +     *   even have an invariant month/day mapping (not after day 59, anyway - +     *   day 60 is either February 29 or March 1, depending on the leapness +     *   of the year, and every day after that is similarly conditional).  As +     *   far as I can tell, no one uses this option, so I'm not sure why it +     *   exists.  The zoneinfo source format doesn't have a way to represent +     *   it, which says to me that no one has ever used it in a statutory DST +     *   start/end date definition in the whole history of time zones around +     *   the world, since the whole history of time zones around the world is +     *   exactly what the zoneinfo database captures in exhaustive and +     *   painstaking detail.  If anyone had ever used it in defining a time +     *   zone, zoneinfo would have an option for it.  My guess is that it's a +     *   fossilized bug from some early C RTL that's been retained out of an +     *   abundance of caution vis-a-vis compatibility, and was entirely +     *   replaced in practice by the J-number format as soon as someone +     *   noticed the fiddly leap year behavior.  But for the sake of +     *   completeness... +     */ +    int yday; +     +    /*  +     *   The month (1-12), week of the month, and day of the week (1-7 for +     *   Sunday to Saturday).  Week 1 is the first week in which 'day' +     *   occurs, week 2 is the second, etc.; week 5 is the last occurrence of +     *   'day' in the month.  These fields are used for "second Sunday in +     *   March" types of rules.  Set these to zero if they're not used; +     *   they're ignored in any case if 'jday' or 'yday' are non-zero. +     */ +    int month; +    int week; +    int day; + +    /* time of day, in seconds after midnight (e.g., 2AM is 120 == 2*60*60) */ +    int time; +}; +struct os_tzinfo_t +{ +    /* +     *   The local offset from GMT, in seconds, for standard time and +     *   daylight time in this zone.  These values are positive for zones +     *   east of GMT and negative for zones west: New York standard time +     *   (EST) is 5 hours west of GMT, so its offset is -5*60*60. +     *    +     *   Set both of these fields (if possible) regardless of whether +     *   standard or daylight time is currently in effect in the zone.  The +     *   caller will select which offset to use based on the start/end rules, +     *   or based on the 'is_dst' flag if no rules are available. +     *    +     *   If it's only possible to determine the current wall clock offset, be +     *   it standard or daylight time, and it's not possible to determine the +     *   time difference between the two, simply set both of these to the +     *   current offset.  This information isn't available from the standard +     *   C library, and many OS APIs also lack it.   +     */ +    int32_t std_ofs; +    int32_t dst_ofs; + +    /* +     *   The abbreviations for the local zone's standard time and daylight +     *   time, respectively, when displaying date/time values.  E.g., "EST" +     *   and "EDT" for US Eastern Time.  If the zone doesn't observe daylight +     *   time (it's on standard time year round), set dst_abbr to an empty +     *   string. +     *    +     *   As with std_ofs and dst_ofs, you can set both of these to the same +     *   string if it's only possible to determine the one that's currently +     *   in effect. +     */ +    char std_abbr[16]; +    char dst_abbr[16]; + +    /* +     *   The ongoing rules for switching between daylight and standard time +     *   in this zone, if available.  'dst_start' is the date when daylight +     *   savings starts, 'dst_end' is the date when standard time resumes. +     *   Set all fields to 0 if the start/stop dates aren't available, or the +     *   zone is on standard time year round. +     */ +    struct os_tzrule_t dst_start; +    struct os_tzrule_t dst_end; + +    /*  +     *   True -> the zone is CURRENTLY on daylight savings time; false means +     *   it's currently on standard time. +     *    +     *   This is only used if the start/end rules aren't specified.  In the +     *   absence of start/end rules, there's no way to know when the current +     *   standard/daylight phase ends, so we'll have to assume that the +     *   current mode is in effect permanently.  In this case, the caller +     *   will use only be able to use the offset and abbreviation for the +     *   current mode and will have to ignore the other one. +     */ +    int is_dst;  }; +int os_get_timezone_info(struct os_tzinfo_t *info); + + +/* + *   Get the current system high-precision timer.  This function returns a + *   value giving the wall-clock ("real") time in milliseconds, relative to + *   any arbitrary zero point.  It doesn't matter what this value is relative + *   to -- the only important thing is that the values returned by two + *   different calls should differ by the number of actual milliseconds that + *   have elapsed between the two calls.  This might be the number of + *   milliseconds since the computer was booted, since the current user + *   logged in, since midnight of the previous night, since the program + *   started running, since 1-1-1970, etc - it doesn't matter what the epoch + *   is, so the implementation can use whatever's convenient on the local + *   system. + *    + *   True millisecond precision isn't required.  Each implementation should + *   simply use the best precision available on the system.  If your system + *   doesn't have any kind of high-precision clock, you can simply use the + *   time() function and multiply the result by 1000 (but see the note below + *   about exceeding 32-bit precision). + *    + *   However, it *is* required that the return value be in *units* of + *   milliseconds, even if your system clock doesn't have that much + *   precision; so on a system that uses its own internal clock units, this + *   routine must multiply the clock units by the appropriate factor to yield + *   milliseconds for the return value. + *    + *   It is also required that the values returned by this function be + *   monotonically increasing.  In other words, each subsequent call must + *   return a value that is equal to or greater than the value returned from + *   the last call.  On some systems, you must be careful of two special + *   situations. + *    + *   First, the system clock may "roll over" to zero at some point; for + *   example, on some systems, the internal clock is reset to zero at + *   midnight every night.  If this happens, you should make sure that you + *   apply a bias after a roll-over to make sure that the value returned from + *   this return continues to increase despite the reset of the system clock. + *    + *   Second, a 32-bit signed number can only hold about twenty-three days + *   worth of milliseconds.  While it seems unlikely that a TADS game would + *   run for 23 days without a break, it's certainly reasonable to expect + *   that the computer itself may run this long without being rebooted.  So, + *   if your system uses some large type (a 64-bit number, for example) for + *   its high-precision timer, you may want to store a zero point the very + *   first time this function is called, and then always subtract this zero + *   point from the large value returned by the system clock.  If you're + *   using time(0)*1000, you should use this technique, since the result of + *   time(0)*1000 will almost certainly not fit in 32 bits in most cases.   + */ +long os_get_sys_clock_ms(void); + + +/* ------------------------------------------------------------------------ */ +/* + *   Hardware Configuration.  Define the following functions appropriately + *   for your hardware.  For efficiency, these functions should be defined + *   as macros if possible. + *    + *   Note that these hardware definitions are independent of the OS, at + *   least to the extent that your OS can run on multiple types of + *   hardware.  So, rather than combining these definitions into your + *   osxxx.h header file, we recommend that you put these definitions in a + *   separate h_yyy.h header file, which can be configured into os.h with + *   an appropriate "_M_yyy" preprocessor symbol.  Refer to os.h for + *   details of configuring the hardware include file.   + */ + +/*  + *   Round a size up to worst-case alignment boundary.  For example, on a + *   platform where the largest type must be aligned on a 4-byte boundary, + *   this should round the value up to the next higher mutliple of 4 and + *   return the result.   + */ +/* size_t osrndsz(size_t siz); */ + +/*  + *   Round a pointer up to worst-case alignment boundary.  + */ +/* void *osrndpt(void *ptr); */ + +/*  + *   Read an unaligned portable unsigned 2-byte value, returning an int + *   value.  The portable representation has the least significant byte + *   first, so the value 0x1234 is represented as the byte 0x34, followed + *   by the byte 0x12. + *    + *   The source value must be treated as unsigned, but the result is + *   signed.  This is significant on 32- and 64-bit platforms, because it + *   means that the source value should never be sign-extended to 32-bits. + *   For example, if the source value is 0xffff, the result is 65535, not + *   -1.   + */ +/* int osrp2(unsigned char *p); */ + +/*  + *   Read an unaligned portable signed 2-byte value, returning int.  This + *   differs from osrp2() in that this function treats the source value as + *   signed, and returns a signed result; hence, on 32- and 64-bit + *   platforms, the result must be sign-extended to the int size.  For + *   example, if the source value is 0xffff, the result is -1.   + */ +/* int osrp2s(unsigned char *p); */ + +/*  + *   Write unsigned int to unaligned portable 2-byte value.  The portable + *   representation stores the low-order byte first in memory, so + *   oswp2(0x1234) should result in storing a byte value 0x34 in the first + *   byte, and 0x12 in the second byte.   + */ +/* void oswp2(unsigned char *p, unsigned int i); */ + +/* + *   Write signed int to unaligned portable 2-byte value.  Negative values + *   must be stored in two's complement notation.  E.g., -1 is stored as + *   FF.FF, -32768 is stored as 00.80 (little-endian).   + *    + *   Virtually all modern hardware uses two's complement notation as the + *   native representation, which makes this routine a trivial synonym of + *   osrp2() (i.e., #define oswp2s(p,i) oswp2(p,i)).  We distinguish the + *   signed version on the extremely off chance that TADS is ever ported to + *   wacky hardware with a different representation for negative integers + *   (one's complement, sign bit, etc).   + */ +/* void oswp2s(unsigned char *p, int i); */ + +/*  + *   Read an unaligned unsigned portable 4-byte value, returning long.  The + *   underlying value should be considered signed, and the result is signed. + *   The portable representation stores the bytes starting with the least + *   significant: the value 0x12345678 is stored with 0x78 in the first byte, + *   0x56 in the second byte, 0x34 in the third byte, and 0x12 in the fourth + *   byte. + */ +/* unsigned long osrp4(unsigned char *p); */ + +/* + *   Read an unaligned signed portable 4-byte value, returning long.  + */ +/* long osrp4s(unsigned char *p); */ + +/*  + *   Write an unsigned long to an unaligned portable 4-byte value.  The + *   portable representation stores the low-order byte first in memory, so + *   0x12345678 is written to memory as 0x78, 0x56, 0x34, 0x12.   + */ +/* void oswp4(unsigned char *p, unsigned long l); */ + +/* + *   Write a signed long, using little-endian byte order and two's complement + *   notation for negative numbers.  This is a trivial synonym for oswp4() + *   for all platforms with native two's complement arithmetic (which is + *   virtually all modern platforms).  See oswp2s() for more discussion. + */ +/* void oswp4s(unsigned char *p, long l); */ + +/* + *   For convenience and readability, the 1-byte integer (signed and + *   unsigned) equivalents of the above. + */ +#define osrp1(p) (*(unsigned char *)(p)) +#define osrp1s(p) (*(signed char *)(p)) +#define oswp1(p, b) (*(unsigned char *)(p) = (b)) +#define oswp1s(p, b) (*(signed char *)(p) = (b)) + + +/* ------------------------------------------------------------------------ */ +/* + *   varargs va_copy() extension. + *    + *   On some compilers, va_list is a reference type.  This means that if a + *   va_list value is passed to a function that uses va_arg() to step through + *   the referenced arguments, the caller's copy of the va_list might be + *   updated on return.  This is problematic in cases where the caller needs + *   to use the va_list again in another function call, since the va_list is + *   no longer pointing to the first argument for the second call.  C99 has a + *   solution in the form of the va_copy() macro.  Unfortunately, this isn't + *   typically available in pre-C99 compilers, and isn't standard in *any* + *   C++ version.  We thus virtualize it here in a macro. + *    + *   os_va_copy() has identical semantics to C99 va_copy().  A matching call + *   to os_va_copy_end() must be made for each call to os_va_copy() before + *   the calling function returns; this has identical semantics to C99 + *   va_end(). + *    + *   Because our semantics are identical to the C99 version, we provide a + *   default definition here for compilers that define va_copy().  Platform + *   headers must provide suitable definitions only if their compilers don't + *   have va_copy().  We also provide a definition for GCC compilers that + *   define the private __va_copy macro, which also has the same semantics. + */ +#ifdef va_copy +# define os_va_copy(dst, src) va_copy(dst, src) +# define os_va_copy_end(dst)  va_end(dst) +#else +# if defined(__GNUC__) && defined(__va_copy) +#  define os_va_copy(dst, src) __va_copy(dst, src) +#  define os_va_copy_end(dst)  va_end(dst) +# endif +#endif + + + +/* ------------------------------------------------------------------------ */ +/* + *   Platform Identifiers.  You must define the following macros in your + *   osxxx.h header file: + *    + *   OS_SYSTEM_NAME - a string giving the system identifier.  This string + *   must contain only characters that are valid in a TADS identifier: + *   letters, numbers, and underscores; and must start with a letter or + *   underscore.  For example, on MS-DOS, this string is "MSDOS". + *    + *   OS_SYSTEM_LDESC - a string giving the system descriptive name.  This + *   is used in messages displayed to the user.  For example, on MS-DOS, + *   this string is "MS-DOS".   + */ + + +/* ------------------------------------------------------------------------ */ +/* + *   Message Linking Configuration.  You should #define ERR_LINK_MESSAGES + *   in your osxxx.h header file if you want error messages linked into + *   the application.  Leave this symbol undefined if you want an external + *   message file.  + */ + + +/* ------------------------------------------------------------------------ */ +/* + *   Program Exit Codes.  These values are used for the argument to exit() + *   to conform to local conventions.  Define the following values in your + *   OS-specific header: + *    + *   OSEXSUCC - successful completion.  Usually defined to 0. + *.  OSEXFAIL - failure.  Usually defined to 1.   + */ + + +/* ------------------------------------------------------------------------ */ +/* + *   Basic memory management interface.  These functions are merely + *   documented here, but no prototypes are defined, because most + *   platforms #define macros for these functions and types, mapping them + *   to malloc or other system interfaces.   + */ + +/* + *   Theoretical maximum osmalloc() size.  This may be less than the + *   capacity of the argument to osmalloc() on some systems.  For example, + *   on segmented architectures (such as 16-bit x86), memory is divided into + *   segments, so a single memory allocation can allocate only a subset of + *   the total addressable memory in the system.  This value thus specifies + *   the maximum amount of memory that can be allocated in one chunk. + *    + *   Note that this is an architectural maximum for the hardware and + *   operating system.  It doesn't have anything to do with the total amount + *   of memory actually available at run-time. + *    + *   #define OSMALMAX to a constant long value with theoretical maximum + *   osmalloc() argument value.  For a platform with a flat (unsegmented) + *   32-bit memory space, this is usually 0xffffffff; for 16-bit platforms, + *   this is usually 0xffff.   + */ +/* #define OSMALMAX 0xffffffff */ + +/*    + *   Allocate a block of memory of the given size in bytes.  The actual + *   allocation may be larger, but may be no smaller.  The block returned + *   should be worst-case aligned (i.e., suitably aligned for any type). + *   Return null if the given amount of memory is not available.   + */ +/* void *osmalloc(size_t siz); */ + +/* + *   Free memory previously allocated with osmalloc().   + */ +/* void osfree(void *block); */ + +/*  + *   Reallocate memory previously allocated with osmalloc() or + *   osrealloc(), changing the block's size to the given number of bytes. + *   If necessary, a new block at a different address can be allocated, in + *   which case the data from the original block is copied (the lesser of + *   the old block size and the new size is copied) to the new block, and + *   the original block is freed.  If the new size is less than the old + *   size, this need not do anything at all, since the returned block can + *   be larger than the new requested size.  If the block cannot be + *   enlarged to the requested size, return null.   + */ +/* void *osrealloc(void *block, size_t siz); */ + + +/* ------------------------------------------------------------------------ */ +/* + *   Basic file I/O interface.  These functions are merely documented here, + *   but no prototypes are defined, because most platforms #define macros for + *   these functions and types, mapping them to stdio or other system I/O + *   interfaces.   + *    + *   When writing a file, writes might or might not be buffered in + *   application memory; this is up to the OS implementation, which can + *   perform buffering according to local conventions and what's most + *   efficient.  However, it shouldn't make any difference to the caller + *   whether writes are buffered or not - the OS implementation must take + *   care that any buffering is invisible to the app.  (Porters: note that + *   the basic C stdio package has the proper behavior here, so you'll get + *   the correct semantics if you use a simple stdio implementation.) + *    + *   Write buffering might be visible to *other* apps, though.  In + *   particular, another process might not see data written to a file (with + *   osfwb(), os_fprint(), etc) immediately, since the write functions might + *   hold the written bytes in an internal memory buffer rather than sending + *   them to the OS.  Any internal buffers are guaranteed to be flushed to + *   the OS upon calling osfcls() or osfflush().  Note that it's never + *   *necessary* to call osfflush(), because buffered data will always be + *   flushed on closing the file with osfcls().  However, if you want other + *   apps to be able to see updates immediately, you can use osfflush() to + *   ensure that buffers are flushed to a file before you close it. + *    + *   You can also use osfflush() to check for buffered write errors.  When + *   you use osfwb() or other write functions to write data, they will return + *   a success indication even if the data was only copied into a buffer. + *   This means that a write that appeared to succeed might actually fail + *   later, when the buffer is flushed.  The only way to know for sure is to + *   explicitly flush buffers using osfflush(), and check the result code. + *   If the original write function and a subsequent osfflush() *both* return + *   success indications, then the write has definitely succeeded.   + */ + + +/* + *   Define the following values in your OS header to indicate local + *   file/path syntax conventions: + *    + *   OSFNMAX - integer indicating maximum length of a filename + *    + *   OSPATHCHAR - character giving the normal path separator character + *.  OSPATHALT - string giving other path separator characters + *.  OSPATHURL - string giving path separator characters for URL conversions + *.  OSPATHSEP - directory separator for PATH-style environment variables + *.  OSPATHPWD - string giving the special path representing the current + *.              working directory; for Unix or Windows, this is "." + *    + *   OSPATHURL is a little different: this specifies the characters that + *   should be converted to URL-style separators when converting a path from + *   local notation to URL notation.  This is usually the same as the union + *   of OSPATHCHAR and OSPATHALT, but need not be; for example, on DOS, the + *   colon (':') is a path separator for most purposes, but is NOT a path + *   character for URL conversions. + */ + +/* + *   Define the type osfildef as the appropriate file handle structure for + *   your osfxxx functions.  This type is always used as a pointer, but + *   the value is always obtained from an osfopxxx call, and is never + *   synthesized by portable code, so you can use essentially any type + *   here that you want. + *    + *   For platforms that use C stdio functions to implement the osfxxx + *   functions, osfildef can simply be defined as FILE. + */ +/* typedef FILE osfildef; */ + + +/* + *   File types. + *    + *   These are symbols of the form OSFTxxxx defining various content types, + *   somewhat aking to MIME types.  These were mainly designed for the old + *   Mac OS (versions up to OS 9), where the file system stored a type tag + *   with each file's metadata.  The type tags were used for things like + *   filtering file selector dialogs and setting file-to-app associations in + *   the desktop shell. + *    + *   Our OSFTxxx symbols are abstract file types that we define, for types + *   used within the TADS family of applications.  They give us a common, + *   cross-platform reference point for each type we use.  Each port where + *   file types are meaningful then maps our abstract type IDs to the + *   corresponding port-specific type IDs.  In practice, this has never been + *   used anywhere other than the old Mac OS ports; in fact, it's not even + *   used in the modern Mac OS (OS X and later), since Apple decided to stop + *   fighting the tide and start using filename suffixes for this sort of + *   tagging, like everyone else always has. + *    + *   For the list of file types, see osifctyp.h  + */ + + +/* + *   Local newline convention. + *    + *   Because of the pernicious NIH ("Not Invented Here") cultures of the + *   major technology vendors, basically every platform out there has its own + *   unique way of expressing newlines in text files.  Unix uses LF (ASCII + *   10); Mac uses CR (ASCII 13); DOS and Windows use CR-LF pairs.  In the + *   past there were heaven-only-knows how many other conventions in use, but + *   fortunately these three have the market pretty well locked up at this + *   point.  But we do still have to worry about these three. + *    + *   Our strategy on input is to be open to just about anything whenever + *   possible.  So, when we're reading something that we believe to be a text + *   file, we'll treat all of these as line endings: CR, LF, CR-LF, and + *   LF-CR.  It's pretty safe to do this; if we have a CR and LF occurring + *   adjacently, it's almost certain that they're intended to be taken + *   together as a single newline sequence.  Likewise, if there's a lone CR + *   or LF, it's rare for it to mean anything other than a newline. + *    + *   On output, though, we can't be as loose.  The problem is that other + *   applications on our big three platforms *don't* tend to aim for the same + *   flexibility we do on input: other apps usually expect exactly the local + *   conventions on input, and don't always work well if they don't get it. + *   So it's important that when we're writing a text file, we write newlines + *   in the local convention.  This means that we sometimes need to know what + *   the local convention actually is.  That's where this definition comes + *   in. + *    + *   Each port must define OS_NEWLINE_SEQ as an ASCII string giving the local + *   newline sequence to write on output.  For example, DOS defines it as + *   "\r\n" (CR-LF).  Always define it as a STRING (not a character + *   constant), even if it's only one character long. + *    + *   (Note that some compilers use wacky mappings for \r and \n.  Some older + *   Mac compilers, for example, defined \n as CR and \r as LF, because of + *   the Mac convention where newline is represented as CR in a text file. + *   If there's any such variability on your platform, you can always use the + *   octal codes to be unambiguous: \012 for LF and \015 for CR.)   + */ +/* #define OS_NEWLINE_SEQ  "\r\n" */ + + + +/*  + *   Open text file for reading.  This opens the file with read-only access; + *   we're not allowed to write to the file using this handle.  Returns NULL + *   on error. + *    + *   A text file differs from a binary file in that some systems perform + *   translations to map between C conventions and local file system + *   conventions; for example, on DOS, the stdio library maps the DOS CR-LF + *   newline convention to the C-style '\n' newline format.  On many systems + *   (Unix, for example), there is no distinction between text and binary + *   files. + *    + *   On systems that support file sharing and locking, this should open the + *   file in "shared read" mode - this means that other processes are allowed + *   to simultaneously read from the file, but no other processs should be + *   allowed to write to the file as long as we have it open.  If another + *   process already has the file open with write access, this routine should + *   return failure, since we can't take away the write privileges the other + *   process already has and thus we can't guarantee that other processes + *   won't write to the file while we have it open.   + */ +/* osfildef *osfoprt(const char *fname, os_filetype_t typ); */ + +/* + *   Open a text file for "volatile" reading: we open the file with read-only + *   access, and we explicitly accept instability in the file's contents due + *   to other processes simultaneously writing to the file.  On systems that + *   support file sharing and locking, the file should be opened in "deny + *   none" mode, meaning that other processes can simultaneously open the + *   file for reading and/or writing even while have the file open.   + */ +/* osfildef *osfoprtv(const char *fname, os_filetype_t typ); */ + +/*  + *   Open text file for writing; returns NULL on error.  If the file already + *   exists, this truncates the file to zero length, deleting any existing + *   contents. + */ +/* osfildef *osfopwt(const char *fname, os_filetype_t typ); */ + +/* + *   Open text file for reading and writing, keeping the file's existing + *   contents if the file already exists or creating a new file if no such + *   file exists.  Returns NULL on error.  + */ +/* osfildef *osfoprwt(const char *fname, os_filetype_t typ); */ + +/*  + *   Open text file for reading/writing.  If the file already exists, + *   truncate the existing contents to zero length.  Create a new file if it + *   doesn't already exist.  Return null on error.   + */ +/* osfildef *osfoprwtt(const char *fname, os_filetype_t typ); */ + +/*  + *   Open binary file for writing; returns NULL on error.  If the file + *   exists, this truncates the existing contents to zero length. + */ +/* osfildef *osfopwb(const char *fname, os_filetype_t typ); */ + +/*  + *   Open source file for reading - use the appropriate text or binary + *   mode.   + */ +/* osfildef *osfoprs(const char *fname, os_filetype_t typ); */ + +/*  + *   Open binary file for reading; returns NULL on error.   + */ +/* osfildef *osfoprb(const char *fname, os_filetype_t typ); */ + +/* + *   Open binary file for 'volatile' reading; returns NULL on error. + *   ("Volatile" means that we'll accept writes from other processes while + *   reading, so the file should be opened in "deny none" mode or the + *   equivalent, to the extent that the local system supports file sharing + *   modes.)   + */ +/* osfildef *osfoprbv(const char *fname, os_filetype_t typ); */ + +/*  + *   Open binary file for random-access reading/writing.  If the file already + *   exists, keep the existing contents; if the file doesn't already exist, + *   create a new empty file. + *    + *   The caller is allowed to perform any mixture of read and write + *   operations on the returned file handle, and can seek around in the file + *   to read and write at random locations. + *    + *   If the local file system supports file sharing or locking controls, this + *   should generally open the file in something equivalent to "exclusive + *   write, shared read" mode ("deny write" in DENY terms), so that other + *   processes can't modify the file at the same time we're modifying it (but + *   it doesn't bother us to have other processes reading from the file while + *   we're working on it, as long as they don't mind that we could change + *   things on the fly).  It's not absolutely necessary to assert these + *   locking semantics, but if there's an option to do so this is preferred. + *   Stricter semantics (such as "exclusive" or "deny all" mode) are better + *   than less strict semantics.  Less strict semantics are dicey, because in + *   that case the caller has no way of knowing that another process could be + *   modifying the file at the same time, and no way (through osifc) of + *   coordinating that activity.  If less strict semantics are implemented, + *   the caller will basically be relying on luck to avoid corruptions due to + *   writing by other processes. + *    + *   Return null on error.   + */ +/* osfildef *osfoprwb(const char *fname, os_filetype_t typ); */ + +/*  + *   Open binary file for random-access reading/writing.  If the file already + *   exists, truncate the existing contents (i.e., delete the contents of the + *   file, resetting it to a zero-length file).  Create a new file if it + *   doesn't already exist.  The caller is allowed to perform any mixture of + *   read and write operations on the returned handle, and can seek around in + *   the file to read and write at random locations. + *    + *   The same comments regarding sharing/locking modes for osfoprwb() apply + *   here as well. + *    + *   Return null on error.   + */ +/* osfildef *osfoprwtb(const char *fname, os_filetype_t typ); */ + +/* + *   Duplicate a file handle.  Returns a new osfildef* handle that accesses + *   the same open file as an existing osfildef* handle.  The new handle is + *   independent of the original handle, with its own seek position, + *   buffering, etc.  The new handle and the original handle must each be + *   closed separately when the caller is done with them (closing one doesn't + *   close the other).  The effect should be roughly the same as the Unix + *   dup() function. + *    + *   On success, returns a new, non-null osfildef* handle duplicating the + *   original handle.  Returns null on failure. + *    + *   'mode' is a simplified stdio fopen() mode string.  The first + *   character(s) indicate the access type: "r" for read access, "w" for + *   write access, or "r+" for read/write access.  Note that "w+" mode is + *   specifically not defined, since the fopen() handling of "w+" is to + *   truncate any existing file contents, which is not desirable when + *   duplicating a handle.  The access type can optionally be followed by "t" + *   for text mode, "s" for source file mode, or "b" for binary mode, with + *   the same meanings as for the various osfop*() functions.  The default is + *   't' for text mode if none of these are specified. + *    + *   If the osfop*() functions are implemented in terms of stdio FILE* + *   objects, this can be implemented as fdopen(dup(fileno(orig)), mode), or + *   using equivalents if the local stdio library uses different names for + *   these functions.  Note that "s" (source file format) isn't a stdio mode, + *   so implementations must translate it to the appropriate "t" or "b" mode. + *   (For that matter, "t" and "b" modes aren't universally supported either, + *   so some implementations may have to translate these, or more likely + *   simply remove them, as most platforms don't distinguish text and binary + *   modes anyway.) + */ +osfildef *osfdup(osfildef *orig, const char *mode); + +/*  + *   Set a file's type information.  This is primarily for implementations on + *   Mac OS 9 and earlier, where the file system keeps file-type metadata + *   separate from the filename.  On such systems, this can be used to set + *   the type metadata after a file is created.  The system should map the + *   os_filetype_t values to the actual metadata values on the local system. + *   On most systems, there's no such thing as file-type metadata, in which + *   case this function should simply be stubbed out with an empty function. + */ +void os_settype(const char *f, os_filetype_t typ); + +/* open the error message file for reading */ +osfildef *oserrop(const char *arg0); + +/*  + *   Get a line of text from a text file.  Uses fgets semantics.   + */ +/* char *osfgets(char *buf, size_t len, osfildef *fp); */ + +/*  + *   Write a line of text to a text file.  Uses fputs semantics.   + */ +/* void osfputs(const char *buf, osfildef *fp); */ + +/* + *   Write to a text file.  os_fprintz() takes a null-terminated string, + *   while os_fprint() takes an explicit separate length argument that might + *   not end with a null terminator.   + */ +void os_fprintz(osfildef *fp, const char *str); +void os_fprint(osfildef *fp, const char *str, size_t len); + +/*  + *   Write bytes to file.  Return 0 on success, non-zero on error. + */ +/* int osfwb(osfildef *fp, const void *buf, int bufl); */ + +/* + *   Flush buffered writes to a file.  This ensures that any bytes written to + *   the file (with osfwb(), os_fprint(), etc) are actually sent out to the + *   operating system, rather than being buffered in application memory for + *   later writing. + *    + *   Note that this routine only guarantees that we write through to the + *   operating system.  This does *not* guarantee that the data will actually + *   be committed to the underlying physical storage device.  Such a + *   guarantee is hard to come by in general, since most modern systems use + *   multiple levels of software and hardware buffering - the OS might buffer + *   some data in system memory, and the physical disk drive might itself + *   buffer data in its own internal cache.  This routine thus isn't good + *   enough, for example, to protect transactional data that needs to survive + *   a power failure or a serious system crash.  What this routine *does* + *   ensure is that buffered data are written through to the OS; in + *   particular, this ensures that another process that's reading from the + *   same file will see all updates we've made up to this point. + *    + *   Returns 0 on success, non-zero on error.  Errors can occur for any + *   reason that they'd occur on an ordinary write - a full disk, a hardware + *   failure, etc.   + */ +/* int osfflush(osfildef *fp); */ + +/*  + *   Read a character from a file.  Provides the same semantics as fgetc(). + */ +/* int osfgetc(osfildef *fp); */ + +/*  + *   Read bytes from file.  Return 0 on success, non-zero on error.   + */ +/* int osfrb(osfildef *fp, void *buf, int bufl); */ + +/*  + *   Read bytes from file and return the number of bytes read.  0 + *   indicates that no bytes could be read.  + */ +/* size_t osfrbc(osfildef *fp, void *buf, size_t bufl); */ + +/*  + *   Get the current seek location in the file.  The first byte of the + *   file has seek position 0.   + */ +/* long osfpos(osfildef *fp); */ + +/*  + *   Seek to a location in the file.  The first byte of the file has seek + *   position 0.  Returns zero on success, non-zero on error. + *    + *   The following constants must be defined in your OS-specific header; + *   these values are used for the "mode" parameter to indicate where to + *   seek in the file: + *    + *   OSFSK_SET - set position relative to the start of the file + *.  OSFSK_CUR - set position relative to the current file position + *.  OSFSK_END - set position relative to the end of the file  + */ +/* int osfseek(osfildef *fp, long pos, int mode); */ + +/*  + *   Close a file. + *    + *   If the OS implementation uses buffered writes, this routine guarantees + *   that any buffered data are flushed to the underlying file.  So, it's not + *   necessary to call osfflush() before calling this routine.  However, + *   since this function doesn't return any error indication, a caller could + *   use osfflush() first to check for errors on any final buffered writes.   + */ +/* void osfcls(osfildef *fp); */ + +/*  + *   Delete a file.  Returns zero on success, non-zero on error.  + */ +/* int osfdel(const char *fname); */ + +/* + *   Rename/move a file.  This should apply the usual C rename() behavior. + *   Renames the old file to the new name, which may be in a new directory + *   location if supported on the local system; moves across devices, + *   volumes, file systems, etc may or may not be supported according to the + *   local system's rules.  If the new file already exists, results are + *   undefined.  Returns true on success, false on failure. + */ +/* int os_rename_file(const char *oldname, const char *newname); */ + + +/* ------------------------------------------------------------------------ */ +/* + *   File "stat()" information - mode, size, time stamps  + */ + +/*  + *   Test access to a file - i.e., determine if the file exists.  Returns + *   zero if the file exists, non-zero if not.  (The semantics may seem + *   backwards, but this is consistent with the conventions used by most of + *   the other osfxxx calls: zero indicates success, non-zero indicates an + *   error.  If the file exists, "accessing" it was successful, so osfacc + *   returns zero; if the file doesn't exist, accessing it gets an error, + *   hence a non-zero return code.)   + */ +/* int osfacc(const char *fname) */ + +/* + *   Get a file's mode and attribute flags.  This retrieves information on + *   the given file equivalent to the st_mode member of the 'struct stat' + *   data returned by the Unix stat() family of functions, as well as some + *   extra system-specific attributes.  On success, fills in *mode (if mode + *   is non-null) with the mode information as a bitwise combination of + *   OSFMODE_xxx values, fills in *attr (if attr is non-null) with a + *   combination of OSFATTR_xxx attribute flags, and returns true; on + *   failure, simply returns false.  Failure can occur if the file doesn't + *   exist, can't be accessed due to permissions, etc. + *    + *   Note that 'mode' and/or 'attr' can be null if the caller doesn't need + *   that information.  Implementations must check these parameters for null + *   pointers and skip returning the corresponding information if null. + *    + *   If the file in 'fname' is a symbolic link, the behavior depends upon + *   'follow_links'.  If 'follow_links' is true, the function should resolve + *   the link reference (and if that points to another link, the function + *   resolves that link as well, and so on) and return information on the + *   object the link points to.  Otherwise, the function returns information + *   on the link itself.  This only applies for symbolic links (not for hard + *   links), and only if the underlying OS and file system support this + *   distinction; if the OS transparently resolves links and doesn't allow + *   retrieving information about the link itself, 'follow_links' can be + *   ignored.  Likewise, hard links (on systems that support them) are + *   generally indistinguishable from regular files, so this function isn't + *   expected to do anything special with them. + *    + *   The '*mode' value returned is a bitwise combination of OSFMODE_xxx flag. + *   Many of the flags are mutually exclusive; for example, "file" and + *   "directory" should never be combined.  It's also possible for '*mode' to + *   be zero for a valid file; this means that the file is of some special + *   type on the local system that doesn't fit any of the OSFMODE_xxx types. + *   (If any ports do encounter such cases, we can add OSFMODE_xxx types to + *   accommodate new types.  The list below isn't meant to be final; it's + *   just what we've encountered so far on the platforms where TADS has + *   already been ported.) + *    + *   The OSFMODE_xxx values are left for the OS to define so that they can be + *   mapped directly to the OS API's equivalent constants, if desired.  This + *   makes the routine easy to write, since you can simply set *mode directly + *   to the mode information the OS returns from its stat() or equivalent. + *   However, note that these MUST be defined as bit flags - that is, each + *   value must be exactly a power of 2.  Windows and Unix-like systems + *   follow this practice, as do most "stat()" functions in C run-time + *   libraries, so this usually works automatically if you map these + *   constants to OS or C library values.  However, if a port defines its own + *   values for these, take care that they're all powers of 2. + *    + *   Obviously, a given OS might not have all of the file types listed here. + *   If any OSFMODE_xxx values aren't applicable on the local OS, you can + *   simply define them as zero since they'll never be returned. + *    + *   Notes on attribute flags: + *    + *   OSFATTR_HIDDEN means that the file is conventionally hidden by default + *   in user interface views or listings, but is still fully accessible to + *   the user.  Hidden files are also usually excluded by default from + *   wildcard patterns in commands ("rm *.*").  On Unix, a hidden file is one + *   whose name starts with "."; on Windows, it's a file with the HIDDEN bit + *   in its file attributes.  On systems where this concept exists, the user + *   can still manipulate these files as normal by naming them explicitly, + *   and can typically make them appear in UI views or directory listings via + *   a preference setting or command flag (e.g., "ls -a" on Unix).  The + *   "hidden" flag is explicitly NOT a security or permissions mechanism, and + *   it doesn't protect the file against intentional access by a user; it's + *   merely a convenience designed to reduce clutter by excluding files + *   maintained by the OS or by an application (such as preference files, + *   indices, caches, etc) from casual folder browsing, where a user is + *   typically only concerned with her own document files.  On systems where + *   there's no such naming convention or attribute metadata, this flag will + *   never appear. + *    + *   OSFATTR_SYSTEM is similar to 'hidden', but means that the file is + *   specially marked as an operating system file.  This is mostly a + *   DOS/Windows concept, where it corresponds to the SYSTEM bit in the file + *   attributes; this flag will probably never appear on other systems.  The + *   distinction between 'system' and 'hidden' is somewhat murky even on + *   Windows; most 'system' file are also marked as 'hidden', and in + *   practical terms in the user interface, 'system' files are treated the + *   same as 'hidden'. + *    + *   OSFATTR_READ means that the file is readable by this process. + *    + *   OSFATTR_WRITE means that the file is writable by this process. + */ +/* int osfmode(const char *fname, int follow_links, */ +/*             unsigned long *mode, unsigned long *attr); */ + +/* file mode/type constants */ +/* #define OSFMODE_FILE    - regular file */ +/* #define OSFMODE_DIR     - directory */ +/* #define OSFMODE_BLK     - block-mode device */ +/* #define OSFMODE_CHAR    - character-mode device */ +/* #define OSFMODE_PIPE    - pipe/FIFO/other character-oriented IPC */ +/* #define OSFMODE_SOCKET  - network socket */ +/* #define OSFMODE_LINK    - symbolic link */ + +/* file attribute constants */ +/* #define OSFATTR_HIDDEN  - hidden file */ +/* #define OSFATTR_SYSTEM  - system file */ +/* #define OSFATTR_READ    - the file is readable by this process */ +/* #define OSFATTR_WRITE   - the file is writable by this process */ + +struct os_file_stat_t { +    /*  +     *   Size of the file, in bytes.  For platforms lacking 64-bit types, we +     *   split this into high and low 32-bit portions.  Platforms where the +     *   native stat() or equivalent only returns a 32-bit file size can +     *   simply set sizehi to zero, since sizelo can hold the entire size +     *   value. +     */ +    uint32_t sizelo; +    uint32_t sizehi; + +    /*  +     *   Creation time, modification time, and last access time.  If the file +     *   system doesn't keep information on one or more of these, use +     *   (os_time_t)0 to indicate that the timestamp isn't available.  It's +     *   fine to return any subset of these.  Per the standard C stat(), +     *   these should be expressed as seconds after the Unix Epoch. +     */ +    os_time_t cre_time; +    os_time_t mod_time; +    os_time_t acc_time; + +    /* file mode, using the same flags as returned from osfmode() */ +    unsigned long mode; + +    /* file attributes, using the same flags as returned from osfmode() */ +    unsigned long attrs; +}; + + +/* + *   Get stat() information.  This fills in the portable os_file_stat + *   structure with the requested file information.  Returns true on success, + *   false on failure (file not found, permissions error, etc). + *    + *   'follow_links' has the same meaning as for osfmode(). + */ +int os_file_stat(const char *fname, int follow_links, os_file_stat_t *s); + +/* + *   Manually resolve a symbolic link.  If the local OS and file system + *   support symbolic links, and the given filename is a symbolic link (in + *   which case osfmode(fname, FALSE, &m, &a) will set OSFMODE_LINK in the + *   mode bits), this fills in 'target' with the name of the link target + *   (i.e., the object that the link in 'fname' points to).  This should + *   return a fully qualified file system path.  Returns true on success, + *   false on failure. + *    + *   This should only resolve a single level of indirection.  If the link + *   target of 'fname' is itself a link to a second target, this should only + *   resolve the single reference from 'fname' to its direct direct.  Callers + *   that wish to resolve the final target of a chain of link references must + *   iterate until the returned path doesn't refer to a link. + */ +int os_resolve_symlink(const char *fname, char *target, size_t target_size); + + +/* ------------------------------------------------------------------------ */ +/* + *   Get a list of root directories.  If 'buf' is non-null, fills in 'buf' + *   with a list of strings giving the root directories for the local, + *   file-oriented devices on the system.  The strings are each null + *   terminated and are arranged consecutively in the buffer, with an extra + *   null terminator after the last string to mark the end of the list. + *    + *   The return value is the length of the buffer required to hold the + *   results.  If the caller's buffer is null or is too short, the routine + *   should return the full length required, and leaves the contents of the + *   buffer undefined; the caller shouldn't expect any contents to be filled + *   in if the return value is greater than buflen.  Both 'buflen' and the + *   return value include the null terminators, including the extra null + *   terminator at the end of the list.  If an error occurs, or the system + *   has no concept of a root directory, returns zero. + *    + *   Each result string should be expressed using the syntax for the root + *   directory on a device.  For example, on Windows, "C:\" represents the + *   root directory on the C: drive. + *    + *   "Local" means a device is mounted locally, as opposed to being merely + *   visible on the network via some remote node syntax; e.g., on Windows + *   this wouldn't include any UNC-style \\SERVER\SHARE names, and on VMS it + *   excludes any SERVER:: nodes.  It's up to each system how to treat + *   virtual local devices, i.e., those that look synctactically like local + *   devices but are actually mounted network devices, such as Windows mapped + *   network drives; we recommend including them if it would take extra work + *   to filter them out, and excluding them if it would take extra work to + *   include them.  "File-oriented" means that the returned devices are + *   accessed via file systems, not as character devices or raw block + *   devices; so this would exclude /dev/xxx devices on Unix and things like + *   CON: and LPT1: on Windows. + *    + *   Examples ("." represents a null byte): + *    + *   Windows: C:\.D:\.E:\.. + *    + *   Unix example: /.. + */ +size_t os_get_root_dirs(char *buf, size_t buflen); + + +/* ------------------------------------------------------------------------ */ +/* + *   Open a directory.  This begins an enumeration of a directory's contents. + *   'dirname' is a relative or absolute path to a directory.  On success, + *   returns true, and 'handle' is set to a port-defined handle value that's + *   used in subsequent calls to os_read_dir() and os_close_dir().  Returns + *   false on failure. + *    + *   If the routine succeeds, the caller must eventually call os_close_dir() + *   to release the resources associated with the handle. + */ +/* typedef <local system type> osdirhdl_t; */ +int os_open_dir(const char *dirname, /*OUT*/osdirhdl_t *handle); + +/* + *   Read the next file in a directory.  'handle' is a handle value obtained + *   from a call to os_open_dir().  On success, returns true and fills in + *   'fname' with the next filename; the handle is also internally updated so + *   that the next call to this function will retrieve the next file, and so + *   on until all files have been retrieved.  If an error occurs, or there + *   are no more files in the directory, returns false. + *    + *   The filename returned is the root filename only, without the path.  The + *   caller can build the full path by calling os_build_full_path() or + *   os_combine_paths() with the original directory name and the returned + *   filename as parameters. + *    + *   This routine lists all objects in the directory that are visible to the + *   corresponding native API, and is non-recursive.  The listing should thus + *   include subdirectory objects, but not the contents of subdirectories. + *   Implementations are encouraged to simply return all objects returned + *   from the corresponding native directory scan API; there's no need to do + *   any filtering, except perhaps in cases where it's difficult or + *   impossible to represent an object in terms of the osifc APIs (e.g., it + *   might be reasonable to exclude files without names).  System relative + *   links, such as the Unix/DOS "." and "..", specifically should be + *   included in the listing.  For unusual objects that don't fit into the + *   os_file_stat() taxonomy or that otherwise might create confusion for a + *   caller, err on the side of full disclosure (i.e., just return everything + *   unfiltered); if necessary, we can extend the os_file_stat() taxonomy or + *   add new osifc APIs to create a portable abstraction to handle whatever + *   is unusual or potentially confusing about the native object.  For + *   example, Unix implementations should feel free to return symbolic link + *   objects, including dangling links, since we have the portable + *   os_resolve_symlink() that lets the caller examine the meaning of the + *   link object. + */ +int os_read_dir(osdirhdl_t handle, char *fname, size_t fname_size); + +/* + *   Close a directory handle.  This releases the resources associated with a + *   directory search started with os_open_dir().  Every successful call to + *   os_open_dir() must have a matching call to os_close_dir().  As usual for + *   open/close protocols, the handle is invalid after calling this function, + *   so no more calls to os_read_dir() may be made with the handle. + */ +void os_close_dir(osdirhdl_t handle); + + +/* ------------------------------------------------------------------------ */ +/* + *   NB - this routine is DEPRECATED as of TADS 2.5.16/3.1.1.  Callers should + *   use os_open_dir(), os_read_dir(), os_close_dir() instead. + *    + *   Find the first file matching a given pattern.  The returned context + *   pointer is a pointer to whatever system-dependent context structure is + *   needed to continue the search with the next file, and is opaque to the + *   caller.  The caller must pass the context pointer to the next-file + *   routine.  The caller can optionally cancel a search by calling the + *   close-search routine with the context pointer.  If the return value is + *   null, it indicates that no matching files were found.  If a file was + *   found, outbuf will be filled in with its name, and isdir will be set to + *   true if the match is a directory, false if it's a file.  If pattern is + *   null, all files in the given directory should be returned; otherwise, + *   pattern is a string containing '*' and '?' as wildcard characters, but + *   not containing any directory separators, and all files in the given + *   directory matching the pattern should be returned. + *    + *   Important: because this routine may allocate memory for the returned + *   context structure, the caller must either call os_find_next_file until + *   that routine returns null, or call os_find_close() to cancel the search, + *   to ensure that the os code has a chance to release the allocated memory. + *    + *   'outbuf' should be set on output to the name of the matching file, + *   without any path information. + *    + *   'outpathbuf' should be set on output to full path of the matching file. + *   If possible, 'outpathbuf' should use the same relative or absolute + *   notation that the search criteria used on input.  For example, if dir = + *   "resfiles", and the file found is "MyPic.jpg", outpathbuf should be set + *   to "resfiles/MyPic.jpg" (or appropriate syntax for the local platform). + *   Similarly, if dir = "/home/tads/resfiles", outpath buf should be + *   "/home/tads/resfiles/MyPic.jpg".  The result should always conform to + *   correct local conventions, which may require some amount of manipulation + *   of the filename; for example, on the Mac, if dir = "resfiles", the + *   result should be ":resfiles:MyPic.jpg" (note the added leading colon) to + *   conform to Macintosh relative path notation. + *    + *   Note that 'outpathbuf' may be null, in which case the caller is not + *   interested in the full path information.   + */ +/*    + *   Note the following possible ways this function may be called: + *    + *   dir = "", pattern = filename - in this case, pattern is the name of a + *   file or directory in the current directory.  filename *might* be a + *   relative path specified by the user (on a command line, for example); + *   for instance, on Unix, it could be something like "resfiles/jpegs". + *    + *   dir = path, pattern = filname - same as above, but this time the + *   filename or directory pattern is relative to the given path, rather + *   than to the current directory.  For example, we could have dir = + *   "/games/mygame" and pattern = "resfiles/jpegs". + *    + *   dir = path, pattern = 0 (NULL) - this should search for all files in + *   the given path.  The path might be absolute or it might be relative. + *    + *   dir = path, pattern = "*" - this should have the same result as when + *   pattern = 0. + *    + *   dir = path, pattern = "*.ext" - this should search for all files in + *   the given path whose names end with ".ext". + *    + *   dir = path, pattern = "abc*" - this should search for all files in + *   the given path whose names start with "abc". + *    + *   All of these combinations are possible because callers, for + *   portability, must generally not manipulate filenames directly; + *   instead, callers obtain paths and search strings from external + *   sources, such as from the user, and present them to this routine with + *   minimal manipulation.   + */ +void *os_find_first_file(const char *dir, +                         char *outbuf, size_t outbufsiz, int *isdir, +                         char *outpathbuf, size_t outpathbufsiz); + +/* + *   Implementation notes for porting os_find_first_file: + *    + *   The algorithm for this routine should go something like this: + *    + *   - If 'path' is null, create a variable real_path and initialize it + *   with the current directory.  Otherwise, copy path to real_path. + *    + *   - If 'pattern' contains any directory separators ("/" on Unix, for + *   example), change real_path so that it reflects the additional leading + *   subdirectories in the path in 'pattern', and remove the leading path + *   information from 'pattern'.  For example, on Unix, if real_path + *   starts out as "./subdir", and pattern is "resfiles/jpegs", change + *   real_path to "./subdir/resfiles", and change pattern to "jpegs". + *   Take care to add and remove path separators as needed to keep the + *   path strings well-formed. + *    + *   - Begin a search using appropriate OS API's for all files in + *   real_path. + *    + *   - Check each file found.  Skip any files that don't match 'pattern', + *   treating "*" as a wildcard that matches any string of zero or more + *   characters, and "?" as a wildcard that matches any single character + *   (or matches nothing at the end of a string).  For example: + *    + *.  "*" matches anything + *.  "abc?" matches "abc", "abcd", "abce", "abcf", but not "abcde" + *.  "abc???" matches "abc", "abcd", "abcde", "abcdef", but not "abcdefg" + *.  "?xyz" matches "wxyz", "axyz", but not "xyz" or "abcxyz" + *    + *   - Return the first file that matches, if any, by filling in 'outbuf' + *   and 'isdir' with appropriate information.  Before returning, allocate + *   a context structure (which is entirely for your own use, and opaque + *   to the caller) and fill it in with the information necessary for + *   os_find_next_file to get the next matching file.  If no file matches, + *   return null.   + */ + + +/* + *   Find the next matching file, continuing a search started with + *   os_find_first_file().  Returns null if no more files were found, in + *   which case the search will have been automatically closed (i.e., + *   there's no need to call os_find_close() after this routine returns + *   null).  Returns a non-null context pointer, which is to be passed to + *   this function again to get the next file, if a file was found. + *    + *   'outbuf' and 'outpathbuf' are filled in with the filename (without + *   path) and full path (relative or absolute, as appropriate), + *   respectively, in the same manner as they do for os_find_first_file(). + *    + *   Implementation note: if os_find_first_file() allocated memory for the + *   search context, this routine must free the memory if it returs null, + *   because this indicates that the search is finished and the caller + *   need not call os_find_close().   + */ +void *os_find_next_file(void *ctx, char *outbuf, size_t outbufsiz, +                        int *isdir, char *outpathbuf, size_t outpathbufsiz); + +/* + *   Cancel a search.  The context pointer returned by the last call to + *   os_find_first_file() or os_find_next_file() is the parameter.  There + *   is no need to call this function if find-first or find-next returned + *   null, since they will have automatically closed the search. + *    + *   Implementation note: if os_find_first_file() allocated memory for the + *   search context, this routine should release the memory.   + */ +void os_find_close(void *ctx); + +/* + *   Special filename classification  + */ +enum os_specfile_t +{ +    /* not a special file */ +    OS_SPECFILE_NONE, + +    /*  +     *   current directory link - this is a file like the "." file on Unix +     *   or DOS, which is a special link that simply refers to itself  +     */ +    OS_SPECFILE_SELF, + +    /*  +     *   parent directory link - this is a file like the ".." file on Unix +     *   or DOS, which is a special link that refers to the parent +     *   directory  +     */ +    OS_SPECFILE_PARENT +}; + +/* + *   Determine if the given filename refers to a special file.  Returns the + *   appropriate enum value if so, or OS_SPECFILE_NONE if not.  The given + *   filename must be a root name - it must not contain a path prefix.  The + *   purpose here is to classify the results from os_find_first_file() and + *   os_find_next_file() to identify the special relative links, so callers + *   can avoid infinite recursion when traversing a directory tree. + */ +enum os_specfile_t os_is_special_file(const char *fname); + +/* ------------------------------------------------------------------------ */ +/*  + *   Convert string to all-lowercase.  + */ +char *os_strlwr(char *s); + + +/* ------------------------------------------------------------------------ */ +/* + *   Character classifications for quote characters.  os_squote() returns + *   true if its argument is any type of single-quote character; + *   os_dquote() returns true if its argument is any type of double-quote + *   character; and os_qmatch(a, b) returns true if a and b are matching + *   open- and close-quote characters. + *    + *   These functions allow systems with extended character codes with + *   weird quote characters (such as the Mac) to match the weird + *   characters, so that users can use the extended quotes in input. + *    + *   These are usually implemented as macros.  The most common + *   implementation simply returns true for the standard ASCII quote + *   characters: + *    + *   #define os_squote(c) ((c) == '\'') + *.  #define os_dquote(c) ((c) == '"') + *.  #define os_qmatch(a, b) ((a) == (b)) + *    + *   These functions take int arguments to allow for the possibility of + *   Unicode input.   + */ +/* int os_squote(int c); */ +/* int os_dquote(int c); */ +/* int os_qmatch(int a, int b); */ + + +/* ------------------------------------------------------------------------ */ +/* + *   Special file and directory locations + */ + +/* + *   Get the full filename (including directory path) to the executable + *   file, given the argv[0] parameter passed into the main program.  This + *   fills in the buffer with a null-terminated string that can be used in + *   osfoprb(), for example, to open the executable file. + *    + *   Returns non-zero on success.  If it's not possible to determine the + *   name of the executable file, returns zero. + *    + *   Some operating systems might not provide access to the executable file + *   information, so non-trivial implementation of this routine is optional; + *   if the necessary information is not available, simply implement this to + *   return zero.  If the information is not available, callers should offer + *   gracefully degraded functionality if possible.   + */ +int os_get_exe_filename(char *buf, size_t buflen, const char *argv0); + +/* + *   Get a special directory path.  Returns the selected path, in a format + *   suitable for use with os_build_full_path().  The main program's argv[0] + *   parameter is provided so that the system code can choose to make the + *   special paths relative to the program install directory, but this is + *   entirely up to the system implementation, so the argv[0] parameter can + *   be ignored if it is not needed. + *    + *   The 'id' parameter selects which special path is requested; this is one + *   of the constants defined below.  If the id is not understood, there is + *   no way of signalling an error to the caller; this routine can fail with + *   an assert() in such cases, because it indicates that the OS layer code + *   is out of date with respect to the calling code. + *    + *   This routine can be implemented using one of the strategies below, or a + *   combination of these.  These are merely suggestions, though, and systems + *   are free to ignore these and implement this routine using whatever + *   scheme is the best fit to local conventions. + *    + *   - Relative to argv[0].  Some systems use this approach because it keeps + *   all of the TADS files together in a single install directory tree, and + *   doesn't require any extra configuration information to find the install + *   directory.  Since we base the path name on the executable that's + *   actually running, we don't need any environment variables or parameter + *   files or registry entries to know where to look for related files. + *    + *   - Environment variables or local equivalent.  On some systems, it is + *   conventional to set some form of global system parameter (environment + *   variables on Unix, for example) for this sort of install configuration + *   data.  In these cases, this routine can look up the appropriate + *   configuration variables in the system environment. + *    + *   - Hard-coded paths.  Some systems have universal conventions for the + *   installation configuration of compiler-like tools, so the paths to our + *   component files can be hard-coded based on these conventions. + *    + *   - Hard-coded default paths with environment variable overrides.  Let the + *   user set environment variables if they want, but use the standard system + *   paths as hard-coded defaults if the variables aren't set.  This is often + *   the best choice; users who expect the standard system conventions won't + *   have to fuss with any manual settings or even be aware of them, while + *   users who need custom settings aren't stuck with the defaults. + */ +void os_get_special_path(char *buf, size_t buflen, +                         const char *argv0, int id); + +/*  + *   TADS 3 system resource path.  This path is used to load system + *   resources, such as character mapping files and error message files.   + */ +#define OS_GSP_T3_RES       1 + +/*  + *   TADS 3 compiler - system headers.  This is the #include path for the + *   header files included with the compiler.  + */ +#define OS_GSP_T3_INC       2 + +/* + *   TADS 3 compiler - system library source code.  This is the path to the + *   library source files that the compiler includes in every compilation by + *   default (such as _main.t).  + */ +#define OS_GSP_T3_LIB       3 + +/* + *   TADS 3 compiler - user library path list.  This is a list of directory + *   paths, separated by the OSPATHSEP character, that should be searched for + *   user library files.  The TADS 3 compiler uses this as an additional set + *   of locations to search after the list of "-Fs" options and before the + *   OS_GSP_T3_LIB directory. + *    + *   This path list is intended for the user's use, so no default value is + *   needed.  The value should be user-configurable using local conventions; + *   on Unix, for example, this might be handled with an environment + *   variable.   + */ +#define OS_GSP_T3_USER_LIBS 4 + +/* + *   TADS 3 interpreter - application data path.  This is the directory where + *   we should store things like option settings: data that we want to store + *   in a file, global to all games.  Depending on local system conventions, + *   this can be a global shared directory for all users, or can be a + *   user-specific directory.  + */ +#define OS_GSP_T3_APP_DATA 5 + +/* + *   TADS 3 interpreter - system configuration files.  This is used for files + *   that affect all games, and generally all users on the system, so it + *   should be in a central location.  On Windows, for example, we simply + *   store these files in the install directory containing the intepreter + *   binary.   + */ +#define OS_GSP_T3_SYSCONFIG  6 + +/* + *   System log files.  This is the directory for system-level status, debug, + *   and error logging files.  (Note that we're NOT talking about in-game + *   transcript logging per the SCRIPT command.  SCRIPT logs are usually sent + *   to files selected by the user via a save-file dialog, so these don't + *   need a special location.) + */ +#define OS_GSP_LOGFILE  7 + + +/*  + *   Seek to the resource file embedded in the current executable file, + *   given the main program's argv[0]. + *    + *   On platforms where the executable file format allows additional + *   information to be attached to an executable, this function can be used + *   to find the extra information within the executable. + *    + *   The 'typ' argument gives a resource type to find.  This is an arbitrary + *   string that the caller uses to identify what type of object to find. + *   The "TGAM" type, for example, is used by convention to indicate a TADS + *   compiled GAM file.   + */ +osfildef *os_exeseek(const char *argv0, const char *typ); + + +/* ------------------------------------------------------------------------ */ +/* + *   Load a string resource.  Given a string ID number, load the string + *   into the given buffer. + *    + *   Returns zero on success, non-zero if an error occurs (for example, + *   the buffer is too small, or the requested resource isn't present). + *    + *   Whenever possible, implementations should use an operating system + *   mechanism for loading the string from a user-modifiable resource + *   store; this will make localization of these strings easier, since the + *   resource store can be modified without the need to recompile the + *   application.  For example, on the Macintosh, the normal system string + *   resource mechanism should be used to load the string from the + *   application's resource fork. + *    + *   When no operating system mechanism exists, the resources can be + *   stored as an array of strings in a static variable; this isn't ideal, + *   because it makes it much more difficult to localize the application. + *    + *   Resource ID's are application-defined.  For example, for TADS 2, + *   "res.h" defines the resource ID's.   + */ +int os_get_str_rsc(int id, char *buf, size_t buflen); + + +/* ------------------------------------------------------------------------ */ +/* + *   Look for a file in the "standard locations": current directory, program + *   directory, PATH-like environment variables, etc.  The actual standard + *   locations are specific to each platform; the implementation is free to + *   use whatever conventions are appropriate to the local system.  On + *   systems that have something like Unix environment variables, it might be + *   desirable to define a TADS-specific variable (TADSPATH, for example) + *   that provides a list of directories to search for TADS-related files. + *    + *   On return, fill in 'buf' with the full filename of the located copy of + *   the file (if a copy was indeed found), in a format suitable for use with + *   the osfopxxx() functions; in other words, after this function returns, + *   the caller should be able to pass the contents of 'buf' to an osfopxxx() + *   function to open the located file. + *    + *   Returns true (non-zero) if a copy of the file was located, false (zero) + *   if the file could not be found in any of the standard locations.   + */ +int os_locate(const char *fname, int flen, const char *arg0, +              char *buf, size_t bufsiz); + + +/* ------------------------------------------------------------------------ */ +/* + *   Create and open a temporary file.  The file must be opened to allow + *   both reading and writing, and must be in "binary" mode rather than + *   "text" mode, if the system makes such a distinction.  Returns null on + *   failure. + *    + *   If 'fname' is non-null, then this routine should create and open a file + *   with the given name.  When 'fname' is non-null, this routine does NOT + *   need to store anything in 'buf'.  Note that the routine shouldn't try + *   to put the file in a special directory or anything like that; just open + *   the file with the name exactly as given. + *    + *   If 'fname' is null, this routine must choose a file name and fill in + *   'buf' with the chosen name; if possible, the file should be in the + *   conventional location for temporary files on this system, and should be + *   unique (i.e., it shouldn't be the same as any existing file).  The + *   filename stored in 'buf' is opaque to the caller, and cannot be used by + *   the caller except to pass to osfdel_temp().  On some systems, it may + *   not be possible to determine the actual filename of a temporary file; + *   in such cases, the implementation may simply store an empty string in + *   the buffer.  (The only way the filename would be unavailable is if the + *   implementation uses a system API that creates a temporary file, and + *   that API doesn't return the name of the created temporary file.  In + *   such cases, we don't need the name; the only reason we need the name is + *   so we can pass it to osfdel_temp() later, but since the system is going + *   to delete the file automatically, osfdel_temp() doesn't need to do + *   anything and thus doesn't need the name.) + *    + *   After the caller is done with the file, it should close the file (using + *   osfcls() as normal), then the caller MUST call osfdel_temp() to delete + *   the temporary file. + *    + *   This interface is intended to take advantage of systems that have + *   automatic support for temporary files, while allowing implementation on + *   systems that don't have any special temp file support.  On systems that + *   do have automatic delete-on-close support, this routine should use that + *   system-level support, because it helps ensure that temp files will be + *   deleted even if the caller fails to call osfdel_temp() due to a + *   programming error or due to a process or system crash.  On systems that + *   don't have any automatic delete-on-close support, this routine can + *   simply use the same underlying system API that osfoprwbt() normally + *   uses (although this routine must also generate a name for the temp file + *   when the caller doesn't supply one). + *    + *   This routine can be implemented using ANSI library functions as + *   follows: if 'fname' is non-null, return fopen(fname,"w+b"); otherwise, + *   set buf[0] to '\0' and return tmpfile().   + */ +osfildef *os_create_tempfile(const char *fname, char *buf); + +/* + *   Delete a temporary file - this is used to delete a file created with + *   os_create_tempfile().  For most platforms, this can simply be defined + *   the same way as osfdel().  For platforms where the operating system or + *   file manager will automatically delete a file opened as a temporary + *   file, this routine should do nothing at all, since the system will take + *   care of deleting the temp file. + *    + *   Callers are REQUIRED to call this routine after closing a file opened + *   with os_create_tempfile().  When os_create_tempfile() is called with a + *   non-null 'fname' argument, the same value should be passed as 'fname' to + *   this function.  When os_create_tempfile() is called with a null 'fname' + *   argument, then the buffer passed in the 'buf' argument to + *   os_create_tempfile() must be passed as the 'fname' argument here.  In + *   other words, if the caller explicitly names the temporary file to be + *   opened in os_create_tempfile(), then that same filename must be passed + *   here to delete the named file; if the caller lets os_create_tempfile() + *   generate a filename, then the generated filename must be passed to this + *   routine. + *    + *   If os_create_tempfile() is implemented using ANSI library functions as + *   described above, then this routine can also be implemented with ANSI + *   library calls as follows: if 'fname' is non-null and fname[0] != '\0', + *   then call remove(fname); otherwise do nothing.   + */ +int osfdel_temp(const char *fname); + +/* + *   Get the temporary file path.  This should fill in the buffer with a + *   path prefix (suitable for strcat'ing a filename onto) for a good + *   directory for a temporary file, such as the swap file.   + */ +void os_get_tmp_path(char *buf); + +/*  + *   Generate a name for a temporary file.  This constructs a random file + *   path in the system temp directory that isn't already used by an existing + *   file. + *    + *   On systems with long filenames, this can be implemented by selecting a + *   GUID-strength random name (such as 32 random hex digits) with a decent + *   random number generator.  That's long enough that the odds of a + *   collision are essentially zero.  On systems that only support short + *   filenames, the odds of a collision are non-zero, so the routine should + *   actually check that the chosen filename doesn't exist. + *    + *   Optionally, before returning, this routine *may* create (and close) an + *   empty placeholder file to "reserve" the chosen filename.  This isn't + *   required, and on systems with long filenames it's usually not necessary + *   because of the negligible chance of a collision.  On systems with short + *   filenames, a placeholder can be useful to prevent a subsequent call to + *   this routine, or a separate process, from using the same filename before + *   the caller has had a chance to use the returned name to create the + *   actual temp file. + *    + *   Returns true on success, false on failure.  This can fail if there's no + *   system temporary directory defined, or the temp directory is so full of + *   other files that we can't find an unused filename.   + */ +int os_gen_temp_filename(char *buf, size_t buflen); + + +/* ------------------------------------------------------------------------ */ +/* + *   Basic directory/folder management routines + */ + +/* + *   Switch to a new working directory. + *    + *   This is meant to behave similarly to the Unix concept of a working + *   directory, in that it sets the base directory assumed for subsequent + *   file operations (e.g., the osfopxx() functions, osfdel(), etc - anything + *   that takes a filename or directory name as an argument).  The working + *   directory applies to filenames specified with relative paths in the + *   local system notation.  File operations on filenames specified with + *   absolute paths, of course, ignore the working directory. + */ +void os_set_pwd(const char *dir); + +/* + *   Switch the working directory to the directory containing the given + *   file.  Generally, this routine should only need to parse the filename + *   enough to determine the part that's the directory path, then use + *   os_set_pwd() to switch to that directory.   + */ +void os_set_pwd_file(const char *filename); + +/* + *   Create a directory.  This creates a new directory/folder with the given + *   name, which may be given as a relative or absolute path.  Returns true + *   on success, false on failure. + *    + *   If 'create_parents' is true, and the directory has mulitiple path + *   elements, this routine should create each enclosing parent that doesn't + *   already exist.  For example, if the path is specified as "a/b/c", and + *   there exists a folder "a" in the working directory, but "a" is empty, + *   this should first create "b" and then create "c".  If an error occurs + *   creating any parent, the routine should simply stop and return failure. + *   (Optionally, the routine may attempt to behave atomically by undoing any + *   parent folder creations it accomplished before failing on a nested + *   folder, but this isn't required.  To reduce the chances of a failure + *   midway through the operation, the routine might want to scan the + *   filename before starting to ensure that it contains only valid + *   characters, since an invalid character is the most likely reason for a + *   failure part of the way through.) + *    + *   We recommend making the routine flexible in terms of the notation it + *   accepts; e.g., on Unix, "/dir/sub/folder" and "/dir/sub/folder/" should + *   be considered equivalent. + */ +int os_mkdir(const char *dir, int create_parents); + +/* + *   Remove a directory.  Returns true on success, false on failure. + *    + *   If the directory isn't already empty, this routine fails.  That is, the + *   routine does NOT recursively delete the contents of a non-empty + *   directory.  It's up to the caller to delete any contents before removing + *   the directory, if that's the caller's intention.  (Note to implementors: + *   most native OS APIs to remove directories fail by default if the + *   directory isn't empty, so it's usually safe to implement this simply by + *   calling the native API.  However, if your system's version of this API + *   can remove a non-empty directory, you MUST add an extra test before + *   removing the directory to ensure it's empty, and return failure if it's + *   not.  For the purposes of this test, "empty" should of course ignore any + *   special objects that are automatically or implicitly present in all + *   directories, such as the Unix "." and ".." relative links.) + */ +int os_rmdir(const char *dir); + + +/* ------------------------------------------------------------------------ */ +/* + *   Filename manipulation routines + */ + +/* apply a default extension to a filename, if it doesn't already have one */ +void os_defext(char *fname, const char *ext); + +/* unconditionally add an extention to a filename */ +void os_addext(char *fname, const char *ext); + +/* remove the extension from a filename */ +void os_remext(char *fname); + +/* + *   Compare two file names/paths for syntactic equivalence.  Returns true if + *   the names are equivalent names according to the local file system's + *   syntax conventions, false if not.  This does a syntax-only comparison of + *   the paths, without looking anything up in the file system.  This means + *   that a false return doesn't guarantee that the paths don't point to the + *   same file. + *    + *   This routine DOES make the following equivalences: + *    + *   - if the local file system is insensitive to case, the names are + *   compared ignoring case + *    + *   - meaningless path separator difference are ignored: on Unix, "a/b" == + *   "a//b" == "a/b/"; on Windows, "a/b" == "a\\b" + *    + *   - relative links that are strictly structural or syntactic are applied; + *   for example, on Unix or Windows, "a/./b" == "a/b" = "a/b/c/..".  This + *   only applies for special relative links that can be resolved without + *   looking anything up in the file system. + *    + *   This DOES NOT do the following: + *    + *   - it doesn't apply working directories/volums to relative paths + *    + *   - it doesn't follow symbolic links in the file system + */ +int os_file_names_equal(const char *a, const char *b); + +/* + *   Get a pointer to the root name portion of a filename.  This is the part + *   of the filename after any path or directory prefix.  For example, on + *   Unix, given the string "/home/mjr/deep.gam", this function should return + *   a pointer to the 'd' in "deep.gam".  If the filename doesn't appear to + *   have a path prefix, it should simply return the argument unchanged. + *    + *   IMPORTANT: the returned pointer MUST point into the original 'buf' + *   string, and the contents of that buffer must NOT be modified.  The + *   return value must point into the same buffer because there are no + *   allowances for the alternatives.  In particular, (a) you can't return a + *   pointer to newly allocated memory, because callers won't free it, so + *   doing so would cause a memory leak; and (b) you can't return a pointer + *   to an internal static buffer, because callers might call this function + *   more than once and still rely on a value returned on an older call, + *   which would be invalid if a static buffer could be overwritten on each + *   call.  For these reasons, it's required that the return value point to a + *   position within the original string passed in 'buf'.   + */ +char *os_get_root_name(const char *buf); + +/* + *   Determine whether a filename specifies an absolute or relative path. + *   This is used to analyze filenames provided by the user (for example, + *   in a #include directive, or on a command line) to determine if the + *   filename can be considered relative or absolute.  This can be used, + *   for example, to determine whether to search a directory path for a + *   file; if a given filename is absolute, a path search makes no sense. + *   A filename that doesn't specify an absolute path can be combined with + *   a path using os_build_full_path(). + *    + *   Returns true if the filename specifies an absolute path, false if + *   not.   + */ +int os_is_file_absolute(const char *fname); + +/* + *   Extract the path from a filename.  Fills in pathbuf with the path + *   portion of the filename.  If the filename has no path, the pathbuf + *   should be set appropriately for the current directory (on Unix or DOS, + *   for example, it can be set to an empty string). + *    + *   The result can end with a path separator character or not, depending on + *   local OS conventions.  Paths extracted with this function can only be + *   used with os_build_full_path(), so the conventions should match that + *   function's. + *    + *   Unix examples: + *    + *.   /home/mjr/deep.gam -> /home/mjr + *.   games/deep.gam -> games + *.   deep.gam -> [empty string] + *    + *   Mac examples: + *    + *    :home:mjr:deep.gam -> :home:mjr + *.   Hard Disk:games:deep.gam -> Hard Disk:games + *.   Hard Disk:deep.gam -> Hard Disk: + *.   deep.gam -> [empty string] + *    + *   VMS examples: + * + *.   SYS$DISK:[mjr.games]deep.gam -> SYS$DISK:[mjr.games] + *.   SYS$DISK:[mjr.games] -> SYS$DISK:[mjr] + *.   deep.gam -> [empty string] + *    + *   Note in the last example that we've retained the trailing colon in the + *   path, whereas we didn't in the others; although the others could also + *   retain the trailing colon, it's required only for the last case.  The + *   last case requires the colon because it would otherwise be impossible to + *   determine whether "Hard Disk" was a local subdirectory or a volume name. + *    + */ +void os_get_path_name(char *pathbuf, size_t pathbuflen, const char *fname); + +/* + *   Build a full path name, given a path and a filename.  The path may have + *   been specified by the user, or may have been extracted from another file + *   via os_get_path_name().  This routine must take care to add path + *   separators as needed, but also must take care not to add too many path + *   separators. + *    + *   This routine should reformat the path into canonical format to the + *   extent possible purely through syntactic analysis.  For example, special + *   relative links, such as Unix "." and "..", should be resolved; for + *   example, combining "a/./b/c" with ".." on Unix should yield "a/b". + *   However, symbolic links that require looking up names in the file system + *   should NOT be resolved.  We don't want to perform any actual file system + *   lookups because might want to construct hypothetical paths that don't + *   necessarily relate to files on the local system. + *    + *   Note that relative path names may require special care on some + *   platforms.  In particular, if the source path is relative, the result + *   should also be relative.  For example, on the Macintosh, a path of + *   "games" and a filename "deep.gam" should yield ":games:deep.gam" - note + *   the addition of the leading colon to make the result path relative. + *    + *   Note also that the 'filename' argument is not only allowed to be an + *   ordinary file, possibly qualified with a relative path, but is also + *   allowed to be a subdirectory.  The result in this case should be a path + *   that can be used as the 'path' argument to a subsequent call to + *   os_build_full_path; this allows a path to be built in multiple steps by + *   descending into subdirectories one at a time. + *    + *   Unix examples: + *    + *.   /home/mjr + deep.gam -> /home/mjr/deep.gam" + *.   /home/mjr + .. -> /home + *.   /home/mjr + ../deep.gam -> /home/deep.gam + *.   /home/mjr/ + deep.gam -> /home/mjr/deep.gam" + *.   games + deep.gam -> games/deep.gam" + *.   games/ + deep.gam -> games/deep.gam" + *.   /home/mjr + games/deep.gam -> /home/mjr/games/deep.gam" + *.   games + scifi/deep.gam -> games/scifi/deep.gam" + *.   /home/mjr + games -> /home/mjr/games" + *    + *   Mac examples: + *    + *.   Hard Disk: + deep.gam -> Hard Disk:deep.gam + *.   :games: + deep.gam -> :games:deep.gam + *.   :games:deep + ::test.gam -> :games:test.gam + *.   games + deep.gam -> :games:deep.gam + *.   Hard Disk: + :games:deep.gam -> Hard Disk:games:deep.gam + *.   games + :scifi:deep.gam -> :games:scifi:deep.gam + *.   Hard Disk: + games -> Hard Disk:games + *.   Hard Disk:games + scifi -> Hard Disk:games:scifi + *.   Hard Disk:games:scifi + deep.gam -> Hard Disk:games:scifi:deep.gam + *.   Hard Disk:games + :scifi:deep.gam -> Hard Disk:games:scifi:deep.gam + *    + *   VMS examples: + *    + *.   [home.mjr] + deep.gam -> [home.mjr]deep.gam + *.   [home.mjr] + [-]deep.gam -> [home]deep.gam + *.   mjr.dir + deep.gam -> [.mjr]deep.gam + *.   [home]mjr.dir + deep.gam -> [home.mjr]deep.gam + *.   [home] + [.mjr]deep.gam -> [home.mjr]deep.gam + */ +void os_build_full_path(char *fullpathbuf, size_t fullpathbuflen, +                        const char *path, const char *filename); + +/* + *   Combine a path and a filename to form a full path to the file.  This is + *   *almost* the same as os_build_full_path(), but if the 'filename' element + *   is a special relative link, such as Unix '.' or '..', this preserves + *   that special link in the final name. + *    + *   Unix examples: + *    + *.    /home/mjr + deep.gam -> /home/mjr/deep.gam + *.    /home/mjr + . -> /home/mjr/. + *.    /home/mjr + .. -> /home/mjr/.. + *    + *   Mac examples: + *    + *.    Hard Disk:games + deep.gam -> HardDisk:games:deep.gam + *.    Hard Disk:games + :: -> HardDisk:games:: + *    + *   VMS exmaples: + *    + *.    [home.mjr] + deep.gam -> [home.mjr]deep.gam + *.    [home.mjr] + [-] -> [home.mjr.-] + */ +void os_combine_paths(char *fullpathbuf, size_t pathbuflen, +                      const char *path, const char *filename); + + +/* + *   Get the absolute, fully qualified filename for a file.  This fills in + *   'result_buf' with the absolute path to the given file, taking into + *   account the current working directory and any other implied environment + *   information that affects the way the file system would resolve the given + *   file name to a specific file on disk if we opened the file now using + *   this name. + *    + *   The returned path should be in absolute path form, meaning that it's + *   independent of the current working directory or any other environment + *   settings.  That is, this path should still refer to the same file even + *   if the working directory changes. + *    + *   Note that it's valid to get the absolute path for a file that doesn't + *   exist, or for a path with directory components that don't exist.  For + *   example, a caller might generate the absolute path for a file that it's + *   about to create, or a hypothetical filename for path comparison + *   purposes.  The function should succeed even if the file or any path + *   components don't exist.  If the file is in relative format, and any path + *   elements don't exist but are syntactically well-formed, the result + *   should be the path obtained from syntactically combining the working + *   directory with the relative path. + *    + *   On many systems, a given file might be reachable through more than one + *   absolute path.  For example, on Unix it might be possible to reach a + *   file through symbolic links to the file itself or to parent directories, + *   or hard links to the file.  It's up to the implementation to determine + *   which path to use in such cases. + *    + *   On success, returns true.  If it's not possible to resolve the file name + *   to an absolute path, the routine copies the original filename to the + *   result buffer exactly as given, and returns false.   + */ +int os_get_abs_filename(char *result_buf, size_t result_buf_size, +                        const char *filename); + +/* + *   Get the relative version of the given filename path 'filename', relative + *   to the given base directory 'basepath'.  Both paths must be given in + *   absolute format. + *    + *   Returns true on success, false if it's not possible to rewrite the path + *   in relative terms.  For example, on Windows, it's not possible to + *   express a path on the "D:" drive as relative to a base path on the "C:" + *   drive, since each drive letter has an independent root folder; there's + *   no namespace entity enclosing a drive letter's root folder.  On + *   Unix-like systems where the entire namespace has a single hierarchical + *   root, it should always be possible to express any path relative to any + *   other. + *    + *   The result should be a relative path that can be combined with + *   'basepath' using os_build_full_path() to reconstruct a path that + *   identifies the same file as the original 'filename' (it's not important + *   that this procedure would result in the identical string - it just has + *   to point to the same file).  If it's not possible to express the + *   filename relative to the base path, fill in 'result_buf' with the + *   original filename and return false. + *    + *   Windows examples: + *    + *.    c:\mjr\games | c:\mjr\games\deep.gam  -> deep.gam + *.    c:\mjr\games | c:\mjr\games\tads\deep.gam  -> tads\deep.gam + *.    c:\mjr\games | c:\mjr\tads\deep.gam  -> ..\tads\deep.gam + *.    c:\mjr\games | d:\deep.gam  ->  d:\deep.gam (and return false) + *    + *   Mac OS examples: + *    + *.    Mac HD:mjr:games | Mac HD:mjr:games:deep.gam -> deep.gam + *.    Mac HD:mjr:games | Mac HD:mjr:games:tads:deep.gam -> :tads:deep.gam + *.    Mac HD:mjr:games | Ext Disk:deep.gam -> Ext Disk:deep.gam (return false) + *    + *   VMS examples: + *    + *.    SYS$:[mjr.games] | SYS$:[mjr.games]deep.gam -> deep.gam + *.    SYS$:[mjr.games] | SYS$:[mjr.games.tads]deep.gam -> [.tads]deep.gam + *.    SYS$:[mjr.games] | SYS$:[mjr.tads]deep.gam -> [-.tads]deep.gam + *.    SYS$:[mjr.games] | DISK$:[mjr]deep.gam -> DISK$[mjr]deep.gam (ret false) + */ +int os_get_rel_path(char *result_buf, size_t result_buf_size, +                    const char *basepath, const char *filename); + +/* + *   Determine if the given file is in the given directory.  Returns true if + *   so, false if not.  'filename' is a relative or absolute file name; + *   'path' is a relative or absolute directory path, such as one returned + *   from os_get_path_name(). + *    + *   If 'include_subdirs' is true, the function returns true if the file is + *   either directly in the directory 'path', OR it's in any subdirectory of + *   'path'.  If 'include_subdirs' is false, the function returns true only + *   if the file is directly in the given directory. + *    + *   If 'match_self' is true, the function returns true if 'filename' and + *   'path' are the same directory; otherwise it returns false in this case. + *    + *   This routine is allowed to return "false negatives" - that is, it can + *   claim that the file isn't in the given directory even when it actually + *   is.  The reason is that it's not always possible to determine for sure + *   that there's not some way for a given file path to end up in the given + *   directory.  In contrast, a positive return must be reliable. + *    + *   If possible, this routine should fully resolve the names through the + *   file system to determine the path relationship, rather than merely + *   analyzing the text superficially.  This can be important because many + *   systems have multiple ways to reach a given file, such as via symbolic + *   links on Unix; analyzing the syntax alone wouldn't reveal these multiple + *   pathways. + *    + *   SECURITY NOTE: If possible, implementations should fully resolve all + *   symbolic links, relative paths (e.g., Unix ".."), etc, before rendering + *   judgment.  One important application for this routine is to determine if + *   a file is in a sandbox directory, to enforce security restrictions that + *   prevent a program from accessing files outside of a designated folder. + *   If the implementation fails to resolve symbolic links or relative paths, + *   a malicious program or user could bypass the security restriction by, + *   for example, creating a symbolic link within the sandbox directory that + *   points to the root folder.  Implementations can avoid this loophole by + *   converting the file and directory names to absolute paths and resolving + *   all symbolic links and relative notation before comparing the paths. + */ +int os_is_file_in_dir(const char *filename, const char *path, +                      int include_subdirs, int match_self); + + +/* ------------------------------------------------------------------------ */ +/* + *   Convert an OS filename path to URL-style format.  This isn't a true URL + *   conversion; rather, it simply expresses a filename in Unix-style + *   notation, as a series of path elements separated by '/' characters. + *   Unlike true URLs, we don't use % encoding or a scheme prefix (file://, + *   etc). + *    + *   The result path never ends in a trailing '/', unless the entire result + *   path is "/".  This is for consistency; even if the source path ends with + *   a local path separator, the result doesn't. + *    + *   If the local file system syntax uses '/' characters as ordinary filename + *   characters, these must be replaced with some other suitable character in + *   the result, since otherwise they'd be taken as path separators when the + *   URL is parsed.  If possible, the substitution should be reversible with + *   respect to os_cvt_dir_url(), so that the same URL read back in on this + *   same platform will produce the same original filename.  One particular + *   suggestion is that if the local system uses '/' to delimit what would be + *   a filename extension on other platforms, replace '/' with '.', since + *   this will provide reversibility as well as a good mapping if the URL is + *   read back in on another platform. + *    + *   The local equivalents of "." and "..", if they exist, are converted to + *   "." and ".." in the URL notation. + *    + *   Examples: + *    + *.   Windows: images\rooms\startroom.jpg -> images/rooms/startroom.jpg + *.   Windows: ..\startroom.jpg -> ../startroom.jpg + *.   Mac:     :images:rooms:startroom.jpg -> images/rooms/startroom.jpg + *.   Mac:     ::startroom.jpg -> ../startroom.jpg + *.   VMS:     [.images.rooms]startroom.jpg -> images/rooms/startroom.jpg + *.   VMS:     [-.images]startroom.jpg -> ../images/startroom.jpg + *.   Unix:    images/rooms/startroom.jpg -> images/rooms/startroom.jpg + *.   Unix:    ../images/startroom.jpg -> ../images/startroom.jpg + *    + *   If the local name is an absolute path in the local file system (e.g., + *   Unix /file, Windows C:\file), translate as follows.  If the local + *   operating system uses a volume or device designator (Windows C:, VMS + *   SYS$DISK:, etc), make the first element of the path the exact local + *   syntax for the device designator: /C:/ on Windows, /SYS$DISK:/ on VMS, + *   etc.  Include the local syntax for the device prefix.  For a system like + *   Unix with a unified file system root ("/"), simply start with the root + *   directory.  Examples: + *    + *.    Windows:  C:\games\deep.gam         -> /C:/games/deep.gam + *.    Windows:  C:games\deep.gam          -> /C:./games/deep.gam + *.    Windows:  \\SERVER\DISK\games\deep.gam -> /\\SERVER/DISK/games/deep.gam + *.    Mac OS 9: Hard Disk:games:deep.gam  -> /Hard Disk:/games/deep.gam + *.    VMS:      SYS$DISK:[games]deep.gam  -> /SYS$DISK:/games/deep.gam + *.    Unix:     /games/deep.gam           -> /games/deep.gam + *    + *   Rationale: it's effectively impossible to create a truly portable + *   representation of an absolute path.  Operating systems are too different + *   in the way they represent root paths, and even if that were solvable, a + *   root path is essentially unusable across machines anyway because it + *   creates a dependency on the contents of a particular machine's disk.  So + *   if we're called upon to translate an absolute path, we can forget about + *   trying to be truly portable and instead focus on round-trip fidelity - + *   i.e., making sure that applying os_cvt_url_dir() to our result recovers + *   the exact original path, assuming it's done on the same operating + *   system.  The approach outlined above should achieve round-trip fidelity + *   when a local path is converted to a URL and back on the same machine, + *   since the local URL-to-path converter should recognize its own special + *   type of local absolute path prefix.  It also produces reasonable results + *   on other platforms - see the os_cvt_url_dir() comments below for + *   examples of the decoding results for absolute paths moved to new + *   platforms.  The result when a device-rooted absolute path is encoded on + *   one machine and then decoded on another will generally be a local path + *   with a root on the default device/volume and an outermost directory with + *   a name based on the original machine's device/volume name.  This + *   obviously won't reproduce the exact original path, but since that's + *   impossible anyway, this is probably as good an approximation as we can + *   create. + *    + *   Character sets: the input could be in local or UTF-8 character sets. + *   The implementation shouldn't care, though - just treat bytes in the + *   range 0-127 as plain ASCII, and everything else as opaque.  I.e., do not + *   quote or otherwise modify characters outside the 0-127 range.   + */ +void os_cvt_dir_url(char *result_buf, size_t result_buf_size, +                    const char *src_path); + +/* + *   Convert a URL-style path into a filename path expressed in the local + *   file system's syntax.  Fills in result_buf with a file path, constructed + *   using the local file system syntax, that corresponds to the path in + *   src_url expressed in URL-style syntax.  Examples: + *    + *   images/rooms/startroom.jpg ->  + *.   Windows   -> images\rooms\startroom.jpg + *.   Mac OS 9  -> :images:rooms:startroom.jpg + *.   VMS       -> [.images.rooms]startroom.jpg + *    + *   The source format isn't a true URL; it's simply a series of path + *   elements separated by '/' characters.  Unlike true URLs, our input + *   format doesn't use % encoding and doesn't have a scheme (file://, etc). + *   (Any % in the source is treated as an ordinary character and left as-is, + *   even if it looks like a %XX sequence.  Anything that looks like a scheme + *   prefix is left as-is, with any // treated as path separators. + *    + *   images/file%20name.jpg -> + *.   Windows   -> images\file%20name.jpg + *    + *   file://images/file.jpg -> + *.   Windows   -> file_\\images\file.jpg + *    + *   Any characters in the path that are invalid in the local file system + *   naming rules are converted to "_", unless "_" is itself invalid, in + *   which case they're converted to "X".  One exception is that if '/' is a + *   valid local filename character (rather than a path separator as it is on + *   Unix and Windows), it can be used as the replacement for the character + *   that os_cvt_dir_url uses as its replacement for '/', so that this + *   substitution is reversible when a URL is generated and then read back in + *   on this same platform. + *    + *   images/file:name.jpg -> + *.   Windows   -> images\file_name.jpg + *.   Mac OS 9  -> :images:file_name.jpg + *.   Unix      -> images/file:name.jpg + *    + *   The path elements "." and ".." are specifically defined as having their + *   Unix meanings: "." is an alias for the preceding path element, or the + *   working directory if it's the first element, and ".." is an alias for + *   the parent of the preceding element.  When these appear as path + *   elements, this routine translates them to the appropriate local + *   conventions.  "." may be translated simply by removing it from the path, + *   since it reiterates the previous path element.  ".." may be translated + *   by removing the previous element - HOWEVER, if ".." appears as the first + *   element, it has to be retained and translated to the equivalent local + *   notation, since it will have to be applied later, when the result_buf + *   path is actually used to open a file, at which point it will combined + *   with the working directory or another base path. + *    + *.  /images/../file.jpg -> [Windows] file.jpg + *.  ../images/file.jpg -> + *.   Windows  -> ..\images\file.jpg + *.   Mac OS 9 -> ::images:file.jpg + *.   VMS      -> [-.images]file.jpg + *    + *   If the URL path is absolute (starts with a '/'), the routine inspects + *   the path to see if it was created by the same OS, according to the local + *   rules for converting absolute paths in os_cvt_dir_url() (see).  If so, + *   we reverse the encoding done there.  If it doesn't appear that the name + *   was created by the same operating system - that is, if reversing the + *   encoding doesn't produce a valid local filename - then we create a local + *   absolute path as follows.  If the local system uses device/volume + *   designators, we start with the current working device/volume or some + *   other suitable default volume.  We then add the first element of the + *   path, if any, as the root directory name, applying the usual "_" or "X" + *   substitution for any characters that aren't allowed in local names.  The + *   rest of the path is handled in the usual fashion. + *    + *.  /images/file.jpg -> + *.    Windows -> \images\file.jpg + *.    Unix    -> /images/file.jpg + *    + *.  /c:/images/file.jpg -> + *.    Windows -> c:\images\file.jpg + *.    Unix    -> /c:/images/file.jpg + *.    VMS     -> SYS$DISK:[c__.images]file.jpg + *    + *.  /Hard Disk:/images/file.jpg -> + *.    Windows -> \Hard Disk_\images\file.jpg + *.    Unix    -> SYS$DISK:[Hard_Disk_.images]file.jpg + *    + *   Note how the device/volume prefix becomes the top-level directory when + *   moving a path across machines.  It's simply not possible to reconstruct + *   the exact original path in such cases, since device/volume syntax rules + *   have little in common across systems.  But this seems like a good + *   approximation in that (a) it produces a valid local path, and (b) it + *   gives the user a reasonable basis for creating a set of folders to mimic + *   the original source system, if they want to use that approach to port + *   the data rather than just changing the paths internally in the source + *   material. + *    + *   Character sets: use the same rules as for os_cvt_dir_url().   + */ +void os_cvt_url_dir(char *result_buf, size_t result_buf_size, +                    const char *src_url); + + +/* ------------------------------------------------------------------------ */ +/* + *   Get a suitable seed for a random number generator; should use the system + *   clock or some other source of an unpredictable and changing seed value. + */ +void os_rand(long *val); + +/* + *   Generate random bytes for use in seeding a PRNG (pseudo-random number + *   generator).  This is an extended version of os_rand() for PRNGs that use + *   large seed vectors containing many bytes, rather than the simple 32-bit + *   seed that os_rand() assumes. + *    + *   As with os_rand(), this function isn't meant to be used directly as a + *   random number source for ongoing use - instead, this is intended mostly + *   for seeding a PRNG, which will then be used as the primary source of + *   random numbers.  The big problem with using this routine directly as a + *   randomness source is that some implementations might rely heavily on + *   environmental randomness, such as the real-time clock or OS usage + *   statistics.  Such sources tend to provide reasonable entropy from one + *   run to the next, but not within a single session, as the underlying data + *   sources don't change rapidly enough. + *    + *   Ideally, this routine should generate *truly* random bytes obtained from + *   hardware sources.  Not all systems can provide that, though, so true + *   randomness isn't guaranteed.  Here are the suggested implementation + *   options, in descending order of desirability: + *    + *   1.  Use a hardware source of true randomness, such as a /dev/rand type + *   of device.  However, note that this call should return reasonably + *   quickly, so always use a non-blocking source.  Some Unix /dev/rand + *   devices, for example, can block indefinitely to allow sufficient entropy + *   to accumulate. + *    + *   2. Use a cryptographic random number source provided by the OS.  Some + *   systems provide this as an API service.  If going this route, be sure + *   that the OS generator is itself "seeded" with some kind of true + *   randomness source, as it defeats the whole purpose if you always return + *   a fixed pseudo-random sequence each time the program runs. + *    + *   3. Use whatever true random sources are available locally to seed a + *   software pseudo-random number generator, then generate bytes from your + *   PRNG.  Some commonly available sources of true randomness are a + *   high-resolution timer, the system clock, the current process ID, + *   logged-in user ID, environment variables, uninitialized pages of memory, + *   the IP address; each of these sources might give you a few bits of + *   entropy at best, so the best bet is to use an ensemble.  You could, for + *   example, concatenate a bunch of this type of information together and + *   calculate an MD5 or SHA1 hash to mix the bits more thoroughly.  For the + *   PRNG, use a cryptographic generator.  (You could use the ISAAC generator + *   from TADS 3, as that's a crypto PRNG, but it's probably better to use a + *   different generator here since TADS 3 is going to turn around and use + *   this function's output to seed ISAAC - seeding one ISAAC instance with + *   another ISAAC's output seems likely to magnify any weaknesses in the + *   ISAAC algorithm.)  Note that this option is basically the DIY version of + *   option 2.  Option 2 is better because the OS probably has access to + *   better sources of true randomness than an application does.   + */ +void os_gen_rand_bytes(unsigned char *buf, size_t len); + + +/* ------------------------------------------------------------------------ */ +/* + *   Display routines. + *    + *   Our display model is a simple stdio-style character stream. + *    + *   In addition, we provide an optional "status line," which is a + *   non-scrolling area where a line of text can be displayed.  If the status + *   line is supported, text should only be displayed in this area when + *   os_status() is used to enter status-line mode (mode 1); while in status + *   line mode, text is written to the status line area, otherwise (mode 0) + *   it's written to the normal main text area.  The status line is normally + *   shown in a different color to set it off from the rest of the text. + *    + *   The OS layer can provide its own formatting (word wrapping in + *   particular) if it wants, in which case it should also provide pagination + *   using os_more_prompt().   + */ + +/* + *   OS_MAXWIDTH - the maximum width of a line of text.  Most platforms use + *   135 for this, but you can use more or less as appropriate.  If you use + *   OS-level line wrapping, then the true width of a text line is + *   irrelevant, and the portable code will use this merely for setting its + *   internal buffer sizes. + *    + *   This must be defined in the os_xxx.h header file for each platform. + */ +/*#define OS_MAXWIDTH 135 - example only: define for real in os_xxx.h header*/ + +/* + *   Print a string on the console.  These routines come in two varieties: + *    + *   os_printz - write a NULL-TERMINATED string + *.  os_print - write a COUNTED-LENGTH string, which may not end with a null + *    + *   These two routines are identical except that os_printz() takes a string + *   which is terminated by a null byte, and os_print() instead takes an + *   explicit length, and a string that may not end with a null byte. + *    + *   os_printz(str) may be implemented as simply os_print(str, strlen(str)). + *    + *   The string is written in one of three ways, depending on the status mode + *   set by os_status(): + *    + *   status mode == 0 -> write to main text window + *.  status mode == 1 -> write to status line + *.  anything else -> do not display the text at all + *    + *   Implementations are free to omit any status line support, in which case + *   they should simply suppress all output when the status mode is anything + *   other than zero. + *    + *   The following special characters must be recognized in the displayed + *   text: + *    + *   '\n' - newline: end the current line and move the cursor to the start of + *   the next line.  If the status line is supported, and the current status + *   mode is 1 (i.e., displaying in the status line), then two special rules + *   apply to newline handling: newlines preceding any other text should be + *   ignored, and a newline following any other text should set the status + *   mode to 2, so that all subsequent output is suppressed until the status + *   mode is changed with an explicit call by the client program to + *   os_status(). + *    + *   '\r' - carriage return: end the current line and move the cursor back to + *   the beginning of the current line.  Subsequent output is expected to + *   overwrite the text previously on this same line.  The implementation + *   may, if desired, IMMEDIATELY clear the previous text when the '\r' is + *   written, rather than waiting for subsequent text to be displayed. + *    + *   All other characters may be assumed to be ordinary printing characters. + *   The routine need not check for any other special characters. + *    + */ +void os_printz(const char *str); +void os_print(const char *str, size_t len); + +/* + *   Print to the debugger console.  These routines are for interactive + *   debugger builds only: they display the given text to a separate window + *   within the debugger UI (separate from the main game command window) + *   where the debugger displays status information specific to the debugging + *   session (such as compiler/build output, breakpoint status messages, + *   etc).  For example, TADS Workbench on Windows displays these messages in + *   its "Debug Log" window. + *    + *   These routines only need to be implemented for interactive debugger + *   builds, such as TADS Workbench on Windows.  These can be omitted for + *   regular interpreter builds.   + */ +void os_dbg_printf(const char *fmt, ...); +void os_dbg_vprintf(const char *fmt, va_list args); + +/* + *   Allocating sprintf and vsprintf.  These work like the regular C library + *   sprintf and vsprintf funtions, but they allocate a return buffer that's + *   big enough to hold the result, rather than formatting into a caller's + *   buffer.  This protects against buffer overruns and ensures that the + *   result isn't truncated. + *    + *   On return, '*bufptr' is filled in with a pointer to a buffer allocated + *   with osmalloc().  This buffer contains the formatted string result.  The + *   caller is responsible for freeing the buffer by calling osfree(). + *   *bufptr can be null on return if an error occurs. + *    + *   The return value is the number of bytes written to the allocated buffer, + *   not including the null terminator.  If an error occurs, the return value + *   is -1 and *bufptr is undefined. + *    + *   Many modern C libraries provide equivalents of these, usually called + *   asprintf() and vasprintf(), respectively.   + */ +/* int os_asprintf(char **bufptr, const char *fmt, ...); */ +int os_vasprintf(char **bufptr, const char *fmt, va_list ap); + + +/*  + *   Set the status line mode.  There are three possible settings: + *    + *   0 -> main text mode.  In this mode, all subsequent text written with + *   os_print() and os_printz() is to be displayed to the main text area. + *   This is the normal mode that should be in effect initially.  This mode + *   stays in effect until an explicit call to os_status(). + *    + *   1 -> statusline mode.  In this mode, text written with os_print() and + *   os_printz() is written to the status line, which is usually rendered as + *   a one-line area across the top of the terminal screen or application + *   window.  In statusline mode, leading newlines ('\n' characters) are to + *   be ignored, and any newline following any other character must change + *   the mode to 2, as though os_status(2) had been called. + *    + *   2 -> suppress mode.  In this mode, all text written with os_print() and + *   os_printz() must simply be ignored, and not displayed at all.  This mode + *   stays in effect until an explicit call to os_status().   + */ +void os_status(int stat); + +/* get the status line mode */ +int os_get_status(); + +/*  + *   Set the score value.  This displays the given score and turn counts on + *   the status line.  In most cases, these values are displayed at the right + *   edge of the status line, in the format "score/turns", but the format is + *   up to the implementation to determine.  In most cases, this can simply + *   be implemented as follows: + *    + *.  void os_score(int score, int turncount) + *.  { + *.     char buf[40]; + *.     sprintf(buf, "%d/%d", score, turncount); + *.     os_strsc(buf); + *.  } + */ +void os_score(int score, int turncount); + +/* display a string in the score area in the status line */ +void os_strsc(const char *p); + +/* clear the screen */ +void oscls(void); + +/* redraw the screen */ +void os_redraw(void); + +/* flush any buffered display output */ +void os_flush(void); + +/* + *   Update the display - process any pending drawing immediately.  This + *   only needs to be implemented for operating systems that use + *   event-driven drawing based on window invalidations; the Windows and + *   Macintosh GUI's both use this method for drawing window contents. + *    + *   The purpose of this routine is to refresh the display prior to a + *   potentially long-running computation, to avoid the appearance that the + *   application is frozen during the computation delay. + *    + *   Platforms that don't need to process events in the main thread in order + *   to draw their window contents do not need to do anything here.  In + *   particular, text-mode implementations generally don't need to implement + *   this routine. + *    + *   This routine doesn't absolutely need a non-empty implementation on any + *   platform, but it will provide better visual feedback if implemented for + *   those platforms that do use event-driven drawing.   + */ +void os_update_display(); + + +/* ------------------------------------------------------------------------ */ +/* + *   Set text attributes.  Text subsequently displayed through os_print() and + *   os_printz() are to be displayed with the given attributes. + *    + *   'attr' is a (bitwise-OR'd) combination of OS_ATTR_xxx values.  A value + *   of zero indicates normal text, with no extra attributes.   + */ +void os_set_text_attr(int attr); + +/* attribute code: bold-face */ +#define OS_ATTR_BOLD     0x0001 + +/* attribute code: italic */ +#define OS_ATTR_ITALIC   0x0002 + +/* + *   Abstract attribute codes.  Each platform can choose a custom rendering + *   for these by #defining them before this point, in the OS-specific header + *   (osdos.h, osmac.h, etc).  We provide *default* definitions in case the + *   platform doesn't define these. + *    + *   For compatibility with past versions, we treat HILITE, EM, and BOLD as + *   equivalent.  Platforms that can display multiple kinds of text + *   attributes (boldface and italic, say) should feel free to use more + *   conventional HTML mappings, such as EM->italic and STRONG->bold.   + */ + +/*  + *   "Highlighted" text, as appropriate to the local platform.  On most + *   text-mode platforms, the only kind of rendering variation possible is a + *   brighter or intensified color.  If actual bold-face is available, that + *   can be used instead.  This is the attribute used for text enclosed in a + *   TADS2 "\( \)" sequence.   + */ +#ifndef OS_ATTR_HILITE +# define OS_ATTR_HILITE  OS_ATTR_BOLD +#endif + +/* HTML <em> attribute - by default, map this to bold-face */ +#ifndef OS_ATTR_EM +# define OS_ATTR_EM      OS_ATTR_BOLD +#endif + +/* HTML <strong> attribute - by default, this has no effect */ +#ifndef OS_ATTR_STRONG +# define OS_ATTR_STRONG  0 +#endif + + +/* ------------------------------------------------------------------------ */ +/* + *   Colors. + *    + *   There are two ways of encoding a color.  First, a specific color can be + *   specified as an RGB (red-green-blue) value, with discreet levels for + *   each component's intensity, ranging from 0 to 255.  Second, a color can + *   be "parameterized," which doesn't specify a color in absolute terms but + *   rather specified one of a number of pre-defined *types* of colors; + *   these pre-defined types can be chosen by the OS implementation, or, on + *   some systems, selected by the user via a preferences mechanism. + *    + *   The os_color_t type encodes a color in 32 bits.  The high-order 8 bits + *   of a color value give the parameterized color identifier, or are set to + *   zero to indicate an RGB color.  An RGB color is encoded in the + *   low-order 24 bits, via the following formula: + *    + *   (R << 16) + (G << 8) + B + *    + *   R specifies the intensity of the red component of the color, G green, + *   and B blue.  Each of R, G, and B must be in the range 0-255.   + */ +typedef unsigned long os_color_t; + +/* encode an R, G, B triplet into an os_color_t value */ +#define os_rgb_color(r, g, b) (((r) << 16) + ((g) << 8) + (b)) + +/*  + *   Determine if a color is given as an RGB value or as a parameterized + *   color value.  Returns true if the color is given as a parameterized + *   color (one of the OS_COLOR_xxx values), false if it's given as an + *   absolute RGB value.   + */ +#define os_color_is_param(color) (((color) & 0xFF000000) != 0) + +/* get the red/green/blue components of an os_color_t value */ +#define os_color_get_r(color) ((int)(((color) >> 16) & 0xFF)) +#define os_color_get_g(color) ((int)(((color) >> 8) & 0xFF)) +#define os_color_get_b(color) ((int)((color) & 0xFF)) + +/* + *   Parameterized color codes.  These are os_color_t values that indicate + *   colors by type, rather than by absolute RGB values.   + */ + +/*  + *   "transparent" - applicable to backgrounds only, this specifies that the + *   current screen background color should be used  + */ +#define OS_COLOR_P_TRANSPARENT ((os_color_t)0x01000000) + +/* "normal text" color (as set via user preferences, if applicable) */ +#define OS_COLOR_P_TEXT        ((os_color_t)0x02000000) + +/* normal text background color (from user preferences) */ +#define OS_COLOR_P_TEXTBG      ((os_color_t)0x03000000) + +/* "status line" text color (as set via user preferences, if applicable) */ +#define OS_COLOR_P_STATUSLINE  ((os_color_t)0x04000000) + +/* status line background color (from user preferences) */ +#define OS_COLOR_P_STATUSBG    ((os_color_t)0x05000000) + +/* input text color (as set via user preferences, if applicable) */ +#define OS_COLOR_P_INPUT       ((os_color_t)0x06000000) + +/* + *   Set the text foreground and background colors.  This sets the text + *   color for subsequent os_printf() and os_vprintf() calls. + *    + *   The background color can be OS_COLOR_TRANSPARENT, in which case the + *   background color is "inherited" from the current screen background. + *   Note that if the platform is capable of keeping old text for + *   "scrollback," then the transparency should be a permanent attribute of + *   the character - in other words, it should not be mapped to the current + *   screen color in the scrollback buffer, because doing so would keep the + *   current screen color even if the screen color changes in the future.  + *    + *   Text color support is optional.  If the platform doesn't support text + *   colors, this can simply do nothing.  If the platform supports text + *   colors, but the requested color or attributes cannot be displayed, the + *   implementation should use the best available approximation.   + */ +void os_set_text_color(os_color_t fg, os_color_t bg); + +/* + *   Set the screen background color.  This sets the text color for the + *   background of the screen.  If possible, this should immediately redraw + *   the main text area with this background color.  The color is given as an + *   OS_COLOR_xxx value. + *    + *   If the platform is capable of redisplaying the existing text, then any + *   existing text that was originally displayed with 'transparent' + *   background color should be redisplayed with the new screen background + *   color.  In other words, the 'transparent' background color of previously + *   drawn text should be a permanent attribute of the character - the color + *   should not be mapped on display to the then-current background color, + *   because doing so would lose the transparency and thus retain the old + *   screen color on a screen color change.   + */ +void os_set_screen_color(os_color_t color); + + +/* ------------------------------------------------------------------------ */ +/*  + *   os_plain() - Use plain ascii mode for the display.  If possible and + *   necessary, turn off any text formatting effects, such as cursor + *   positioning, highlighting, or coloring.  If this routine is called, + *   the terminal should be treated as a simple text stream; users might + *   wish to use this mode for things like text-to-speech converters. + *    + *   Purely graphical implementations that cannot offer a textual mode + *   (such as Mac OS or Windows) can ignore this setting. + *    + *   If this routine is to be called, it must be called BEFORE os_init(). + *   The implementation should set a flag so that os_init() will know to + *   set up the terminal for plain text output.   + */ +#ifndef os_plain +/*  + *   some platforms (e.g. Mac OS) define this to be a null macro, so don't + *   define a prototype in those cases  + */ +void os_plain(void); +#endif + +/* + *   Set the game title.  The output layer calls this routine when a game + *   sets its title (via an HTML <title> tag, for example).  If it's + *   convenient to do so, the OS layer can use this string to set a window + *   caption, or whatever else makes sense on each system.  Most + *   character-mode implementations will provide an empty implementation, + *   since there's not usually any standard way to show the current + *   application title on a character-mode display.   + */ +void os_set_title(const char *title); + +/* + *   Show the system-specific MORE prompt, and wait for the user to respond. + *   Before returning, remove the MORE prompt from the screen. + *    + *   This routine is only used and only needs to be implemented when the OS + *   layer takes responsibility for pagination; this will be the case on + *   most systems that use proportionally-spaced (variable-pitch) fonts or + *   variable-sized windows, since on such platforms the OS layer must do + *   most of the formatting work, leaving the standard output layer unable + *   to guess where pagination should occur. + *    + *   If the portable output formatter handles the MORE prompt, which is the + *   usual case for character-mode or terminal-style implementations, this + *   routine is not used and you don't need to provide an implementation. + *   Note that HTML TADS provides an implementation of this routine, because + *   the HTML renderer handles line breaking and thus must handle + *   pagination.   + */ +void os_more_prompt(); + +/* + *   Interpreter Class Configuration. + *    + *   If this is a TEXT-ONLY interpreter: DO NOT define USE_HTML. + *    + *   If this is a MULTIMEDIA (HTML TADS) intepreter: #define USE_HTML + *    + *   (This really should be called something like OS_USE_HTML - the USE_ name + *   is for historical reasons.  This purpose of this macro is to configure + *   the tads 2 VM-level output formatter's line breaking and MORE mode + *   behavior.  In HTML mode, the VM-level formatter knows that it's feeding + *   its output to a page layout engine, so the VM-level output is just a + *   text stream.  In plain-text mode, the VM formatter *is* the page layout + *   engine, so it needs to do all of the word wrapping and MORE prompting + *   itself.  The tads 3 output layer does NOT use this macro for its + *   equivalent configuration, but instead has different .cpp files for the + *   different modes, and you simply link in the one for the configuration + *   you want.)   + */ +/* #define USE_HTML */ + + +/* + *   Enter HTML mode.  This is only used when the run-time is compiled + *   with the USE_HTML flag defined.  This call instructs the renderer + *   that HTML sequences should be parsed; until this call is made, the + *   renderer should not interpret output as HTML.  Non-HTML + *   implementations do not need to define this routine, since the + *   run-time will not call it if USE_HTML is not defined.   + */ +void os_start_html(void); + +/* exit HTML mode */ +void os_end_html(void); + +/* + *   Global variables with the height and width (in character cells - rows + *   and columns) of the main text display area into which os_printf + *   displays.  The height and width are given in text lines and character + *   columns, respectively.  The portable code can use these values to + *   format text for display via os_printf(); for example, the caller can + *   use the width to determine where to put line breaks. + *    + *   These values are only needed for systems where os_printf() doesn't + *   perform its own word-wrap formatting.  On systems such as the Mac, + *   where os_printf() performs word wrapping, these sizes aren't really + *   important because the portable code doesn't need to perform any real + *   formatting. + *    + *   These variables reflect the size of the "main text area," which is the + *   area of the screen excluding the status line and any "banner" windows + *   (as created with the os_banner_xxx() interfaces). + *    + *   The OS code must initialize these variables during start-up, and must + *   adjust them whenever the display size is changed by user action or + *   other external events (for example, if we're running inside a terminal + *   window, and the user resizes the window, the OS code must recalculate + *   the layout and adjust these accordingly).   + */ +extern int G_os_pagelength; +extern int G_os_linewidth; + +/* + *   Global flag that tells the output formatter whether to count lines + *   that it's displaying against the total on the screen so far.  If this + *   variable is true, lines are counted, and the screen is paused with a + *   [More] message when it's full.  When not in MORE mode, lines aren't + *   counted.  This variable should be set to false when displaying text + *   that doesn't count against the current page, such as status line + *   information. + *    + *   This flag should not be modified by OS code.  Instead, the output + *   formatter will set this flag according to its current state; the OS + *   code can use this flag to determine whether or not to display a MORE + *   prompt during os_printf()-type operations.  Note that this flag is + *   normally interesting to the OS code only when the OS code itself is + *   handling the MORE prompt.   + */ +extern int G_os_moremode; + +/* + *   Global buffer containing the name of the byte-code file (the "game + *   file") loaded into the VM.  This is used only where applicable, which + *   generally means in TADS Interpreter builds.  In other application + *   builds, this is simply left empty.  The application is responsible for + *   setting this during start-up (or wherever else the byte-code filename + *   becomes known or changes).   + */ +extern char G_os_gamename[OSFNMAX]; + +/* + *   Set non-stop mode.  This tells the OS layer that it should disable any + *   MORE prompting it would normally do. + *    + *   This routine is needed only when the OS layer handles MORE prompting; on + *   character-mode platforms, where the prompting is handled in the portable + *   console layer, this can be a dummy implementation.   + */ +void os_nonstop_mode(int flag); + +/*  + *   Update progress display with current info, if appropriate.  This can + *   be used to provide a status display during compilation.  Most + *   command-line implementations will just ignore this notification; this + *   can be used for GUI compiler implementations to provide regular + *   display updates during compilation to show the progress so far.   + */ +/* void os_progress(const char *fname, unsigned long linenum); */ + +/*  + *   Set busy cursor.  If 'flag' is true, provide a visual representation + *   that the system or application is busy doing work.  If 'flag' is + *   false, remove any visual "busy" indication and show normal status. + *    + *   We provide a prototype here if your osxxx.h header file does not + *   #define a macro for os_csr_busy.  On many systems, this function has + *   no effect at all, so the osxxx.h header file simply #define's it to + *   do an empty macro.   + */ +#ifndef os_csr_busy +void os_csr_busy(int flag); +#endif + + +/* ------------------------------------------------------------------------ */ +/* + *   User Input Routines + */ + +/* + *   Ask the user for a filename, using a system-dependent dialog or other + *   mechanism.  Returns one of the OS_AFE_xxx status codes (see below). + *    + *   prompt_type is the type of prompt to provide -- this is one of the + *   OS_AFP_xxx codes (see below).  The OS implementation doesn't need to + *   pay any attention to this parameter, but it can be used if desired to + *   determine the type of dialog to present if the system provides + *   different types of dialogs for different types of operations. + *    + *   file_type is one of the OSFTxxx codes for system file type.  The OS + *   implementation is free to ignore this information, but can use it to + *   filter the list of files displayed if desired; this can also be used + *   to apply a default suffix on systems that use suffixes to indicate + *   file type.  If OSFTUNK is specified, it means that no filtering + *   should be performed, and no default suffix should be applied.   + */ +int os_askfile(const char *prompt, char *fname_buf, int fname_buf_len, +               int prompt_type, os_filetype_t file_type); + +/*  + *   os_askfile status codes  + */ + +/* success */ +#define OS_AFE_SUCCESS  0  + +/*  + *   Generic failure - this is largely provided for compatibility with + *   past versions, in which only zero and non-zero error codes were + *   meaningful; since TRUE is defined as 1 on most platforms, we assume + *   that 1 is probably the generic non-zero error code that most OS + *   implementations have traditionally used.  In addition, this can be + *   used to indicate any other error for which there is no more specific + *   error code.   + */ +#define OS_AFE_FAILURE  1 + +/* user cancelled */ +#define OS_AFE_CANCEL   2 + +/*  + *   os_askfile prompt types + *    + *   Important note: do not change these values when porting TADS.  These + *   values can be used by games, so they must be the same on all + *   platforms.   + */ +#define OS_AFP_OPEN    1     /* choose an existing file to open for reading */ +#define OS_AFP_SAVE    2          /* choose a filename for saving to a file */ + + +/*  + *   Read a string of input.  Fills in the buffer with a null-terminated + *   string containing a line of text read from the standard input.  The + *   returned string should NOT contain a trailing newline sequence.  On + *   success, returns 'buf'; on failure, including end of file, returns a + *   null pointer.   + */ +unsigned char *os_gets(unsigned char *buf, size_t bufl); + +/* + *   Read a string of input with an optional timeout.  This behaves like + *   os_gets(), in that it allows the user to edit a line of text (ideally + *   using the same editing keys that os_gets() does), showing the line of + *   text under construction during editing.  This routine differs from + *   os_gets() in that it returns if the given timeout interval expires + *   before the user presses Return (or the local equivalent). + *    + *   If the user presses Return before the timeout expires, we store the + *   command line in the given buffer, just as os_gets() would, and we return + *   OS_EVT_LINE.  We also update the display in the same manner that + *   os_gets() would, by moving the cursor to a new line and scrolling the + *   displayed text as needed. + *    + *   If a timeout occurs before the user presses Return, we store the command + *   line so far in the given buffer, statically store the cursor position, + *   insert mode, buffer text, and anything else relevant to the editing + *   state, and we return OS_EVT_TIMEOUT. + *    + *   If the implementation does not support the timeout operation, this + *   routine should simply return OS_EVT_NOTIMEOUT immediately when called; + *   the routine should not allow the user to perform any editing if the + *   timeout is not supported.  Callers must use the ordinary os_gets() + *   routine, which has no timeout capabilities, if the timeout is not + *   supported. + *    + *   When we return OS_EVT_TIMEOUT, the caller is responsible for doing one + *   of two things. + *    + *   The first possibility is that the caller performs some work that doesn't + *   require any display operations (in other words, the caller doesn't + *   invoke os_printf, os_getc, or anything else that would update the + *   display), and then calls os_gets_timeout() again.  In this case, we will + *   use the editing state that we statically stored before we returned + *   OS_EVT_TIMEOUT to continue editing where we left off.  This allows the + *   caller to perform some computation in the middle of user command editing + *   without interrupting the user - the extra computation is transparent to + *   the user, because we act as though we were still in the midst of the + *   original editing. + *    + *   The second possibility is that the caller wants to update the display. + *   In this case, the caller must call os_gets_cancel() BEFORE making any + *   display changes.  Then, the caller must do any post-input work of its + *   own, such as updating the display mode (for example, closing HTML font + *   tags that were opened at the start of the input).  The caller is now + *   free to do any display work it wants. + *    + *   If we have information stored from a previous call that was interrupted + *   by a timeout, and os_gets_cancel(TRUE) was never called, we will resume + *   editing where we left off when the cancelled call returned; this means + *   that we'll restore the cursor position, insertion state, and anything + *   else relevant.  Note that if os_gets_cancel(FALSE) was called, we must + *   re-display the command line under construction, but if os_gets_cancel() + *   was never called, we will not have to make any changes to the display at + *   all. + *    + *   Note that when resuming an interrupted editing session (interrupted via + *   os_gets_cancel()), the caller must re-display the prompt prior to + *   invoking this routine. + *    + *   Note that we can return OS_EVT_EOF in addition to the other codes + *   mentioned above.  OS_EVT_EOF indicates that an error occurred reading, + *   which usually indicates that the application is being terminated or that + *   some hardware error occurred reading the keyboard.   + *    + *   If 'use_timeout' is false, the timeout should be ignored.  Without a + *   timeout, the function behaves the same as os_gets(), except that it will + *   resume editing of a previously-interrupted command line if appropriate. + *   (This difference is why the timeout is optional: a caller might not need + *   a timeout, but might still want to resume a previous input that did time + *   out, in which case the caller would invoke this routine with + *   use_timeout==FALSE.  The regular os_gets() would not satisfy this need, + *   because it cannot resume an interrupted input.) + *    + *   Note that a zero timeout has the same meaning as for os_get_event(): if + *   input is available IMMEDIATELY, return the input, otherwise return + *   immediately with the OS_EVT_TIMEOUT result code.   + */ +int os_gets_timeout(unsigned char *buf, size_t bufl, +                    unsigned long timeout_in_milliseconds, int use_timeout); + +/* + *   Cancel an interrupted editing session.  This MUST be called if any + *   output is to be displayed after a call to os_gets_timeout() returns + *   OS_EVT_TIMEOUT. + *    + *   'reset' indicates whether or not we will forget the input state saved + *   by os_gets_timeout() when it last returned.  If 'reset' is true, we'll + *   clear the input state, so that the next call to os_gets_timeout() will + *   start with an empty input buffer.  If 'reset' is false, we will retain + *   the previous input state, if any; this means that the next call to + *   os_gets_timeout() will re-display the same input buffer that was under + *   construction when it last returned. + *    + *   This routine need not be called if os_gets_timeout() is to be called + *   again with no other output operations between the previous + *   os_gets_timeout() call and the next one. + *    + *   Note that this routine needs only a trivial implementation when + *   os_gets_timeout() is not supported (i.e., the function always returns + *   OS_EVT_NOTIMEOUT).   + */ +void os_gets_cancel(int reset); + +/*  + *   Read a character from the keyboard.  For extended keystrokes, this + *   function returns zero, and then returns the CMD_xxx code for the + *   extended keystroke on the next call.  For example, if the user presses + *   the up-arrow key, the first call to os_getc() should return 0, and the + *   next call should return CMD_UP.  Refer to the CMD_xxx codes below. + *    + *   os_getc() should return a high-level, translated command code for + *   command editing.  This means that, where a functional interpretation of + *   a key and the raw key-cap interpretation both exist as CMD_xxx codes, + *   the functional interpretation should be returned.  For example, on Unix, + *   Ctrl-E is conventionally used in command editing to move to the end of + *   the line, following Emacs key bindings.  Hence, os_getc() should return + *   CMD_END for this keystroke, rather than a single 0x05 character (ASCII + *   Ctrl-E), because CMD_END is the high-level command code for the + *   operation. + *    + *   The translation ability of this function allows for system-dependent key + *   mappings to functional meanings.   + */ +int os_getc(void); + +/* + *   Read a character from the keyboard, following the same protocol as + *   os_getc() for CMD_xxx codes (i.e., when an extended keystroke is + *   encountered, os_getc_raw() returns zero, then returns the CMD_xxx code + *   on the subsequent call). + *    + *   This function differs from os_getc() in that this function returns the + *   low-level, untranslated key code whenever possible.  This means that, + *   when a functional interpretation of a key and the raw key-cap + *   interpretation both exist as CMD_xxx codes, this function returns the + *   key-cap interpretation.  For the Unix Ctrl-E example in the comments + *   describing os_getc() above, this function should return 5 (the ASCII + *   code for Ctrl-E), because the ASCII control character interpretation is + *   the low-level key code. + *    + *   This function should return all control keys using their ASCII control + *   codes, whenever possible.  Similarly, this function should return ASCII + *   27 for the Escape key, if possible.   + *    + *   For keys for which there is no portable ASCII representation, this + *   should return the CMD_xxx sequence.  So, this function acts exactly the + *   same as os_getc() for arrow keys, function keys, and other special keys + *   that have no ASCII representation.  This function returns a + *   non-translated version ONLY when an ASCII representation exists - in + *   practice, this means that this function and os_getc() vary only for CTRL + *   keys and Escape.   + */ +int os_getc_raw(void); + + +/* wait for a character to become available from the keyboard */ +void os_waitc(void); + +/* + *   Constants for os_getc() when returning commands.  When used for + *   command line editing, special keys (arrows, END, etc.)  should cause + *   os_getc() to return 0, and return the appropriate CMD_ value on the + *   NEXT call.  Hence, os_getc() must keep the appropriate information + *   around statically for the next call when a command key is issued. + *    + *   The comments indicate which CMD_xxx codes are "translated" codes and + *   which are "raw"; the difference is that, when a particular keystroke + *   could be interpreted as two different CMD_xxx codes, one translated + *   and the other raw, os_getc() should always return the translated + *   version of the key, and os_getc_raw() should return the raw version. + */ +#define CMD_UP    1                        /* move up/up arrow (translated) */ +#define CMD_DOWN  2                    /* move down/down arrow (translated) */ +#define CMD_RIGHT 3                  /* move right/right arrow (translated) */ +#define CMD_LEFT  4                    /* move left/left arrow (translated) */ +#define CMD_END   5              /* move cursor to end of line (translated) */ +#define CMD_HOME  6            /* move cursor to start of line (translated) */ +#define CMD_DEOL  7                   /* delete to end of line (translated) */ +#define CMD_KILL  8                      /* delete entire line (translated) */ +#define CMD_DEL   9                /* delete current character (translated) */ +#define CMD_SCR   10                 /* toggle scrollback mode (translated) */ +#define CMD_PGUP  11                                /* page up (translated) */ +#define CMD_PGDN  12                              /* page down (translated) */ +#define CMD_TOP   13                            /* top of file (translated) */ +#define CMD_BOT   14                         /* bottom of file (translated) */ +#define CMD_F1    15                               /* function key F1 (raw) */ +#define CMD_F2    16                               /* function key F2 (raw) */ +#define CMD_F3    17                               /* function key F3 (raw) */ +#define CMD_F4    18                               /* function key F4 (raw) */ +#define CMD_F5    19                               /* function key F5 (raw) */ +#define CMD_F6    20                               /* function key F6 (raw) */ +#define CMD_F7    21                               /* function key F7 (raw) */ +#define CMD_F8    22                               /* function key F8 (raw) */ +#define CMD_F9    23                               /* function key F9 (raw) */ +#define CMD_F10   24                              /* function key F10 (raw) */ +#define CMD_CHOME 25                                  /* control-home (raw) */ +#define CMD_TAB   26                                    /* tab (translated) */ +#define CMD_SF2   27                                      /* shift-F2 (raw) */ +/* not used (obsolete) - 28 */ +#define CMD_WORD_LEFT  29      /* word left (ctrl-left on dos) (translated) */ +#define CMD_WORD_RIGHT 30    /* word right (ctrl-right on dos) (translated) */ +#define CMD_WORDKILL 31                   /* delete word right (translated) */ +#define CMD_EOF   32                                   /* end-of-file (raw) */ +#define CMD_BREAK 33     /* break (Ctrl-C or local equivalent) (translated) */ +#define CMD_INS   34                                    /* insert key (raw) */ + + +/* + *   ALT-keys - add alphabetical code to CMD_ALT: ALT-A == CMD_ALT + 0, + *   ALT-B == CMD_ALT + 1, ALT-C == CMD_ALT + 2, etc + *    + *   These keys are all raw (untranslated).   + */ +#define CMD_ALT   128                                  /* start of ALT keys */ + + +/* ------------------------------------------------------------------------ */ +/* + *   Event information structure for os_get_event.  The appropriate union + *   member should be filled in, depending on the type of event that + *   occurs.  + */ +union os_event_info_t +{ +    /*  +     *   OS_EVT_KEY - this returns the one or two characters of the +     *   keystroke.  If the key is an extended key, so that os_getc() would +     *   return a two-character sequence for the keystroke, the first +     *   character should be zero and the second the extended key code. +     *   Otherwise, the first character should simply be the ASCII key code. +     *    +     *   The key code here is the "raw" keycode, equivalent to the codes +     *   returned by os_getc_raw().  Note in particular that this means that +     *   CTRL and Escape keys are presented as one-byte ASCII control +     *   characters, not as two-byte CMD_xxx sequences.   +     *    +     *   For multi-byte character sets (Shift-JIS, for example), note that +     *   os_get_event() must NOT return a complete two-byte character here. +     *   The two bytes here are exclusively used to represent special +     *   CMD_xxx keys (such as arrow keys and function keys).  For a +     *   keystroke that is represented in a multi-byte character set using +     *   more than one byte, os_get_event() must return a series of events. +     *   Return an ordinary OS_EVT_KEY for the first byte of the sequence, +     *   putting the byte in key[0]; then, return the second byte as a +     *   separate OS_EVT_KEY as the next event; and so on for any additional +     *   bytes.  This will allow callers that are not multibyte-aware to +     *   treat multi-byte characters as though they were sequences of +     *   one-byte characters.   +     */ +    int key[2]; + +    /* +     *   OS_EVT_HREF - this returns the text of the HREF as a +     *   null-terminated string.   +     */ +    char href[256]; + +    /* command ID (for OS_EVT_COMMAND) */ +    int cmd_id; +}; +typedef union os_event_info_t os_event_info_t; + +/* + *   Event types for os_get_event  + */ + +/* invalid/no event */ +#define OS_EVT_NONE      0x0000 + +/* OS_EVT_KEY - user typed a key on the keyboard */ +#define OS_EVT_KEY       0x0001 + +/* OS_EVT_TIMEOUT - no event occurred before the timeout elapsed */ +#define OS_EVT_TIMEOUT   0x0002 + +/*  + *   OS_EVT_HREF - user clicked on a <A HREF> link.  This only applies to + *   the HTML-enabled run-time.  + */ +#define OS_EVT_HREF      0x0003 + +/*  + *   OS_EVT_NOTIMEOUT - caller requested a timeout, but timeout is not + *   supported by this version of the run-time  + */ +#define OS_EVT_NOTIMEOUT 0x0004 + +/* + *   OS_EVT_EOF - an error occurred reading the event.  This generally + *   means that the application is quitting or we can no longer read from + *   the keyboard or terminal.  + */ +#define OS_EVT_EOF       0x0005 + +/*  + *   OS_EVT_LINE - user entered a line of text on the keyboard.  This event + *   is not returned from os_get_event(), but rather from os_gets_timeout(). + */ +#define OS_EVT_LINE      0x0006 + + +/* + *   Get an input event.  The event types are shown above.  If use_timeout is + *   false, this routine should simply wait until one of the events it + *   recognizes occurs, then return the appropriate information on the event. + *   If use_timeout is true, this routine should return OS_EVT_TIMEOUT after + *   the given number of milliseconds elapses if no event occurs first. + *    + *   This function is not obligated to obey the timeout.  If a timeout is + *   specified and it is not possible to obey the timeout, the function + *   should simply return OS_EVT_NOTIMEOUT.  The trivial implementation thus + *   checks for a timeout, returns an error if specified, and otherwise + *   simply waits for the user to press a key. + *    + *   A timeout value of 0 does *not* mean that there's no timeout (i.e., it + *   doesn't mean we should wait indefinitely) - that's specified by passing + *   FALSE for use_timeout.  A zero timeout also doesn't meant that the + *   function should unconditionally return OS_EVT_TIMEOUT.  Instead, a zero + *   timeout specifically means that IF an event is available IMMEDIATELY, + *   without blocking the thread, we should return that event; otherwise we + *   should immediately return a timeout event.   + */ +int os_get_event(unsigned long timeout_in_milliseconds, int use_timeout, +                 os_event_info_t *info); + + +/* ------------------------------------------------------------------------ */ +/* + *   Extended os_get_event() codes. + *    + *   THESE ARE NOT USED in the basic osifc implementation - these are only + *   used if the interpreter supports the "extended" interface defined in + *   osifcext.h.   + */ + +/* + *   System menu command event.  Some interpreters (such as HTML TADS for + *   Windows) provide menu commands for common system-level game verbs - + *   SAVE, RESTORE, UNDO, QUIT.  By default, these commands are returned as + *   literal command strings ("save", "restore", etc) via os_gets(), as + *   though the user had typed the commands at the keyboard.  The extended OS + *   interface allows the program to receive these commands via + *   os_get_event().  When a command is enabled for os_get_event() via the + *   extended OS interface, and the player selects the command via a menu (or + *   toolbar button, etc) during a call to os_get_event(), os_get_event() + *   returns this event code, with the menu ID stored in the cmd_id field of + *   the event structure.   + */ +#define OS_EVT_COMMAND   0x0100 + +/* command IDs for OS_EVT_COMMAND */ +#define OS_CMD_NONE      0x0000     /* invalid command ID, for internal use */ +#define OS_CMD_SAVE      0x0001                                /* save game */ +#define OS_CMD_RESTORE   0x0002                             /* restore game */ +#define OS_CMD_UNDO      0x0003                           /* undo last turn */ +#define OS_CMD_QUIT      0x0004                                /* quit game */ +#define OS_CMD_CLOSE     0x0005                    /* close the game window */ +#define OS_CMD_HELP      0x0006                           /* show game help */ + +/* highest command ID used in this version of the interface */ +#define OS_CMD_LAST      0x0006 + + +/* ------------------------------------------------------------------------ */ +/* + *   Ask for input through a dialog. + *    + *   'prompt' is a text string to display as a prompting message.  For + *   graphical systems, this message should be displayed in the dialog; + *   for text systems, this should be displayed on the terminal after a + *   newline. + *    + *   'standard_button_set' is one of the OS_INDLG_xxx values defined + *   below, or zero.  If this value is zero, no standard button set is to + *   be used; the custom set of buttons defined in 'buttons' is to be used + *   instead.  If this value is non-zero, the appropriate set of standard + *   buttons, with labels translated to the local language if possible, is + *   to be used. + *    + *   'buttons' is an array of strings to use as button labels. + *   'button_count' gives the number of entries in the 'buttons' array. + *   'buttons' and 'button_count' are ignored if 'standard_button_set' is + *   non-zero, since a standard set of buttons is used instead.  If + *   'buttons' and 'button_count' are to be used, each entry contains the + *   label of a button to show.   + */ +/*    + *   An ampersand ('&') character in a label string indicates that the + *   next character after the '&' is to be used as the short-cut key for + *   the button, if supported.  The '&' should NOT be displayed in the + *   string; instead, the character should be highlighted according to + *   local system conventions.  On Windows, for example, the short-cut + *   character should be shown underlined; on a text display, the response + *   might be shown with the short-cut character enclosed in parentheses. + *   If there is no local convention for displaying a short-cut character, + *   then the '&' should simply be removed from the displayed text.   + *    + *   The short-cut key specified by each '&' character should be used in + *   processing responses.  If the user presses the key corresponding to a + *   button's short-cut, the effect should be the same as if the user + *   clicked the button with the mouse.  If local system conventions don't + *   allow for short-cut keys, any short-cut keys can be ignored. + *    + *   'default_index' is the 1-based index of the button to use as the + *   default.  If this value is zero, there is no default response.  If + *   the user performs the appropriate system-specific action to select + *   the default response for the dialog, this is the response that is to + *   be selected.  On Windows, for example, pressing the "Return" key + *   should select this item. + *    + *   'cancel_index' is the 1-based index of the button to use as the + *   cancel response.  If this value is zero, there is no cancel response. + *   This is the response to be used if the user cancels the dialog using + *   the appropriate system-specific action.  On Windows, for example, + *   pressing the "Escape" key should select this item.   + */ +/* + *   icon_id is one of the OS_INDLG_ICON_xxx values defined below.  If + *   possible, an appropriate icon should be displayed in the dialog. + *   This can be ignored in text mode, and also in GUI mode if there is no + *   appropriate system icon. + *    + *   The return value is the 1-based index of the response selected.  If + *   an error occurs, return 0.   + */ +int os_input_dialog(int icon_id, const char *prompt, int standard_button_set, +                    const char **buttons, int button_count, +                    int default_index, int cancel_index); + +/* + *   Standard button set ID's  + */ + +/* OK */ +#define OS_INDLG_OK            1 + +/* OK, Cancel */ +#define OS_INDLG_OKCANCEL      2 + +/* Yes, No */ +#define OS_INDLG_YESNO         3 + +/* Yes, No, Cancel */ +#define OS_INDLG_YESNOCANCEL   4 + +/* + *   Dialog icons  + */ + +/* no icon */ +#define OS_INDLG_ICON_NONE     0 + +/* warning */ +#define OS_INDLG_ICON_WARNING  1 + +/* information */ +#define OS_INDLG_ICON_INFO     2 + +/* question */ +#define OS_INDLG_ICON_QUESTION 3 + +/* error */ +#define OS_INDLG_ICON_ERROR    4 + + + +/* ------------------------------------------------------------------------ */ +/*  + *   OS main entrypoint  + */ +int os0main(int oargc, char **oargv, +            int (*mainfn)(int, char **, char *),  +            const char *before, const char *config); + +/*  + *   new-style OS main entrypoint - takes an application container context  + */ +int os0main2(int oargc, char **oargv, +             int (*mainfn)(int, char **, struct appctxdef *, char *), +             const char *before, const char *config, +             struct appctxdef *appctx); + +/* + *   OBSOLETE - Get filename from startup parameter, if possible; returns + *   true and fills in the buffer with the parameter filename on success, + *   false if no parameter file could be found. + *    + *   (This was used until TADS 2.2.5 for the benefit of the Mac interpreter, + *   and interpreters on systems with similar desktop shells, to allow the + *   user to launch the terp by double-clicking on a saved game file.  The + *   terp would read the launch parameters, discover that a saved game had + *   been used to invoke it, and would then stash away the saved game info + *   for later retrieval from this function.  This functionality was replaced + *   in 2.2.5 with a command-line parameter: the terp now uses the desktop + *   launch data to synthesize a suitable argv[] vectro to pass to os0main() + *   or os0main2().  This function should now simply be stubbed out - it + *   should simply return FALSE.)   + */ +int os_paramfile(char *buf); + +/*  + *   Initialize.  This should be called during program startup to + *   initialize the OS layer and check OS-specific command-line arguments. + *    + *   If 'prompt' and 'buf' are non-null, and there are no arguments on the + *   given command line, the OS code can use the prompt to ask the user to + *   supply a filename, then store the filename in 'buf' and set up + *   argc/argv to give a one-argument command string.  (This mechanism for + *   prompting for a filename is obsolescent, and is retained for + *   compatibility with a small number of existing implementations only; + *   new implementations should ignore this mechanism and leave the + *   argc/argv values unchanged.)   + */ +int os_init(int *argc, char *argv[], const char *prompt, +            char *buf, int bufsiz); + +/* + *   Termination functions.  There are three main termination functions, + *   described individually below; here's a brief overview of the + *   relationship among the functions.  The important thing to realize is + *   that these functions have completely independent purposes; they should + *   never call one another, and they should never do any of the work that's + *   intended for the others. + *    + *   os_uninit() is meant to undo the effects of os_init().  On many + *   systems, os_init() has some global effect, such as setting the terminal + *   to some special input or output mode.  os_uninit's purpose is to undo + *   these global effects, returning the terminal mode (and whatever else) + *   to the conditions they were in at program startup.  Portable callers + *   are meant to call this routine at some point before terminating if they + *   ever called os_init().  Note that this routine DOES NOT terminate the + *   program - it should simply undo anything that os_init() did and return, + *   to let the caller do any additional termination work of its own. + *    + *   os_expause() optionally pauses before termination, to allow the user to + *   acknowledge any text the program displays just before exiting.  This + *   doesn't have to do anything at all, but it's useful on systems where + *   program termination will do something drastic like close the window: + *   without a pause, the user wouldn't have a chance to read any text the + *   program displayed after the last interactive input, because the window + *   would abruptly disappear moments after the text was displayed.  For + *   systems where termination doesn't have such drastic effects, there's no + *   need to do anything in this routine.  Because it's up to this routine + *   whether or not to pause, this routine must display a prompt if it's + *   going to pause for user input - the portable caller obviously can't do + *   so, because the caller doesn't know if the routine is going to pause or + *   not (so if the caller displayed a prompt assuming the routine would + *   pause, the prompt would show up even on systems where there actually is + *   no pause, which would be confusing).  This routine DOES NOT terminate + *   the program; it simply pauses if necessary to allow the user to + *   acknowledge the last bit of text the program displayed, then returns to + *   allow the caller to carry on with its own termination work. + *    + *   os_term() is meant to perform the same function as the C standard + *   library routine exit(): this actually terminates the program, exiting + *   to the operating system.  This routine is not meant to return to its + *   caller, because it's supposed to exit the program directly.  For many + *   systems, this routine can simply call exit(); the portable code calls + *   this routine instead of calling exit() directly, because some systems + *   have their own OS-specific way of terminating rather than using exit(). + *   This routine MUST NOT undo the effects of os_init(), because callers + *   might not have ever called os_init(); callers are required to call + *   os_uninit() if they ever called os_init(), before calling os_term(), so + *   this routine can simply assume that any global modes set by os_init() + *   have already been undone by the time this is called.   + */ + +/* + *   Uninitialize.  This is called prior to progam termination to reverse + *   the effect of any changes made in os_init().  For example, if + *   os_init() put the terminal in raw mode, this should restore the + *   previous terminal mode.  This routine should not terminate the + *   program (so don't call exit() here) - the caller might have more + *   processing to perform after this routine returns.   + */ +void os_uninit(void); + +/*  + *   Pause prior to exit, if desired.  This is meant to be called by + *   portable code just before the program is to be terminated; it can be + *   implemented to show a prompt and wait for user acknowledgment before + *   proceeding.  This is useful for implementations that are using + *   something like a character-mode terminal window running on a graphical + *   operating system: this gives the implementation a chance to pause + *   before exiting, so that the window doesn't just disappear + *   unceremoniously. + *    + *   This is allowed to do nothing at all.  For regular character-mode + *   systems, this routine usually doesn't do anything, because when the + *   program exits, the terminal will simply return to the OS command + *   prompt; none of the text displayed just before the program exited will + *   be lost, so there's no need for any interactive pause.  Likewise, for + *   graphical systems where the window will remain open, even after the + *   program exits, until the user explicitly closes the window, there's no + *   need to do anything here. + *    + *   If this is implemented to pause, then this routine MUST show some kind + *   of prompt to let the user know we're waiting.  In the simple case of a + *   text-mode terminal window on a graphical OS, this should simply print + *   out some prompt text ("Press a key to exit...") and then wait for the + *   user to acknowledge the prompt (by pressing a key, for example).  For + *   graphical systems, the prompt could be placed in the window's title + *   bar, or status-bar, or wherever is appropriate for the OS.   + */ +void os_expause(void); + +/*  + *   Terminate.  This should exit the program with the given exit status. + *   In general, this should be equivalent to the standard C library + *   exit() function, but we define this interface to allow the OS code to + *   do any necessary pre-termination cleanup.   + */ +void os_term(int status); + +/*  + *   Install/uninstall the break handler.  If possible, the OS code should + *   set (if 'install' is true) or clear (if 'install' is false) a signal + *   handler for keyboard break signals (control-C, etc, depending on + *   local convention).  The OS code should set its own handler routine, + *   which should note that a break occurred with an internal flag; the + *   portable code uses os_break() from time to time to poll this flag.   + */ +void os_instbrk(int install); + +/* + *   Check for user break ("control-C", etc) - returns true if a break is + *   pending, false if not.  If this returns true, it should "consume" the + *   pending break (probably by simply clearing the OS code's internal + *   break-pending flag).   + */ +int os_break(void); + +/* + *   Sleep for a given interval.  This should simply pause for the given + *   number of milliseconds, then return.  On multi-tasking systems, this + *   should use a system API to suspend the current process for the desired + *   delay; on single-tasking systems, this can simply sit in a wait loop + *   until the desired interval has elapsed.   + */ +void os_sleep_ms(long delay_in_milliseconds); + +/* + *   Yield CPU; returns TRUE if user requested an interrupt (a "control-C" + *   type of operation to abort the entire program), FALSE to continue. + *   Portable code should call this routine from time to time during lengthy + *   computations that don't involve any UI operations; if practical, this + *   routine should be invoked roughly every 10 to 100 milliseconds. + *    + *   The purpose of this routine is to support "cooperative multitasking" + *   systems, such as pre-X MacOS, where it's necessary for each running + *   program to call the operating system explicitly in order to yield the + *   CPU from time to time to other concurrently running programs.  On + *   cooperative multitasking systems, a program can only lose control of + *   the CPU by making specific system calls, usually related to GUI events; + *   a program can never lose control of the CPU asynchronously.  So, a + *   program that performs lengthy computations without any UI interaction + *   can cause the rest of the system to freeze up until the computations + *   are finished; but if a compute-intensive program explicitly yields the + *   CPU from time to time, it allows other programs to remain responsive. + *   Yielding the CPU at least every 100 milliseconds or so will generally + *   allow the UI to remain responsive; yielding more frequently than every + *   10 ms or so will probably start adding noticeable overhead. + *    + *   On single-tasking systems (such as MS-DOS), there's only one program + *   running at a time, so there's no need to yield the CPU; on virtually + *   every modern system, the OS automatically schedules CPU time without + *   the running programs having any say in the matter, so again there's no + *   need for a program to yield the CPU.  For systems where this routine + *   isn't needed, the system header should simply #define os_yield to + *   something like "((void)0)" - this will allow the compiler to completely + *   ignore calls to this routine for systems where they aren't needed. + *    + *   Note that this routine is NOT meant to provide scheduling hinting to + *   modern systems with true multitasking, so a trivial implementation is + *   fine for any modern system.   + */ +#ifndef os_yield +int os_yield(void); +#endif + +/* + *   Set the default saved-game extension.  This routine will NOT be called + *   when we're using the standard saved game extension; this routine will be + *   invoked only if we're running as a stand-alone game, and the game author + *   specified a non-standard saved-game extension when creating the + *   stand-alone game. + *    + *   This routine is not required if the system does not use the standard, + *   semi-portable os0.c implementation.  Even if the system uses the + *   standard os0.c implementation, it can provide an empty routine here if + *   the system code doesn't need to do anything special with this + *   information. + *    + *   The extension is specified as a null-terminated string.  The extension + *   does NOT include the leading period.   + */ +void os_set_save_ext(const char *ext); + +/*  + *   Get the saved game extension previously set with os_set_save_ext(). + *   Returns null if no custom extension has been set. + */ +const char *os_get_save_ext(); + + +/* ------------------------------------------------------------------------*/ +/* + *   Translate a character from the HTML 4 Unicode character set to the + *   current character set used for display.  Takes an HTML 4 character + *   code and returns the appropriate local character code. + *    + *   The result buffer should be filled in with a null-terminated string + *   that should be used to represent the character.  Multi-character + *   results are possible, which may be useful for certain approximations + *   (such as using "(c)" for the copyright symbol). + *    + *   Note that we only define this prototype if this symbol isn't already + *   defined as a macro, which may be the case on some platforms. + *   Alternatively, if the function is already defined (for example, as an + *   inline function), the defining code can define OS_XLAT_HTML4_DEFINED, + *   in which case we'll also omit this prototype. + *    + *   Important: this routine provides the *default* mapping that is used + *   when no external character mapping file is present, and for any named + *   entities not defined in the mapping file.  Any entities in the + *   mapping file, if used, will override this routine. + *    + *   A trivial implementation of this routine (that simply returns a + *   one-character result consisting of the original input character, + *   truncated to eight bits if necessary) can be used if you want to + *   require an external mapping file to be used for any game that + *   includes HTML character entities.  The DOS version implements this + *   routine so that games will still look reasonable when played with no + *   mapping file present, but other systems are not required to do this.   + */ +#ifndef os_xlat_html4 +# ifndef OS_XLAT_HTML4_DEFINED +void os_xlat_html4(unsigned int html4_char, +                   char *result, size_t result_buf_len); +# endif +#endif + +/* + *   Generate a filename for a character-set mapping file.  This function + *   should determine the current native character set in use, if + *   possible, then generate a filename, according to system-specific + *   conventions, that we should attempt to load to get a mapping between + *   the current native character set and the internal character set + *   identified by 'internal_id'. + *    + *   The internal character set ID is a string of up to 4 characters. + *    + *   On DOS, the native character set is a DOS code page.  DOS code pages + *   are identified by 3- or 4-digit identifiers; for example, code page + *   437 is the default US ASCII DOS code page.  We generate the + *   character-set mapping filename by appending the internal character + *   set identifier to the DOS code page number, then appending ".TCP" to + *   the result.  So, to map between ISO Latin-1 (internal ID = "La1") and + *   DOS code page 437, we would generate the filename "437La1.TCP". + *    + *   Note that this function should do only two things.  First, determine + *   the current native character set that's in use.  Second, generate a + *   filename based on the current native code page and the internal ID. + *   This function is NOT responsible for figuring out the mapping or + *   anything like that -- it's simply where we generate the correct + *   filename based on local convention. + *    + *   'filename' is a buffer of at least OSFNMAX characters. + *    + *   'argv0' is the executable filename from the original command line. + *   This parameter is provided so that the system code can look for + *   mapping files in the original TADS executables directory, if desired. + */ +void os_gen_charmap_filename(char *filename, char *internal_id, +                             char *argv0); + +/* + *   Receive notification that a character mapping file has been loaded. + *   The caller doesn't require this routine to do anything at all; this + *   is purely for the system-dependent code's use so that it can take + *   care of any initialization that it must do after the caller has + *   loaded a charater mapping file.  'id' is the character set ID, and + *   'ldesc' is the display name of the character set.  'sysinfo' is the + *   extra system information string that is stored in the mapping file; + *   the interpretation of this information is up to this routine. + *    + *   For reference, the Windows version uses the extra information as a + *   code page identifier, and chooses its default font character set to + *   match the code page.  On DOS, the run-time requires the player to + *   activate an appropriate code page using a DOS command (MODE CON CP + *   SELECT) prior to starting the run-time, so this routine doesn't do + *   anything at all on DOS.  + */ +void os_advise_load_charmap(const char *id, const char *ldesc, const char *sysinfo); + +/* + *   Generate the name of the character set mapping table for Unicode + *   characters to and from the given local character set.  Fills in the + *   buffer with the implementation-dependent name of the desired + *   character set map.  See below for the character set ID codes. + *    + *   For example, on Windows, the implementation would obtain the + *   appropriate active code page (which is simply a Windows character set + *   identifier number) from the operating system, and build the name of + *   the Unicode mapping file for that code page, such as "CP1252".  On + *   Macintosh, the implementation would look up the current script system + *   and return the name of the Unicode mapping for that script system, + *   such as "ROMAN" or "CENTEURO". + *    + *   If it is not possible to determine the specific character set that is + *   in use, this function should return "asc7dflt" (ASCII 7-bit default) + *   as the character set identifier on an ASCII system, or an appropriate + *   base character set name on a non-ASCII system.  "asc7dflt" is the + *   generic character set mapping for plain ASCII characters. + *    + *   The given buffer must be at least 32 bytes long; the implementation + *   must limit the result it stores to 32 bytes.  (We use a fixed-size + *   buffer in this interface for simplicity, and because there seems no + *   need for greater flexibility in the interface; a character set name + *   doesn't carry very much information so shouldn't need to be very + *   long.  Note that this function doesn't generate a filename, but + *   simply a mapping name; in practice, a map name will be used to + *   construct a mapping file name.) + *    + *   Because this function obtains the Unicode mapping name, there is no + *   need to specify the internal character set to be used: the internal + *   character set is Unicode.   + */ +/* + *   Implementation note: when porting this routine, the convention that + *   you use to name your mapping files is up to you.  You should simply + *   choose a convention for this implementation, and then use the same + *   convention for packaging the mapping files for your OS release.  In + *   most cases, the best convention is to use the names that the Unicode + *   consortium uses in their published cross-mapping listings, since + *   these listings can be used as the basis of the mapping files that you + *   include with your release.  For example, on Windows, the convention + *   is to use the code page number to construct the map name, as in + *   CP1252 or CP1250.   + */ +void os_get_charmap(char *mapname, int charmap_id); + +/* + *   Character map for the display (i.e., for the user interface).  This + *   is the character set which is used for input read from the keyboard, + *   and for output displayed on the monitor or terminal.   + */ +#define OS_CHARMAP_DISPLAY     1 + +/*  + *   Character map for mapping filename strings.  This should identify the + *   character set currently in use on the local system for filenames + *   (i.e., for strings identifying objects in the local file system), + *   providing a suitable name for use in opening a mapping file. + *    + *   On many platforms, the entire system uses only one character set at + *   the OS level, so the file system character set is the same as the + *   display character set.  Some systems define a particular character + *   set for file system use, though, because different users might be + *   running applications on terminals that display different character + *   sets.   + */ +#define OS_CHARMAP_FILENAME    2 + +/* + *   Default character map for file contents.  On most systems, this will + *   be the same as display.  On some systems, it won't be possible to + *   know in general what character set files use; in fact, this isn't + *   possible anywhere, since a file might have been copied without + *   translation from a different type of computer using a different + *   character set.  So, this isn't meant to provide a reliable choice of + *   character set for any arbitrary file; it's simply meant to be a good + *   guess that most files on this system are likely to use.   + */ +#define OS_CHARMAP_FILECONTENTS  3 + +/* + *   Default character map for the command line.  This is the maping we use + *   to interpret command line arguments passed to our main() or equivalent. + *   On most systems, this will be the same as the display character set. + */ +#define OS_CHARMAP_CMDLINE     4 + + +/* ------------------------------------------------------------------------ */ +/* + *   External Banner Interface.  This interface provides the ability to + *   divide the display window into multiple sub-windows, each with its own + *   independent contents. + *    + *   To determine where a new banner is displayed, we look at the banners as + *   a tree, rooted at the "main window," the special banner that the system + *   automatically creates initially for the main game text.  We start by + *   allocating the entire display (or the entire application window, if + *   we're running on a GUI system) to the main window.  We then traverse + *   the tree, starting with the root window's children.  For each child + *   window, we allocate space for the child out of the parent window's + *   area, according to the child's alignment and size settings, and deduct + *   this space from the parent window's size.  We then lay out the children + *   of the child. + *    + *   For each banner window, we take its requested space out of the parent + *   window's area by starting at the edge of the parent window rectangle as + *   indicated by the banner's alignment, and taking the requested `width + *   (for a left/right banner) or height (for a top/bottom banner), limiting + *   to the available width/height in the parent window's space.  Give the + *   banner the full extent of the parent's space in its other dimension (so + *   a left/right banner gets the full height of the parent space, and a + *   top/bottom banner gets the full width). + *    + *   Note that the layout proceeds exclusively down the tree (i.e., from the + *   root to children to grandchildren, and so on).  It *appears* that a + *   child affects its parent, because of the deduction step: a child + *   acquires screen space by carving out a chunk of its parent.  The right + *   way to think about this, though, is that the parent's full area is the + *   union of the parent window and all of its children; when viewed this + *   way, the parent's full area is fully determined the instant the parent + *   is laid out, and never changes as its children are laid out.  Note in + *   particular that a child can never make a parent larger; the only thing + *   a child can do to a parent is carve out a chunk of the parent for + *   itself, which doesn't affect the boundaries of the union of the parent + *   plus its children. + *    + *   Note also that if the banner has a border, and the implementation + *   actually draws borders, the border must be drawn for the *full* area of + *   the banner, as defined above.  For example, suppose we have two + *   borders: banner A is a child of the main window, is top-aligned, and + *   has a border.  Banner B is a child of banner A, right-aligned, with no + *   border.  Obviously, without considering banner B, banner A's space runs + *   across the entire width of the main window, so its border (at the + *   bottom of its area) runs across the entire width of the main window. + *   Banner B carves out some space from A's right side for itself, so + *   banner A's actual on-screen area runs from the left edge of the main + *   window to banner B's left edge.  However, even though banner A itself + *   no longer runs the full width of the main window, banner A's *full* + *   area - that is, the union of banner A's on-screen area and all of its + *   children's full areas - does still run the entire width of the main + *   window, hence banner A's border must still run the full width of the + *   main window.  The simple way of looking at this is that a banner's + *   border is always to be drawn exactly the same way, regardless of + *   whether or not the banner has children - simply draw the banner as it + *   would be drawn if the banner had no children. + *    + *   Each time a banner is added or removed, we must recalculate the layout + *   of the remaining banners and main text area.  The os_banner_xxx() + *   implementation is responsible for this layout refiguring. + *    + *   The entire external banner window interface is optional, although the + *   functions must at least be defined as dummies to avoid linker errors + *   when building.  If a platform doesn't implement this feature, + *   os_banner_create() should simply return null, and the other routines + *   can do nothing.   + */ + +/*  + *   Create a banner window.  'info' gives the desired parameters for the new + *   banner. + *    + *   Note that certain requested parameter settings might or might not be + *   respected, depending on the capabilities of the platform and user + *   preferences.  os_banner_getinfo() can be used after creation to + *   determine which parameter settings are actually used in the new banner. + *    + *   'parent' gives the parent of this banner; this is the banner handle of + *   another banner window, or null.  If 'parent' is null, then the new + *   banner is a child of the main window, which the system creates + *   automatically at startup and which contains the main input/output + *   transcript.  The new banner's on-screen area is carved out of the + *   parent's space, according to the alignment and size settings of the new + *   window, so this determines how the window is laid out on the screen. + *    + *   'where' is OS_BANNER_FIRST to make the new window the first child of its + *   parent; OS_BANNER_LAST to make it the last child of its parent; + *   OS_BANNER_BEFORE to insert it immediately before the existing banner + *   identified by handle in 'other'; or OS_BANNER_AFTER to insert + *   immediately after 'other'.  When BEFORE or AFTER is used, 'other' must + *   be another child of the same parent; if it is not, the routine should + *   act as though 'where' were given as OS_BANNER_LAST. + *    + *   'other' is a banner handle for an existing banner window.  This is used + *   to specify the relative position among children of the new banner's + *   parent, if 'where' is either OS_BANNER_BEFORE or OS_BANNER_AFTER.  If + *   'where' is OS_BANNER_FIRST or OS_BANNER_LAST, 'other' is ignored. + *    + *   'wintype' is the type of the window.  This is one of the + *   OS_BANNER_TYPE_xxx codes indicating what kind of window is desired. + *    + *   'align' is the banner's alignment, given as an OS_BANNER_ALIGN_xxx + *   value.  Top/bottom banners are horizontal: they run across the full + *   width of the existing main text area.  Left/right banners are vertical: + *   they run down the full height of the existing main text area. + *    + *   'siz' is the requested size of the new banner.  The meaning of 'siz' + *   depends on the value of 'siz_units', which can be OS_BANNER_SIZE_PCT to + *   set the size as a percentage of the REMAINING space, or + *   OS_BANNER_SIZE_ABS to set an absolute size in the "natural" units of the + *   window.  The natural units vary by window type: for text and text grid + *   windows, this is in rows/columns of '0' characters in the default font + *   for the window.  Note that when OS_BANNER_SIZE_ABS is used in a text or + *   text grid window, the OS implementation MUST add the space needed for + *   margins and borders when determining the actual pixel size of the + *   window; in other words, the window should be large enough that it can + *   actually display the given number or rows or columns. + *    + *   The size is interpreted as a width or height according to the window's + *   orientation.  For a TOP or BOTTOM banner, the size is the height; for a + *   LEFT or RIGHT banner, the size is the width.  A banner has only one + *   dimension's size given, since the other dimension's size is determined + *   automatically by the layout rules. + *    + *   Note that the window's size can be changed later using + *   banner_size_to_contents() or banner_set_size(). + *    + *   'style' is a combination of OS_BANNER_STYLE_xxx flags - see below.  The + *   style flags give the REQUESTED style for the banner, which might or + *   might not be respected, depending on the platform's capabilities, user + *   preferences, and other factors.  os_banner_getinfo() can be used to + *   determine which style flags are actually used. + *    + *   Returns the "handle" to the new banner window, which is an opaque value + *   that is used in subsequent os_banner_xxx calls to operate on the window. + *   Returns null if the window cannot be created.  An implementation is not + *   required to support this functionality at all, and can subset it if it + *   does support it (for example, an implementation could support only + *   top/bottom-aligned banners, but not left/right-aligned), so callers must + *   be prepared for this routine to return null.   + */ +void *os_banner_create(void *parent, int where, void *other, int wintype, +                       int align, int siz, int siz_units, +                       unsigned long style); + + +/* + *   insertion positions  + */ +#define OS_BANNER_FIRST   1 +#define OS_BANNER_LAST    2 +#define OS_BANNER_BEFORE  3 +#define OS_BANNER_AFTER   4 + +/* + *   banner types  + */ + +/*  + *   Normal text stream window.  This is a text stream that behaves + *   essentially like the main text window: text is displayed to this + *   through os_banner_disp(), always in a stream-like fashion by adding new + *   text to the end of any exiting text. + *    + *   Systems that use proportional fonts should usually simply use the same + *   font they use by default in the main text window.  However, note that + *   the OS_BANNER_STYLE_TAB_ALIGN style flag might imply that a fixed-pitch + *   font should be used even when proportional fonts are available, because + *   a fixed-pitch font will allow the calling code to rely on using spaces + *   to align text within the window.   + */ +#define OS_BANNER_TYPE_TEXT       1 + +/*  + *   "Text grid" window.  This type of window is similar to an normal text + *   window (OS_BANNER_TYPE_TEXT), but is guaranteed to arrange its text in + *   a regular grid of character cells, all of the same size.  This means + *   that the output position can be moved to an arbitrary point within the + *   window at any time, so the calling program can precisely control the + *   layout of the text in the window. + *    + *   Because the output position can be moved to arbitrary positions in the + *   window, it is possible to overwrite text previously displayed.  When + *   this happens, the old text is completely obliterated by the new text, + *   leaving no trace of the overwritten text. + *    + *   In order to guarantee that character cells are all the same size, this + *   type of window does not allow any text attributes.  The implementation + *   should simply ignore any attempts to change text attributes in this + *   type of window.  However, colors can be used to the same degree they + *   can be used in an ordinary text window. + *    + *   To guarantee the regular spacing of character cells, all + *   implementations must use fixed-pitch fonts for these windows.  This + *   applies even to platforms where proportional fonts are available.   + */ +#define OS_BANNER_TYPE_TEXTGRID   2 + + +/*  + *   banner alignment types  + */ +#define OS_BANNER_ALIGN_TOP       0 +#define OS_BANNER_ALIGN_BOTTOM    1 +#define OS_BANNER_ALIGN_LEFT      2 +#define OS_BANNER_ALIGN_RIGHT     3 + +/* + *   size units  + */ +#define OS_BANNER_SIZE_PCT  1 +#define OS_BANNER_SIZE_ABS  2 + + +/*  + *   banner style flags  + */ + +/*  + *   The banner has a visible border; this indicates that a line is to be + *   drawn to separate the banner from the adjacent window or windows + *   "inside" the banner.  So, a top-aligned banner will have its border + *   drawn along its bottom edge; a left-aligned banner will show a border + *   along its right edge; and so forth. + *    + *   Note that character-mode platforms generally do NOT respect the border + *   style, since doing so takes up too much screen space.   + */ +#define OS_BANNER_STYLE_BORDER     0x00000001 + +/* + *   The banner has a vertical/horizontal scrollbar.  Character-mode + *   platforms generally do not support scrollbars.   + */ +#define OS_BANNER_STYLE_VSCROLL    0x00000002 +#define OS_BANNER_STYLE_HSCROLL    0x00000004 + +/*  + *   Automatically scroll the banner vertically/horizontally whenever new + *   text is displayed in the window.  In other words, whenever + *   os_banner_disp() is called, scroll the window so that the text that the + *   new cursor position after the new text is displayed is visible in the + *   window. + *    + *   Note that this style is independent of the presence of scrollbars. + *   Even if there are no scrollbars, we can still scroll the window's + *   contents programmatically. + *    + *   Implementations can, if desired, keep an internal buffer of the + *   window's contents, so that the contents can be recalled via the + *   scrollbars if the text displayed in the banner exceeds the space + *   available in the banner's window on the screen.  If the implementation + *   does keep such a buffer, we recommend the following method for managing + *   this buffer.  If the AUTO_VSCROLL flag is not set, then the banner's + *   contents should be truncated at the bottom when the contents overflow + *   the buffer; that is, once the banner's internal buffer is full, any new + *   text that the calling program attempts to add to the banner should + *   simply be discarded.  If the AUTO_VSCROLL flag is set, then the OLDEST + *   text should be discarded instead, so that the most recent text is + *   always retained.   + */ +#define OS_BANNER_STYLE_AUTO_VSCROLL 0x00000008 +#define OS_BANNER_STYLE_AUTO_HSCROLL 0x00000010 + +/* + *   Tab-based alignment is required/supported.  On creation, this is a hint + *   to the implementation that is sometimes necessary to determine what + *   kind of font to use in the new window, for non-HTML platforms.  If this + *   flag is set on creation, the caller is indicating that it wants to use + *   <TAB> tags to align text in the window. + *    + *   Character-mode implementations that use a single font with fixed pitch + *   can simply ignore this.  These implementations ALWAYS have a working + *   <TAB> capability, because the portable output formatter provides <TAB> + *   interpretation for a fixed-pitch window. + *    + *   Full HTML TADS implementations can also ignore this.  HTML TADS + *   implementations always have full <TAB> support via the HTML + *   parser/renderer. + *    + *   Text-only implementations on GUI platforms (i.e., implementations that + *   are not based on the HTML parser/renderer engine in HTML TADS, but + *   which run on GUI platforms with proportionally-spaced text) should use + *   this flag to determine the font to display.  If this flag is NOT set, + *   then the caller doesn't care about <TAB>, and the implementation is + *   free to use a proportionally-spaced font in the window if desired. + *    + *   When retrieving information on an existing banner, this flag indicates + *   that <TAB> alignment is actually supported on the window.   + */ +#define OS_BANNER_STYLE_TAB_ALIGN 0x00000020 + +/* + *   Use "MORE" mode in this window.  By default, a banner window should + *   happily allow text to overflow the vertical limits of the window; the + *   only special thing that should happen on overflow is that the window + *   should be srolled down to show the latest text, if the auto-vscroll + *   style is set.  With this flag, though, a banner window acts just like + *   the main text window: when the window fills up vertically, we show a + *   MORE prompt (using appropriate system conventions), and wait for the + *   user to indicate that they're ready to see more text.  On most systems, + *   the user acknowledges a MORE prompt by pressing a key or scrolling with + *   the mouse, but it's up to the system implementor to decide what's + *   appropriate for the system. + *    + *   Note that MORE mode in ANY banner window should generally override all + *   other user input focus.  In other words, if the game in the main window + *   would like to read a keystroke from the user, but one of the banner + *   windows is pausing with a MORE prompt, any keyboard input should be + *   directed to the banner paused at the MORE prompt, not to the main + *   window; the main window should not receive any key events until the MORE + *   prompt has been removed. + *    + *   This style requires the auto-vscroll style.  Implementations should + *   assume auto-vscroll when this style is set.  This style can be ignored + *   with text grid windows.   + */ +#define OS_BANNER_STYLE_MOREMODE  0x00000040 + +/* + *   This banner is a horizontal/vertical "strut" for sizing purposes.  This + *   means that the banner's content size is taken into account when figuring + *   the content size of its *parent* banner.  If the banner has the same + *   orientation as the parent, its content size is added to its parent's + *   internal content size to determine the parent's overall content size. + *   If the banner's orientation is orthogonal to the parent's, then the + *   parent's overall content size is the larger of the parent's internal + *   content size and this banner's content size.   + */ +#define OS_BANNER_STYLE_HSTRUT    0x00000080 +#define OS_BANNER_STYLE_VSTRUT    0x00000100 + + +/*  + *   Delete a banner.  This removes the banner from the display, which + *   requires recalculating the entire screen's layout to reallocate this + *   banner's space to other windows.  When this routine returns, the banner + *   handle is invalid and can no longer be used in any os_banner_xxx + *   function calls.   + *    + *   If the banner has children, the children will no longer be displayed, + *   but will remain valid in memory until deleted.  A child window's + *   display area always comes out of its parent's space, so once the parent + *   is gone, a child has no way to acquire any display space; resizing the + *   child won't help, since it simply has no way to obtain any screen space + *   once its parent has been deleted.  Even though the window's children + *   will become invisible, their banner handles will remain valid; the + *   caller is responsible for explicitly deleting the children even after + *   deleting their parent.   + */ +void os_banner_delete(void *banner_handle); + +/* + *   "Orphan" a banner.  This tells the osifc implementation that the caller + *   wishes to sever all of its ties with the banner (as part of program + *   termination, for example), but that the calling program does not + *   actually require that the banner's on-screen display be immediately + *   removed. + *    + *   The osifc implementation can do one of two things: + *    + *   1.  Simply call os_banner_delete().  If the osifc implementation + *   doesn't want to do anything extra with the banner, it can simply delete + *   the banner, since the caller has no more use for it. + *    + *   2.  Take ownership of the banner.  If the osifc implementation wishes + *   to continue displaying the final screen configuration after a program + *   has terminated, it can simply take over the banner and leave it on the + *   screen.  The osifc subsystem must eventually delete the banner itself + *   if it takes this routine; for example, if the osifc subsystem allows + *   another client program to be loaded into the same window after a + *   previous program has terminated, it would want to delete any orphaned + *   banners from the previous program when loading a new program.   + */ +void os_banner_orphan(void *banner_handle); + +/* + *   Banner information structure.  This is filled in by the system-specific + *   implementation in os_banner_getinfo().   + */ +struct os_banner_info_t +{ +    /* alignment */ +    int align; + +    /* style flags - these indicate the style flags actually in use */ +    unsigned long style; + +    /*  +     *   Actual on-screen size of the banner, in rows and columns.  If the +     *   banner is displayed in a proportional font or can display multiple +     *   fonts of different sizes, this is approximated by the number of "0" +     *   characters in the window's default font that will fit in the +     *   window's display area.   +     */ +    int rows; +    int columns; + +    /* +     *   Actual on-screen size of the banner in pixels.  This is meaningful +     *   only for full HTML interpreter; for text-only interpreters, these +     *   are always set to zero. +     *    +     *   Note that even if we're running on a GUI operating system, these +     *   aren't meaningful unless this is a full HTML interpreter.  Text-only +     *   interpreters should always set these to zero, even on GUI OS's.   +     */ +    int pix_width; +    int pix_height; + +    /*  +     *   OS line wrapping flag.  If this is set, the window uses OS-level +     *   line wrapping because the window uses a proportional font, so the +     *   caller does not need to (and should not) perform line breaking in +     *   text displayed in the window. +     *    +     *   Note that OS line wrapping is a PERMANENT feature of the window. +     *   Callers can note this information once and expect it to remain +     *   fixed through the window's lifetime.   +     */ +    int os_line_wrap; +}; +typedef struct os_banner_info_t os_banner_info_t; + +/*  + *   Get information on the banner - fills in the information structure with + *   the banner's current settings.  Note that this should indicate the + *   ACTUAL properties of the banner, not the requested properties; this + *   allows callers to determine how the banner is actually displayed, which + *   depends upon the platform's capabilities and user preferences. + *    + *   Returns true if the information was successfully obtained, false if + *   not.  This can return false if the underlying OS window has already + *   been closed by a user action, for example.   + */ +int os_banner_getinfo(void *banner_handle, os_banner_info_t *info); + +/*  + *   Get the character width/height of the banner, for layout purposes.  This + *   gives the size of the banner in character cells. + *    + *   These are not meaningful when the underlying window uses a proportional + *   font or varying fonts of different sizes.  When the size of text varies + *   in the window, the OS layer is responsible for word-wrapping and other + *   layout, in which case these simply return zero. + *    + *   Note that these routines might appear to be redundant with the 'rows' + *   and 'columns' information returned from os_banner_getinfo(), but these + *   have two important distinctions.  First, these routines return only the + *   width and height information, so they can be implemented with less + *   overhead than os_banner_getinfo(); this is important because formatters + *   might need to call these routines frequently while formatting text. + *   Second, these routines are not required to return an approximation for + *   windows using proportional fonts, as os_banner_getinfo() does; these can + *   simply return zero when a proportional font is in use.   + */ +int os_banner_get_charwidth(void *banner_handle); +int os_banner_get_charheight(void *banner_handle); + +/* clear the contents of a banner */ +void os_banner_clear(void *banner_handle); + +/*  + *   Display output on a banner.  Writes the output to the window on the + *   display at the current output position. + *    + *   The following special characters should be recognized and handled: + *    + *   '\n' - newline; move output position to the start of the next line. + *    + *   '\r' - move output position to start of current line; subsequent text + *   overwrites any text previously displayed on the current line.  It is + *   permissible to delete the old text immediately on seeing the '\r', + *   rather than waiting for additional text to actually overwrite it. + *    + *   All other characters should simply be displayed as ordinary printing + *   text characters.  Note that tab characters should not be passed to this + *   routine, but if they are, they can simply be treated as ordinary spaces + *   if desired.  Other control characters (backspace, escape, etc) should + *   never be passed to this routine; the implementation is free to ignore + *   any control characters not listed above. + *    + *   If any text displayed here overflows the current boundaries of the + *   window on the screen, the text MUST be "clipped" to the current window + *   boundaries; in other words, anything this routine tries to display + *   outside of the window's on-screen rectangle must not actually be shown + *   on the screen. + *    + *   Text overflowing the display boundaries MUST also be retained in an + *   internal buffer.  This internal buffer can be limited to the actual + *   maximum display size of the terminal screen or application window, if + *   desired.  It is necessary to retain clipped text, because this allows a + *   window to be expanded to the size of its contents AFTER the contents + *   have already been displayed. + *    + *   If the banner does its own line wrapping, it must indicate this via the + *   os_line_wrap flag in the os_banner_getinfo() return data.  If the + *   banner doesn't indicate this flag, then it must not do any line + *   wrapping at all, even if the caller attempts to write text beyond the + *   right edge of the window - any text overflowing the width of the window + *   must simply be clipped. + *    + *   Text grid banners must ALWAYS clip - these banners should never perform + *   any line wrapping.   + */ +void os_banner_disp(void *banner_handle, const char *txt, size_t len); + +/* + *   Set the text attributes in a banner, for subsequent text displays. + *   'attr' is a (bitwise-OR'd) combination of OS_ATTR_xxx values.  + */ +void os_banner_set_attr(void *banner_handle, int attr); + +/*  + *   Set the text color in a banner, for subsequent text displays.  The 'fg' + *   and 'bg' colors are given as RGB or parameterized colors; see the + *   definition of os_color_t for details. + *    + *   If the underlying renderer is HTML-enabled, then this should not be + *   used; the appropriate HTML code should simply be displayed to the + *   banner instead.   + */ +void os_banner_set_color(void *banner_handle, os_color_t fg, os_color_t bg); + +/*  + *   Set the screen color in the banner - this is analogous to the screen + *   color in the main text area. + *    + *   If the underlying renderer is HTML-enabled, then this should not be + *   used; the HTML <BODY> tag should be used instead.   + */ +void os_banner_set_screen_color(void *banner_handle, os_color_t color); + +/* flush output on a banner */ +void os_banner_flush(void *banner_handle); + +/* + *   Set the banner's size.  The size has the same meaning as in + *   os_banner_create(). + *    + *   'is_advisory' indicates whether the sizing is required or advisory only. + *   If this flag is false, then the size should be set as requested.  If + *   this flag is true, it means that the caller intends to call + *   os_banner_size_to_contents() at some point, and that the size being set + *   now is for advisory purposes only.  Platforms that support + *   size-to-contents may simply ignore advisory sizing requests, although + *   they might want to ensure that they have sufficient off-screen buffer + *   space to keep track of the requested size of display, so that the + *   information the caller displays in preparation for calling + *   size-to-contents will be retained.  Platforms that do not support + *   size-to-contents should set the requested size even when 'is_advisory' + *   is true.   + */ +void os_banner_set_size(void *banner_handle, int siz, int siz_units, +                        int is_advisory); + +/*  + *   Set the banner to the size of its current contents.  This can be used + *   to set the banner's size after some text (or other material) has been + *   displayed to the banner, so that the size can be set according to the + *   banner's actual space requirements. + *    + *   This changes the banner's "requested size" to match the current size. + *   Subsequent calls to os_banner_getinfo() will thus indicate a requested + *   size according to the size set here.   + */ +void os_banner_size_to_contents(void *banner_handle); + +/*  + *   Turn HTML mode on/off in the banner window.  If the underlying renderer + *   doesn't support HTML, these have no effect.   + */ +void os_banner_start_html(void *banner_handle); +void os_banner_end_html(void *banner_handle); + +/* + *   Set the output coordinates in a text grid window.  The grid window is + *   arranged into character cells numbered from row zero, column zero for + *   the upper left cell.  This function can only be used if the window was + *   created with type OS_BANNER_TYPE_TEXTGRID; the request should simply be + *   ignored by other window types. + *    + *   Moving the output position has no immediate effect on the display, and + *   does not itself affect the "content size" for the purposes of + *   os_banner_size_to_contents().  This simply sets the coordinates where + *   any subsequent text is displayed.   + */ +void os_banner_goto(void *banner_handle, int row, int col); + + +/* ------------------------------------------------------------------------ */ +/* + *   Get system information.  'code' is a SYSINFO_xxx code, which + *   specifies what type of information to get.  The 'param' argument's + *   meaning depends on which code is selected.  'result' is a pointer to + *   an integer that is to be filled in with the result value.  If the + *   code is not known, this function should return FALSE.  If the code is + *   known, the function should fill in *result and return TRUE. + */ +int os_get_sysinfo(int code, void *param, long *result); + +/* determine if systemInfo is supported - os_get_sysinfo never gets this */ +#define SYSINFO_SYSINFO   1 + +/* get interpreter version number - os_get_sysinfo never gets this */ +#define SYSINFO_VERSION   2 + +/* get operating system name - os_get_sysinfo never gets this */ +#define SYSINFO_OS_NAME   3 + +/*  + *   Can the system process HTML directives?  returns 1 if so, 0 if not. + *   Note that if this returns false, then all of the codes below from + *   JPEG to LINKS are implicitly false as well, since TADS can only use + *   images, sounds, and links through HTML.  + */ +#define SYSINFO_HTML      4 + +/* can the system display JPEG's?  1 if yes, 0 if no */ +#define SYSINFO_JPEG      5 + +/* can the system display PNG's?  1 if yes, 0 if no */ +#define SYSINFO_PNG       6 + +/* can the system play WAV's?  1 if yes, 0 if no */ +#define SYSINFO_WAV       7 + +/* can the system play MIDI's?  1 if yes, 0 if no */ +#define SYSINFO_MIDI      8 + +/* can the system play MIDI and WAV's simultaneously?  yes=1, no=0 */ +#define SYSINFO_WAV_MIDI_OVL  9 + +/* can the system play multiple WAV's simultaneously?  yes=1, no=0 */ +#define SYSINFO_WAV_OVL   10 + +/* + *   GENERAL NOTES ON PREFERENCE SETTINGS: + *    + *   Several of the selectors below refer to the preference settings.  We're + *   talking about user-settable options to control various aspects of the + *   interpreter.  The conventional GUI for this kind of thing is a dialog + *   box reachable through a menu command named something like "Options" or + *   "Preferences".  A couple of general notes about these: + *    + *   1.  The entire existence of a preferences mechanism is optional - + *   interpreter writers aren't required to implement anything along these + *   lines.  In some cases the local platforms might not have any suitable + *   conventions for a preferences UI (e.g., character-mode console + *   applications), and in other cases the terp developer might just want to + *   omit a prefs mechanism because of the work involved to implement it, or + *   to keep the UI simpler. + *    + *   2.  If a given SYSINFO_PREF_xxx selector refers to a preference item + *   that's not implemented in the local interpreter, the terp should simply + *   return a suitable default result.  For example, if the interpreter + *   doesn't have a preference item to allow the user to turn sounds off, the + *   selector SYSINFO_PREF_SOUNDS should return 1 to indicate that the user + *   has not in fact turned off sounds (because there's no way to do so). + *    + *   3.  The various SYSINFO_PREF_xxx selectors are purely queries - they're + *   NOT a mechanism for enforcing the preferences.  For example, if the + *   interpreter provides a "Sounds On/Off" option, it's up to the terp to + *   enforce it the Off setting by ignoring any sound playback requests.  The + *   game isn't under any obligation to query any of the preferences or to + *   alter its behavior based on their settings - you should expect that the + *   game will go on trying to play sounds even when "Sounds Off" is selected + *   in the preferences.  The purpose of these SYSINFO selectors is to let + *   the game determine the current presentation status, but *only if it + *   cares*.  For example, the game might determine whether or not sounds are + *   actually being heard just before playing a sound effect that's important + *   to the progress of the game, so that it can provide a visual alternative + *   if the effect won't be heard.   + */ + +/*  + *   Get image preference setting - 1 = images can be displayed, 0 = images + *   are not being displayed because the user turned off images in the + *   preferences.  This is, of course, irrelevant if images can't be + *   displayed at all. + *    + *   See the general notes on preferences queries above.   + */ +#define SYSINFO_PREF_IMAGES  11 + +/* + *   Get digitized sound effect (WAV) preference setting - 1 = sounds can be + *   played, 0 = sounds are not being played because the user turned off + *   sounds in the preferences. + *    + *   See the general notes on preferences queries above.   + */ +#define SYSINFO_PREF_SOUNDS  12 + +/* + *   Get music (MIDI) preference setting - 1 = music can be played, 0 = music + *   is not being played because the user turned off music in the + *   preferences. + *    + *   See the general notes on preferences queries above.   + */ +#define SYSINFO_PREF_MUSIC   13 + +/* + *   Get link display preference setting - 0 = links are not being displayed + *   because the user set a preference item that suppresses all links (which + *   doesn't actually hide them, but merely displays them and otherwise + *   treats them as ordinary text).  1 = links are to be displayed normally. + *   2 = links can be displayed temporarily by the user by pressing a key or + *   some similar action, but aren't being displayed at all times.   + *    + *   See the general note on preferences queries above.   + */ +#define SYSINFO_PREF_LINKS   14 + +/* can the system play MPEG sounds of any kind? */ +#define SYSINFO_MPEG         15 + +/* can the system play MPEG audio 2.0 layer I/II/III sounds? */ +#define SYSINFO_MPEG1        16 +#define SYSINFO_MPEG2        17 +#define SYSINFO_MPEG3        18 + +/*  + *   is the system *currently* in HTML mode?  os_get_sysinfo never gets + *   this code, since the portable output layer keeps track of this  + */ +#define SYSINFO_HTML_MODE    19 + +/*  + *   Does the system allow following external URL links of the various + *   types?  These return true if the system is capable of following these + *   types of hypertext links, false if not.  Note that, if the system is + *   capable of following these links, these should return true regardless + *   of any current mode settings; in particular, these should not be + *   sensitive to the current HTML mode or the current link display mode, + *   since the question is not whether a link now displayed can be + *   followed by the user, but rather whether the system has the + *   capability to follow these types of links at all.   + */ +#define SYSINFO_LINKS_HTTP   20 +#define SYSINFO_LINKS_FTP    21 +#define SYSINFO_LINKS_NEWS   22 +#define SYSINFO_LINKS_MAILTO 23 +#define SYSINFO_LINKS_TELNET 24 + +/* is PNG transparency supported? */ +#define SYSINFO_PNG_TRANS    25 + +/* is PNG alpha blending supported? */ +#define SYSINFO_PNG_ALPHA    26 + +/* is the Ogg Vorbis audio format supported? */ +#define SYSINFO_OGG          27 + +/* can the system display MNG's? */ +#define SYSINFO_MNG          28 + +/* can the system display MNG's with transparency? */ +#define SYSINFO_MNG_TRANS    29 + +/* can the system display MNG's with alpha blending? */ +#define SYSINFO_MNG_ALPHA    30 + +/* can we display highlighted text in its own appearance? */ +#define SYSINFO_TEXT_HILITE  31 + +/*  + *   Can we display text colors?  This returns a SYSINFO_TXC_xxx code + *   indicating the level of color support. + *    + *   The os_xxx interfaces don't presently support anything beyond the ANSI + *   colors; however, HTML-enabled interpreters generally support full RGB + *   colors, so we call this out as a separate level.   + */ +#define SYSINFO_TEXT_COLORS  32 + +/* no text color support */ +#define SYSINFO_TXC_NONE      0 + +/* parameterized color names only (OS_COLOR_P_TEXT, etc) */ +#define SYSINFO_TXC_PARAM     1 + +/*  + *   we support only the basic ANSI colors, foreground control only (white, + *   black, blue, red, green, yellow, cyan, magenta)  + */ +#define SYSINFO_TXC_ANSI_FG   2 + +/* ANSI colors, foreground and background */ +#define SYSINFO_TXC_ANSI_FGBG 3 + +/* full RGB support */ +#define SYSINFO_TXC_RGB       4 + +/* are the os_banner_xxx() interfaces supported? */ +#define SYSINFO_BANNERS      33 + +/* Interpreter Class - this returns one of the SYSINFO_ICLASS_xxx codes */ +#define SYSINFO_INTERP_CLASS 34 + +/*  + *   Interpreter class: Character-mode Text-Only.  Interpreters of this class + *   use a single, fixed-pitch font to display all text, and use the + *   text-only HTML subset, and cannot display graphics. + */ +#define SYSINFO_ICLASS_TEXT    1 + +/*  + *   Interpreter class: Text-Only GUI.  Interpreters of this class are + *   traditional text-only interpreters running on graphical operating + *   systems.  These interpreters might use multiple fonts (for example, they + *   might display highlighted text in boldface), and might use + *   proportionally-spaced text for some windows.  These interpreters support + *   the text-only HTML subset, and cannot display graphics. + *    + *   Text-only GUI interpreters act essentially the same as character-mode + *   text-only interpreters, from the perspective of the client program.   + */ +#define SYSINFO_ICLASS_TEXTGUI 2 + +/* + *   Interpreter class: HTML.  Interpreters of this class can display + *   graphics and sounds, can display multiple fonts and font sizes, can use + *   proportional fonts, and support the full HTML TADS markup language for + *   formatting.   + */ +#define SYSINFO_ICLASS_HTML    3 + +/* + *   Audio fade information. + *    + *   SYSINFO_AUDIO_FADE: basic fade-in and fade-out support.  Interpreters + *   that don't support audio fade at all should return 0.  Interpreters that + *   support fades should return a bitwise OR'd combination of + *   SYSINFO_AUDIOFADE_xxx flags below indicating which formats can be used + *   with fades. + *    + *   SYSINFO_AUDIO_CROSSFADE: cross-fades are supported (i.e., simultaneous + *   play of overlapping tracks, one fading out while the other fades in). + *   If cross-fades aren't supported, return 0.  If they're supported, return + *   a combination of SYSINFO_AUDIOFADE_xxx flags indicating which formats + *   can be used with cross-fades.   + */ +#define SYSINFO_AUDIO_FADE       35 +#define SYSINFO_AUDIO_CROSSFADE  36 + +/*  + *   Specific audio fading features.  These are bit flags that can be + *   combined to indicate the fading capabilities of the interpreter.   + */ +#define SYSINFO_AUDIOFADE_MPEG  0x0001          /* supported for MPEG audio */ +#define SYSINFO_AUDIOFADE_OGG   0x0002          /* supported for Ogg Vorbis */ +#define SYSINFO_AUDIOFADE_WAV   0x0004                 /* supported for WAV */ +#define SYSINFO_AUDIOFADE_MIDI  0x0008                /* supported for MIDI */ + + +/* ------------------------------------------------------------------------ */ +/* + *   Integer division operators.  For any compiler that follows ANSI C + *   rules, no definitions are required for these routine, since the + *   standard definitions below will work properly.  However, if your + *   compiler does not follow ANSI standards with respect to integer + *   division of negative numbers, you must provide implementations of + *   these routines that produce the correct results. + *    + *   Division must "truncate towards zero," which means that any + *   fractional part is dropped from the result.  If the result is + *   positive, the result must be the largest integer less than the + *   algebraic result: 11/3 yields 3.  If the result is negative, the + *   result must be the smallest integer less than the result: (-11)/3 + *   yields -3. + *    + *   The remainder must obey the relationship (a/b)*b + a%b == a, for any + *   integers a and b (b != 0). + *    + *   If your compiler does not obey the ANSI rules for the division + *   operators, make the following changes in your osxxx.h file + *    + *   - define the symbol OS_NON_ANSI_DIVIDE in the osxxx.h file + *    + *   - either define your own macros for os_divide_long() and + *   os_remainder_long(), or put actual prototypes for these functions + *   into your osxxx.h file and write appropriate implementations of these + *   functions in one of your osxxx.c or .cpp files. + */ +/* long os_divide_long(long a, long b);    // returns (a/b) with ANSI rules */ +/* long os_remainder_long(long a, long b); // returns (a%b) with ANSI rules */ + +/* standard definitions for any ANSI compiler */ +#ifndef OS_NON_ANSI_DIVIDE +#define os_divide_long(a, b)     ((a) / (b)) +#define os_remainder_long(a, b)  ((a) % (b)) +#endif + +/* ------------------------------------------------------------------------ */ +/* + *   Special "switch" statement optimization flags.  These definitions are + *   OPTIONAL - if a platform doesn't provide these definitions, suitable + *   code that's fully portable will be used. + *    + *   On some compilers, the performance of a "switch" statement can be + *   improved by fully populating the switch with all possible "case" + *   values.  When the compiler can safely assume that every possible "case" + *   value is specifically called out in the switch, the compiler can + *   generate somewhat faster code by omitting any range check for the + *   controlling expression of the switch: a range check is unnecessary + *   because the compiler knows that the value can never be outside the + *   "case" table. + *    + *   This type of optimization doesn't apply to all compilers.  When a given + *   platform's compiler can be coerced to produce faster "switch" + *   statements, though, there might be some benefit in defining these + *   symbols according to local platform rules. + *    + *   First, #define OS_FILL_OUT_CASE_TABLES if you want this type of switch + *   optimization at all.  This symbol is merely a flag, so it doesn't need + *   a value - #defining it is enough to activate the special code.  If you + *   don't define this symbol, then the code that cares about this will + *   simply generate ordinary switches, leaving holes in the case table and + *   using "default:" to cover them.  If the platform's osxxx.h header does + *   #define OS_FILL_OUT_CASE_TABLES, then the portable code will know to + *   fill out case tables with all possible alternatives where it's possible + *   and potentially beneficial to do so. + *    + *   Second, if you #define OS_FILL_OUT_CASE_TABLES, you MUST ALSO #define + *   OS_IMPOSSIBLE_DEFAULT_CASE.  The value for this symbol must be some + *   code to insert into a "switch" statement at the point where a + *   "default:" case would normally go.  On some compilers, special + *   extensions allow the program to explicitly indicate within a switch + *   that all possible cases are covered, so that the compiler doesn't have + *   to be relied upon to infer this for itself (which would be impossible + *   for it to do in many cases anyway).  You can use an empty definition + *   for this symbol if your compiler doesn't have any special construct for + *   indicating a fully-populated case table. + *    + *   Note that *most* switch statements in portable code won't care one way + *   or the other about these definitions.  There's a time/space trade-off + *   in fully populating a switch's case table, so only the most + *   time-critical code will bother trying.   + */ + + + +/* ------------------------------------------------------------------------ */ +/* + *   TADS 2 swapping configuration.  Define OS_DEFAULT_SWAP_ENABLED to 0 + *   if you want swapping turned off, 1 if you want it turned on.  If we + *   haven't defined a default swapping mode yet, turn swapping on by + *   default.   + */ +#ifndef OS_DEFAULT_SWAP_ENABLED +# define OS_DEFAULT_SWAP_ENABLED   1 +#endif + +/* + *   If the system "long description" (for the banner) isn't defined, make + *   it the same as the platform ID string.   + */ +#ifndef OS_SYSTEM_LDESC +# define OS_SYSTEM_LDESC  OS_SYSTEM_NAME +#endif + +/* + *   TADS 2 Usage Lines + *    + *   If the "usage" lines (i.e., the descriptive lines of text describing + *   how to run the various programs) haven't been otherwise defined, + *   define defaults for them here.  Some platforms use names other than + *   tc, tr, and tdb for the tools (for example, on Unix they're usually + *   tadsc, tadsr, and tadsdb), so the usage lines should be adjusted + *   accordingly; simply define them earlier than this point (in this file + *   or in one of the files included by this file, such as osunixt.h) for + *   non-default text.   + */ +#ifndef OS_TC_USAGE +# define OS_TC_USAGE  "usage: tc [options] file" +#endif +#ifndef OS_TR_USAGE +# define OS_TR_USAGE  "usage: tr [options] file" +#endif +#ifndef OS_TDB_USAGE +# define OS_TDB_USAGE  "usage: tdb [options] file" +#endif + +/* + *   Ports can define a special TDB startup message, which is displayed + *   after the copyright/version banner.  If it's not defined at this + *   point, define it to an empty string.   + */ +#ifndef OS_TDB_STARTUP_MSG +# define OS_TDB_STARTUP_MSG "" +#endif + +/* + *   If a system patch sub-level isn't defined, define it here as zero. + *   The patch sub-level is used on some systems where a number of ports + *   are derived from a base port (for example, a large number of ports + *   are based on the generic Unix port).  For platforms like the Mac, + *   where the porting work applies only to that one platform, this + *   sub-level isn't meaningful. + */ +#ifndef OS_SYSTEM_PATCHSUBLVL +# define OS_SYSTEM_PATCHSUBLVL  "0" +#endif  } // End of namespace TADS2  } // End of namespace TADS diff --git a/engines/glk/tads/tads2/post_compilation.h b/engines/glk/tads/tads2/post_compilation.h new file mode 100644 index 0000000000..1dde838d02 --- /dev/null +++ b/engines/glk/tads/tads2/post_compilation.h @@ -0,0 +1,96 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* definitions for post-compilation setup + * + * Does post-compilation setup, such as setting up contents lists + * faster run-time performance, but run - time errors could be disastrous. + */ + +#ifndef GLK_TADS_TADS2_POST_COMPILATION +#define GLK_TADS_TADS2_POST_COMPILATION + +#include "glk/tads/tads2/run.h" +#include "glk/tads/tads2/tokenizer.h" +#include "glk/tads/tads2/vocabulary.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* setup context */ +struct supcxdef { +    errcxdef *supcxerr; +    mcmcxdef *supcxmem;                    /* memory manager client context */ +    voccxdef *supcxvoc;                   /* player command parsing context */ +    tokthdef *supcxtab;                           /* top-level symbol table */ +    runcxdef *supcxrun;                                /* execution context */ +    uchar    *supcxbuf;                        /* space for building a list */ +    ushort    supcxlen;                                   /* size of buffer */ +}; + +/* set up contents list for one object for demand-on-load */ +void supcont(void *ctx, objnum obj, prpnum prp); + +/* set up inherited vocabulary (called before executing game) */ +void supivoc(supcxdef *ctx); + +/* find required objects/functions */ +void supfind(errcxdef *ctx, tokthdef *tab, voccxdef *voc, +             objnum *preinit, int warnlevel, int casefold); + +/* set up reserved words */ +void suprsrv(supcxdef *sup, void (*bif[])(struct bifcxdef *, int), +             toktdef *tab, int fncntmax, int v1compat, char *new_do, +             int casefold); + +/* set up built-in functions without symbol table (for run-time) */ +void supbif(supcxdef *sup, void (*bif[])(struct bifcxdef *, int), +            int bifsiz); + +/* log an undefined-object error */ +void sup_log_undefobj(mcmcxdef *mctx, errcxdef *ec, int err, +                      char *sym_name, int sym_name_len, objnum objn); + +/* set up inherited vocabulary for a particular object */ +void supivoc1(supcxdef *sup, voccxdef *ctx, vocidef *v, objnum target, +              int inh_from_obj, int flags); + +/* get name of an object out of symbol table */ +void supgnam(char *buf, tokthdef *tab, objnum objn); + +/* table of built-in functions */ +typedef struct supbidef supbidef; +struct supbidef +{ +    char  *supbinam;                                    /* name of function */ +    void (*supbifn)(struct bifcxdef *, int);           /* C routine to call */ +}; + +/* external definition for special token table */ +extern tokldef supsctab[]; + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/property.h b/engines/glk/tads/tads2/property.h new file mode 100644 index 0000000000..86a4b69d67 --- /dev/null +++ b/engines/glk/tads/tads2/property.h @@ -0,0 +1,167 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* property definitions + * + * A property structure must be binary-portable, because properties are + * stored in objects, which must be binary-portable.  Hence, the internal + * structure of a property header is not a C structure, but a portable + * sequence of bytes.  Multi-byte quantities are stored in Intel format. + *  + * property number    - 2 bytes + * property datatype  - 1 byte + * property size      - 2 bytes + * property flags     - 1 byte + *  + * This header is followed immediately by the property value.  For + * convenience, a set of macros is defined to provide access to the + * fields of a property header. + */ + +#ifndef GLK_TADS_TADS2_PROPERTY +#define GLK_TADS_TADS2_PROPERTY + +#include "glk/tads/tads.h" +#include "glk/tads/tads2/data.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/** + * a property number, used to look up all properties */ +typedef ushort prpnum; + +typedef uchar prpdef;                   /* prpdef is just an array of bytes */ +#define PRPHDRSIZ 6           /* "sizeof(prpdef)" - size of property header */ + +/* Macros to provide access to property header entries */ +#define prpprop(p) osrp2((uchar *)(p)) +#define prptype(p) (*(((uchar *)(p)) + 2)) +#define prpsize(p) osrp2((((uchar *)(p)) + 3)) +#define prpflg(p)  (*(((uchar *)(p)) + 5)) +#define prpvalp(p) (((uchar *)(p)) + 6) + +#define prpsetprop(p,n) oswp2((uchar *)(p), n) +#define prpsetsize(p,s) oswp2((((uchar *)(p)) + 3), s) + +/* property flag values */ +#define PRPFORG   0x01                /* property is original startup value */ +#define PRPFIGN   0x02               /* ignore this prop (has been changed) */ +#define PRPFDEL   0x04             /* property has been permanently deleted */ + +/** + * Invalid property number - this number will never be used as an actual + * property, so it can be used to signify the lack of a valid property  + */ +#define PRP_INVALID     0 + +/* certain property types are special, and are reserved here */ +#define PRP_DOACTION    1                              /* doAction property */ + +/* vocabulary properties - keep these contiguous, and must start at 2 */ +#define PRP_VERB        2                      /* verb vocabulary property  */ +#define PRP_NOUN        3                       /* noun vocabulary property */ +#define PRP_ADJ         4                  /* adjective vocabulary property */ +#define PRP_PREP        5                /* preposition vocabulary property */ +#define PRP_ARTICLE     6                    /* article vocabulary property */ +#define PRP_PLURAL      7                     /* plural vocabulary property */ + +/* determine if a property is a vocab property */ +/* int prpisvoc(prpnum p); */ +#define prpisvoc(p) ((p) >= PRP_VERB && (p) <= PRP_PLURAL) + +/* more properties... */ +#define PRP_SDESC       8 +#define PRP_THEDESC     9 +#define PRP_DODEFAULT   10 +#define PRP_IODEFAULT   11 +#define PRP_IOACTION    12 +#define PRP_LOCATION    13 +#define PRP_VALUE       14 +#define PRP_ROOMACTION  15 +#define PRP_ACTORACTION 16 +#define PRP_CONTENTS    17 +#define PRP_TPL         18           /* special built-in TEMPLATE structure */ +#define PRP_PREPDEFAULT 19 +#define PRP_VERACTOR    20 +#define PRP_VALIDDO     21 +#define PRP_VALIDIO     22 +#define PRP_LOOKAROUND  23 +#define PRP_ROOMCHECK   24 +#define PRP_STATUSLINE  25 +#define PRP_LOCOK       26 +#define PRP_ISVIS       27 +#define PRP_NOREACH     28 +#define PRP_ISHIM       29 +#define PRP_ISHER       30 +#define PRP_ACTION      31                                 /* action method */ +#define PRP_VALDOLIST   32                                   /* validDoList */ +#define PRP_VALIOLIST   33                                   /* validIoList */ +#define PRP_IOBJGEN     34                                       /* iobjGen */ +#define PRP_DOBJGEN     35                                       /* dobjGen */ +#define PRP_NILPREP     36                                       /* nilPrep */ +#define PRP_REJECTMDO   37                               /* rejectMultiDobj */ +#define PRP_MOVEINTO    38                                      /* moveInto */ +#define PRP_CONSTRUCT   39                                     /* construct */ +#define PRP_DESTRUCT    40                                      /* destruct */ +#define PRP_VALIDACTOR  41                                    /* validActor */ +#define PRP_PREFACTOR   42                                /* preferredActor */ +#define PRP_ISEQUIV     43                                  /* isEquivalent */ +#define PRP_ADESC       44 +#define PRP_MULTISDESC  45 +#define PRP_TPL2        46         /* new-style built-in TEMPLATE structure */ +#define PRP_ANYVALUE    47   /* anyvalue(n) - value to use for '#' with ANY */ +#define PRP_NEWNUMOBJ   48   /* newnumbered(n) - create new numbered object */ +#define PRP_UNKNOWN     49           /* internal property for unknown words */ +#define PRP_PARSEUNKNOWNDOBJ 50                         /* parseUnknownDobj */ +#define PRP_PARSEUNKNOWNIOBJ 51                         /* parseUnknownIobj */ +#define PRP_DOBJCHECK   52                                     /* dobjCheck */ +#define PRP_IOBJCHECK   53                                     /* iobjCheck */ +#define PRP_VERBACTION  54                                    /* verbAction */ +#define PRP_DISAMBIGDO  55                                  /* disambigDobj */ +#define PRP_DISAMBIGIO  56                                  /* disambigIobj */ +#define PRP_PREFIXDESC  57                                    /* prefixdesc */ +#define PRP_ISTHEM      58                                        /* isThem */ + +/* properties used by TADS/Graphic */ +#define PRP_GP_PIC      100                                   /* gp_picture */ +#define PRP_GP_NAME     101                                      /* gp_name */ +#define PRP_GP_DEFVERB  102                                   /* gp_defverb */ +#define PRP_GP_ACTIVE   103                                    /* gp_active */ +#define PRP_GP_HOTLIST  104                                   /* gp_hotlist */ +#define PRP_GP_ICON     105                                      /* gp_icon */ +#define PRP_GP_DEFVERB2 106                                  /* gp_defverb2 */ +#define PRP_GP_DEFPREP  107                                   /* gp_defprep */ +#define PRP_GP_HOTID    108                                     /* gp_hotid */ +#define PRP_GP_OVERLAY  109                                   /* gp_overlay */ +#define PRP_GP_HOTX     110                                      /* gp_hotx */ +#define PRP_GP_HOTY     111                                      /* gp_hoty */ + +/* highest reserved property number - must match last one above */ +#define PRP_LASTRSV     111 + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/regex.cpp b/engines/glk/tads/tads2/regex.cpp index a7f7d99369..5d608dfa4a 100644 --- a/engines/glk/tads/tads2/regex.cpp +++ b/engines/glk/tads/tads2/regex.cpp @@ -68,10 +68,8 @@ Notes  */  #include "glk/tads/tads2/regex.h" -#include "glk/tads/tads2/ler.h" -#include "glk/tads/tads2/os.h" -//#include "glk/tads/tads2/std.h" -#//include "engines/glk/tads/tads2/ler.h" +#include "glk/tads/tads2/memory_cache_heap.h" +#include "common/util.h"  namespace Glk {  namespace TADS { @@ -128,1148 +126,1461 @@ enum {  	RE_GROUP_MATCH_9      = (RE_GROUP_MATCH_0 + 9)  }; -re_context::re_context(errcxdef *errctx) { -	// save the error context -	_errctx = errctx; -	 -	// clear states -	_next_state = RE_STATE_FIRST_VALID; +/* ------------------------------------------------------------------------ */ +/* + *   A machine description.  Machines are fully described by their initial + *   and final state ID's.   + */ +struct re_machine { +    /* the machine's initial state */ +    re_state_id init; -	// clear groups -	_cur_group = 0; +    /* the machine's final state */ +    re_state_id final; +}; -	// no string buffer yet -	_strbuf = 0; +/* ------------------------------------------------------------------------ */ +/* + *   Initialize the context.  The memory for the context structure itself + *   is allocated and maintained by the caller.   + */ +void re_init(re_context *ctx, errcxdef *errctx) +{ +    /* save the error context */ +    ctx->errctx = errctx; +     +    /* no tuple array yet */ +    ctx->tuple_arr = 0; +    ctx->tuples_alloc = 0; + +    /* clear states */ +    ctx->next_state = RE_STATE_FIRST_VALID; + +    /* clear groups */ +    ctx->cur_group = 0; + +    /* no string buffer yet */ +    ctx->strbuf = 0;  } -re_context::~re_context() { -	// reset state -	reset(); - -	// if we allocated a string buffer, delete it -	if (_strbuf != 0) { -		mchfre(_strbuf); -		_strbuf = nullptr; -	} +/* ------------------------------------------------------------------------ */ +/* + *   Reset compiler - clears states and tuples  + */ +static void re_reset(re_context *ctx) +{ +    int i; +     +    /* delete any range tables we've allocated */ +    for (i = 0 ; i < ctx->next_state ; ++i) +    { +        if (ctx->tuple_arr[i].char_range != 0) +        { +            mchfre(ctx->tuple_arr[i].char_range); +            ctx->tuple_arr[i].char_range = 0; +        } +    } + +    /* clear states */ +    ctx->next_state = RE_STATE_FIRST_VALID; + +    /* clear groups */ +    ctx->cur_group = 0;  } -void re_context::reset() { -	int i; -	 -	// delete any range tables we've allocated -	for (i = 0 ; i < _next_state ; ++i) { -		if (_tuple_arr[i].char_range != 0) { -			mchfre(_tuple_arr[i].char_range); -			_tuple_arr[i].char_range = 0; -		} -	} - -	// clear states -	_next_state = RE_STATE_FIRST_VALID; - -	// clear groups -	_cur_group = 0; +/* ------------------------------------------------------------------------ */ +/* + *   Delete the context - frees structures associated with the context. + *   Does NOT free the memory used by the context structure itself.   + */ +void re_delete(re_context *ctx) +{ +    /* reset state */ +    re_reset(ctx); +     +    /* if we've allocated an array, delete it */ +    if (ctx->tuple_arr != 0) +    { +        mchfre(ctx->tuple_arr); +        ctx->tuple_arr = 0; +    } + +    /* if we allocated a string buffer, delete it */ +    if (ctx->strbuf != 0) +    { +        mchfre(ctx->strbuf); +        ctx->strbuf = 0; +    }  } -re_state_id re_context::alloc_state() { -	// If we don't have enough room for another state, expand the array  -	if (_next_state >= (int)_tuple_arr.size()) { -		// bump the size by a bit -		_tuple_arr.resize(_tuple_arr.size() + 100); -	} - -	// initialize the next state -	_tuple_arr[_next_state].next_state_1 = RE_STATE_INVALID; -	_tuple_arr[_next_state].next_state_2 = RE_STATE_INVALID; -	_tuple_arr[_next_state].ch = RE_EPSILON; -	_tuple_arr[_next_state].flags = 0; -	_tuple_arr[_next_state].char_range = 0; - -	// return the new state's ID -	return _next_state++; +/* ------------------------------------------------------------------------ */ +/* + *   Allocate a new state ID  + */ +static re_state_id re_alloc_state(re_context *ctx) +{ +    /* +     *   If we don't have enough room for another state, expand the array  +     */ +    if (ctx->next_state >= ctx->tuples_alloc) +    { +        uint new_alloc; +         +        /* bump the size by a bit */ +        new_alloc = ctx->tuples_alloc + 100; +         +        /* allocate or expand the array */ +        if (ctx->tuples_alloc == 0) +        { +            /* allocate the initial memory block */ +            ctx->tuple_arr = +                (re_tuple *)mchalo(ctx->errctx, +                                   (new_alloc * sizeof(re_tuple)), +                                   "regex"); +        } +        else +        { +            re_tuple *ptr; +             +            /* allocate a new memory block */ +            ptr = (re_tuple *)mchalo(ctx->errctx, +                                     (new_alloc * sizeof(re_tuple)), +                                     "regex"); +             +            /* copy the old memory to the new memory */ +            memcpy(ptr, ctx->tuple_arr, ctx->tuples_alloc * sizeof(re_tuple)); + +            /* free the old block */ +            mchfre(ctx->tuple_arr); + +            /* use the new block */ +            ctx->tuple_arr = ptr; +        } + +        /* remember the new allocation size */ +        ctx->tuples_alloc = new_alloc; +    } + +    /* initialize the next state */ +    ctx->tuple_arr[ctx->next_state].next_state_1 = RE_STATE_INVALID; +    ctx->tuple_arr[ctx->next_state].next_state_2 = RE_STATE_INVALID; +    ctx->tuple_arr[ctx->next_state].ch = RE_EPSILON; +    ctx->tuple_arr[ctx->next_state].flags = 0; +    ctx->tuple_arr[ctx->next_state].char_range = 0; + +    /* return the new state's ID */ +    return ctx->next_state++;  } -void re_context::set_trans(re_state_id id, re_state_id dest_id, char ch) { -	re_tuple *tuple; -	 -	/*  -	 * get the tuple containing the transitions for this state ID - the -	 * state ID is the index of the state's transition tuple in the array  -	 */ -	tuple = &_tuple_arr[id]; - -	/* -	 * If the first state pointer hasn't been set yet, set it to the new -	 * destination.  Otherwise, set the second state pointer. -	 *  -	 * Only set the character on setting the first state.  When setting -	 * the second state, we must assume that the character for the state -	 * has already been set, since any given state can have only one -	 * character setting.   -	 */ -	if (tuple->next_state_1 == RE_STATE_INVALID) { -		/*  -		 * set the character ID, unless the state has been marked with a -		 * special flag which indicates that the character value has -		 * another meaning (in particular, a group marker)  -		 */ -		if (!(tuple->flags & (RE_STATE_GROUP_BEGIN | RE_STATE_GROUP_END))) -			tuple->ch = ch; - -		// set the first transition -		tuple->next_state_1 = dest_id; -	} else { -		// set only the second transition state - don't set the character -		tuple->next_state_2 = dest_id; -	} + +/* ------------------------------------------------------------------------ */ +/* + *   Set a transition from a state to a given destination state.   + */ +static void re_set_trans(re_context *ctx, +                         re_state_id id, re_state_id dest_id, char ch) +{ +    re_tuple *tuple; +     +    /*  +     *   get the tuple containing the transitions for this state ID - the +     *   state ID is the index of the state's transition tuple in the +     *   array  +     */ +    tuple = &ctx->tuple_arr[id]; + +    /* +     *   If the first state pointer hasn't been set yet, set it to the new +     *   destination.  Otherwise, set the second state pointer. +     *    +     *   Only set the character on setting the first state.  When setting +     *   the second state, we must assume that the character for the state +     *   has already been set, since any given state can have only one +     *   character setting.   +     */ +    if (tuple->next_state_1 == RE_STATE_INVALID) +    { +        /*  +         *   set the character ID, unless the state has been marked with a +         *   special flag which indicates that the character value has +         *   another meaning (in particular, a group marker)  +         */ +        if (!(tuple->flags & (RE_STATE_GROUP_BEGIN | RE_STATE_GROUP_END))) +            tuple->ch = ch; + +        /* set the first transition */ +        tuple->next_state_1 = dest_id; +    } +    else +    { +        /* set only the second transition state - don't set the character */ +        tuple->next_state_2 = dest_id; +    }  } -void re_context::init_machine(re_machine *machine) { -	machine->init = alloc_state(); -	machine->final = alloc_state(); +/* ------------------------------------------------------------------------ */ +/* + *   Initialize a new machine, giving it an initial and final state  + */ +static void re_init_machine(re_context *ctx, re_machine *machine) +{ +    machine->init = re_alloc_state(ctx); +    machine->final = re_alloc_state(ctx);  } -void re_context::build_char(re_machine *machine, char ch) { -	// initialize our new machine -	init_machine(machine); +/* + *   Build a character recognizer + */ +static void re_build_char(re_context *ctx, re_machine *machine, char ch) +{ +    /* initialize our new machine */ +    re_init_machine(ctx, machine); -	// allocate a transition tuple for the new state -	set_trans(machine->init, machine->final, ch); +    /* allocate a transition tuple for the new state */ +    re_set_trans(ctx, machine->init, machine->final, ch);  } -void re_context::build_char_range(re_machine *machine, unsigned char *range, int exclusion) { -	unsigned char *range_copy; -	 -	// initialize our new machine -	init_machine(machine); - -	// allocate a transition table for the new state -	set_trans(machine->init, machine->final, (char)(exclusion ? RE_RANGE_EXCL : RE_RANGE)); +/* + *   Build a character range recognizer.  'range' is a 256-bit (32-byte) + *   bit vector.   + */ +static void re_build_char_range(re_context *ctx, re_machine *machine, +                                unsigned char *range, int exclusion) +{ +    unsigned char *range_copy; +     +    /* initialize our new machine */ +    re_init_machine(ctx, machine); + +    /* allocate a transition table for the new state */ +    re_set_trans(ctx, machine->init, machine->final, +                 (char)(exclusion ? RE_RANGE_EXCL : RE_RANGE)); + +    /* allocate a copy of the range bit vector */ +    range_copy = (unsigned char *)mchalo(ctx->errctx, 32, "regex range"); + +    /* copy the caller's range */ +    memcpy(range_copy, range, 32); + +    /* store it in the tuple */ +    ctx->tuple_arr[machine->init].char_range = range_copy; +} +                                 -	// allocate a copy of the range bit vector -	range_copy = (unsigned char *)mchalo(_errctx, 32, "regex range"); +/* + *   Build a group recognizer.  This is almost the same as a character + *   recognizer, but matches a previous group rather than a literal + *   character.  + */ +static void re_build_group_matcher(re_context *ctx, +                                   re_machine *machine, int group_num) +{ +    /* initialize our new machine */ +    re_init_machine(ctx, machine); + +    /*  +     *   Allocate a transition tuple for the new state, using the group ID +     *   as the character code.  Store the special code for a group +     *   recognizer rather than the normal literal character code.   +     */ +    re_set_trans(ctx, machine->init, machine->final, +                 (char)(group_num + RE_GROUP_MATCH_0)); +} -	// copy the caller's range -	memcpy(range_copy, range, 32); -	// store it in the tuple -	_tuple_arr[machine->init].char_range = range_copy; +/* + *   Build a concatenation recognizer  + */ +static void re_build_concat(re_context *ctx, re_machine *new_machine, +                            re_machine *lhs, re_machine *rhs) +{ +    /* initialize the new machine */ +    re_init_machine(ctx, new_machine); + +    /*  +     *   set up an epsilon transition from the new machine's initial state +     *   to the first submachine's initial state  +     */ +    re_set_trans(ctx, new_machine->init, lhs->init, RE_EPSILON); + +    /* +     *   Set up an epsilon transition from the first submachine's final +     *   state to the second submachine's initial state  +     */ +    re_set_trans(ctx, lhs->final, rhs->init, RE_EPSILON); + +    /* +     *   Set up an epsilon transition from the second submachine's final +     *   state to our new machine's final state  +     */ +    re_set_trans(ctx, rhs->final, new_machine->final, RE_EPSILON);  } -void re_context::build_group_matcher(re_machine *machine, int group_num) { -	// initialize our new machine -	init_machine(machine); - -	/*  -	 * Allocate a transition tuple for the new state, using the group ID -	 * as the character code.  Store the special code for a group -	 * recognizer rather than the normal literal character code.   -	 */ -	set_trans(machine->init, machine->final, (char)(group_num + RE_GROUP_MATCH_0)); +/* + *   Build a group machine.  sub_machine contains the machine that + *   expresses the group's contents; we'll fill in new_machine with a + *   newly-created machine that encloses and marks the group.   + */ +static void re_build_group(re_context *ctx, re_machine *new_machine, +                           re_machine *sub_machine, int group_id) +{ +    /* initialize the container machine */ +    re_init_machine(ctx, new_machine); + +    /*  +     *   set up an epsilon transition from the new machine's initial state +     *   into the initial state of the group, and another transition from +     *   the group's final state into the container's final state  +     */ +    re_set_trans(ctx, new_machine->init, sub_machine->init, RE_EPSILON); +    re_set_trans(ctx, sub_machine->final, new_machine->final, RE_EPSILON); + +    /* +     *   Mark the initial and final states of the group machine as being +     *   group markers.   +     */ +    ctx->tuple_arr[new_machine->init].flags |= RE_STATE_GROUP_BEGIN; +    ctx->tuple_arr[new_machine->final].flags |= RE_STATE_GROUP_END; + +    /* store the group ID in the 'ch' member of the start and end states */ +    ctx->tuple_arr[new_machine->init].ch = group_id; +    ctx->tuple_arr[new_machine->final].ch = group_id;  } -void re_context::build_concat(re_machine *new_machine, re_machine *lhs, re_machine *rhs) { -	// initialize the new machine -	init_machine(new_machine); - -	/*  -	 * Set up an epsilon transition from the new machine's initial state -	 * to the first submachine's initial state  -	 */ -	set_trans(new_machine->init, lhs->init, RE_EPSILON); - -	/* -	 * Set up an epsilon transition from the first submachine's final -	 * state to the second submachine's initial state  -	 */ -	set_trans(lhs->final, rhs->init, RE_EPSILON); - -	/* -	 * Set up an epsilon transition from the second submachine's final -	 * state to our new machine's final state  -	 */ -	set_trans(rhs->final, new_machine->final, RE_EPSILON); +/* + *   Build an alternation recognizer  + */ +static void re_build_alter(re_context *ctx, re_machine *new_machine, +                           re_machine *lhs, re_machine *rhs) +{ +    /* initialize the new machine */ +    re_init_machine(ctx, new_machine); + +    /* +     *   Set up an epsilon transition from our new machine's initial state +     *   to the initial state of each submachine  +     */ +    re_set_trans(ctx, new_machine->init, lhs->init, RE_EPSILON); +    re_set_trans(ctx, new_machine->init, rhs->init, RE_EPSILON); + +    /* +     *   Set up an epsilon transition from the final state of each +     *   submachine to our final state  +     */ +    re_set_trans(ctx, lhs->final, new_machine->final, RE_EPSILON); +    re_set_trans(ctx, rhs->final, new_machine->final, RE_EPSILON);  } -void re_context::build_group(re_machine *new_machine, re_machine *sub_machine, int group_id) { -	// initialize the container machine -	init_machine(new_machine); - -	/*  -	 * set up an epsilon transition from the new machine's initial state -	 * into the initial state of the group, and another transition from -	 * the group's final state into the container's final state  -	 */ -	set_trans(new_machine->init, sub_machine->init, RE_EPSILON); -	set_trans(sub_machine->final, new_machine->final, RE_EPSILON); - -	// Mark the initial and final states of the group machine as being group markers -	_tuple_arr[new_machine->init].flags |= RE_STATE_GROUP_BEGIN; -	_tuple_arr[new_machine->final].flags |= RE_STATE_GROUP_END; - -	// store the group ID in the 'ch' member of the start and end states -	_tuple_arr[new_machine->init].ch = group_id; -	_tuple_arr[new_machine->final].ch = group_id; +/* + *   Build a closure recognizer + */ +static void re_build_closure(re_context *ctx, +                             re_machine *new_machine, re_machine *sub, +                             char specifier) +{ +    /* initialize the new machine */ +    re_init_machine(ctx, new_machine); + +    /*  +     *   set up an epsilon transition from our initial state to the +     *   submachine's initial state, and from the submachine's final state +     *   to our final state  +     */ +    re_set_trans(ctx, new_machine->init, sub->init, RE_EPSILON); +    re_set_trans(ctx, sub->final, new_machine->final, RE_EPSILON); + +    /* +     *   If this is an unbounded closure ('*' or '+', but not '?'), set up +     *   the loop transition that takes us from the new machine's final +     *   state back to its initial state.  We don't do this on the +     *   zero-or-one closure, because we can only match the expression +     *   once.   +     */ +    if (specifier != '?') +        re_set_trans(ctx, sub->final, sub->init, RE_EPSILON); + +    /* +     *   If this is a zero-or-one closure or a zero-or-more closure, set +     *   up an epsilon transition from our initial state to our final +     *   state, since we can skip the entire subexpression.  We don't do +     *   this on the one-or-more closure, because we can't skip the +     *   subexpression in this case.   +     */ +    if (specifier != '+') +        re_set_trans(ctx, new_machine->init, new_machine->final, RE_EPSILON);  } -void re_context::build_alter(re_machine *new_machine, re_machine *lhs, re_machine *rhs) { -	// initialize the new machine -	init_machine(new_machine); - -	/* -	 * Set up an epsilon transition from our new machine's initial state -	 * to the initial state of each submachine  -	 */ -	set_trans(new_machine->init, lhs->init, RE_EPSILON); -	set_trans(new_machine->init, rhs->init, RE_EPSILON); - -	/* -	 * Set up an epsilon transition from the final state of each -	 * submachine to our final state  -	 */ -	set_trans(lhs->final, new_machine->final, RE_EPSILON); -	set_trans(rhs->final, new_machine->final, RE_EPSILON); +/* + *   Build a null machine  + */ +static void re_build_null_machine(re_context *ctx, re_machine *machine) +{ +    machine->init = machine->final = RE_STATE_INVALID;  } -void re_context::build_closure(re_machine *new_machine, re_machine *sub, char specifier) { -	// initialize the new machine -	init_machine(new_machine); - -	/*  -	 * Set up an epsilon transition from our initial state to the submachine's initial -	 * state, and from the submachine's final state to our final state  -	 */ -	set_trans(new_machine->init, sub->init, RE_EPSILON); -	set_trans(sub->final, new_machine->final, RE_EPSILON); - -	/* -	 * If this is an unbounded closure ('*' or '+', but not '?'), set up -	 * the loop transition that takes us from the new machine's final -	 * state back to its initial state.  We don't do this on the -	 * zero-or-one closure, because we can only match the expression -	 * once.   -	 */ -	if (specifier != '?') -		set_trans(sub->final, sub->init, RE_EPSILON); - -	/* -	 * If this is a zero-or-one closure or a zero-or-more closure, set -	 * up an epsilon transition from our initial state to our final -	 * state, since we can skip the entire subexpression.  We don't do -	 * this on the one-or-more closure, because we can't skip the -	 * subexpression in this case.   -	 */ -	if (specifier != '+') -		set_trans(new_machine->init, new_machine->final, RE_EPSILON); +/* ------------------------------------------------------------------------ */ +/* + *   Determine if a machine is null  + */ +static int re_is_machine_null(re_context *ctx, re_machine *machine) +{ +    return (machine->init == RE_STATE_INVALID);  } -void re_context::concat_onto(re_machine *dest, re_machine *rhs) { -	// check for a null destination machine -	if (dest->isNull()) { -		/*  -		 * the first machine is null - simply copy the second machine -		 * onto the first unchanged  -		 */ -		*dest = *rhs; -	} else { -		re_machine new_machine; -		 -		// build the concatenated machine -		build_concat(&new_machine, dest, rhs); - -		// copy the concatenated machine onto the first machine -		*dest = new_machine; -	} + +/* ------------------------------------------------------------------------ */ +/* + *   Concatenate the second machine onto the first machine, replacing the + *   first machine with the resulting machine.  If the first machine is a + *   null machine (created with re_build_null_machine), we'll simply copy + *   the second machine into the first.  + */ +static void re_concat_onto(re_context *ctx, +                           re_machine *dest, re_machine *rhs) +{ +    /* check for a null destination machine */ +    if (re_is_machine_null(ctx, dest)) +    { +        /*  +         *   the first machine is null - simply copy the second machine +         *   onto the first unchanged  +         */ +        *dest = *rhs; +    } +    else +    { +        re_machine new_machine; +         +        /* build the concatenated machine */ +        re_build_concat(ctx, &new_machine, dest, rhs); + +        /* copy the concatenated machine onto the first machine */ +        *dest = new_machine; +    }  } -void re_context::alternate_onto(re_machine *dest, re_machine *rhs) { -	// check to see if the first machine is null -	if (dest->isNull()) { -		/*  -		 * the first machine is null - simply copy the second machine -		 * onto the first  -		 */ -		*dest = *rhs; -	} else { -		/*  -		 * if the second machine is null, don't do anything; otherwise, -		 * build the alternation  -		 */ -		if (!rhs->isNull()) { -			re_machine new_machine; -			 -			// build the alternation -			build_alter(&new_machine, dest, rhs); - -			// replace the first machine with the alternation -			*dest = new_machine; -		} -	} +/* + *   Alternate the second machine onto the first machine, replacing the + *   first machine with the resulting machine.  If the first machine is a + *   null machine, this simply replaces the first machine with the second + *   machine.  If the second machine is null, this simply leaves the first + *   machine unchanged.  + */ +static void re_alternate_onto(re_context *ctx, +                              re_machine *dest, re_machine *rhs) +{ +    /* check to see if the first machine is null */ +    if (re_is_machine_null(ctx, dest)) +    { +        /*  +         *   the first machine is null - simply copy the second machine +         *   onto the first  +         */ +        *dest = *rhs; +    } +    else +    { +        /*  +         *   if the second machine is null, don't do anything; otherwise, +         *   build the alternation  +         */ +        if (!re_is_machine_null(ctx, rhs)) +        { +            re_machine new_machine; +             +            /* build the alternation */ +            re_build_alter(ctx, &new_machine, dest, rhs); + +            /* replace the first machine with the alternation */ +            *dest = new_machine; +        } +    }  } -/** - * Set a bit in a bit vector. +/* ------------------------------------------------------------------------ */ +/* + *   Set a bit in a bit vector.   */  #define re_set_bit(set, bit) \ -	(((unsigned char *)(set))[(bit) >> 3] |= (1 << ((bit) & 7))) +    (((unsigned char *)(set))[(bit) >> 3] |= (1 << ((bit) & 7))) -/** - * Test a bit in a bit vector  +/* + *   Test a bit in a bit vector    */  #define re_is_bit_set(set, bit) \ -	((((unsigned char *)(set))[(bit) >> 3] & (1 << ((bit) & 7))) != 0) - -re_status_t re_context::compile(const char *expr, size_t exprlen, re_machine *result_machine) { -	re_machine cur_machine; -	re_machine alter_machine; -	re_machine new_machine; -	size_t group_stack_level; -	struct { -		re_machine old_cur; -		re_machine old_alter; -		int group_id; -	} group_stack[50]; - -	// reset everything -	reset(); - -	// start out with no current machine and no alternate machine -	cur_machine.build_null_machine(); -	alter_machine.build_null_machine(); - -	// nothing on the stack yet -	group_stack_level = 0; - -	// loop until we run out of expression to parse -	for (; exprlen != 0 ; ++expr, --exprlen) { -		switch(*expr) { -		case '^': -			/* -			 * beginning of line - if we're not at the beginning of the -			 * current expression (i.e., we already have some -			 * concatentations accumulated), treat it as an ordinary -			 * character  -			 */ -			if (!cur_machine.isNull()) -				goto normal_char; - -			// build a new start-of-text recognizer -			build_char(&new_machine, RE_TEXT_BEGIN); - -			/*  -			 * concatenate it onto the string - note that this can't -			 * have any postfix operators  -			 */ -			concat_onto(&cur_machine, &new_machine); -			break; - -		case '$': -			/* -			 * End of line specifier - if there's anything left after -			 * the '$' other than a close parens or alternation -			 * specifier, great it as a normal character  -			 */ -			if (exprlen > 1 -				&& (*(expr+1) != ')' && *(expr+1) != '|')) -				goto normal_char; - -			// build a new end-of-text recognizer -			build_char(&new_machine, RE_TEXT_END); - -			/*  -			 * concatenate it onto the string - note that this can't -			 * have any postfix operators  -			 */ -			concat_onto(&cur_machine, &new_machine); -			break; -			 -		case '(': -			/*  -			 * Add a nesting level.  Push the current machine and -			 * alternate machines onto the group stack, and clear -			 * everything out for the new group.  -			 */ -			if (group_stack_level > sizeof(group_stack)/sizeof(group_stack[0])) { -				/* we cannot proceed - return an error */ -				return RE_STATUS_GROUP_NESTING_TOO_DEEP; -			} - -			// save the current state on the stack -			group_stack[group_stack_level].old_cur = cur_machine; -			group_stack[group_stack_level].old_alter = alter_machine; - -			/*  -			 * Assign the group a group ID - groups are numbered in -			 * order of their opening (left) parentheses, so we want to -			 * assign a group number now.  We won't actually need to -			 * know the group number until we get to the matching close -			 * paren, but we need to assign it now, so store it in the -			 * group stack.  -			 */ -			group_stack[group_stack_level].group_id = _cur_group; - -			// consume the group number -			_cur_group++; - -			// push the level -			++group_stack_level; - -			// start the new group with empty machines -			cur_machine.build_null_machine(); -			alter_machine.build_null_machine(); -			break; - -		case ')': -			// if there's nothing on the stack, ignore this -			if (group_stack_level == 0) -				break; - -			// take a level off the stack -			--group_stack_level; - -			/*  -			 * Remove a nesting level.  If we have a pending alternate -			 * expression, build the alternation expression.  This will -			 * leave the entire group expression in alter_machine, -			 * regardless of whether an alternation was in progress or -			 * not.   -			 */ -			alternate_onto(&alter_machine, &cur_machine); - -			/* -			 * Create a group machine that encloses the group and marks -			 * it with a group number.  We assigned the group number -			 * when we parsed the open paren, so read that group number -			 * from the stack. -			 *  -			 * Note that this will leave 'new_machine' with the entire -			 * group machine.   -			 */ -			build_group(&new_machine, &alter_machine, -				group_stack[group_stack_level].group_id); - -			/* -			 * Pop the stack - restore the alternation and current -			 * machines that were in progress before the group started.  -			 */ -			cur_machine = group_stack[group_stack_level].old_cur; -			alter_machine = group_stack[group_stack_level].old_alter; - -			/* -			 * Check the group expression (in new_machine) for postfix -			 * expressions  -			 */ -			goto apply_postfix; - -		case '|': -			/*  -			 * Start a new alternation.  This ends the current -			 * alternation; if we have a previous pending alternate, -			 * build an alternation machine out of the previous -			 * alternate and the current machine and move that to the -			 * alternate; otherwise, simply move the current machine to -			 * the pending alternate.  -			 */ -			alternate_onto(&alter_machine, &cur_machine); - -			/*  -			 * the alternation starts out with a blank slate, so null -			 * out the current machine  -			 */ -			cur_machine.build_null_machine(); -			break; - -		case '%': -			// quoted character - skip the quote mark and see what we have  -			++expr; -			--exprlen; - -			// check to see if we're at the end of the expression -			if (exprlen == 0) { -				/*  -				 * end of the string - ignore it, but undo the extra -				 * increment of the expression index so that we exit the -				 * enclosing loop properly  -				 */ -				--expr; -				++exprlen; -				break; -			} - -			// see what we have -			switch(*expr) { -			case '1': -			case '2': -			case '3': -			case '4': -			case '5': -			case '6': -			case '7': -			case '8': -			case '9': -				// group match - build a new literal group recognizer -				build_group_matcher(&new_machine, (int)(*expr - '1')); - -				// apply any postfix expression to the group recognizer -				goto apply_postfix; - -			case '<': -				// build a beginning-of-word recognizer -				build_char(&new_machine, RE_WORD_BEGIN); - -				// it can't be postfixed - just concatenate it -				concat_onto(&cur_machine, &new_machine); -				break; - -			case '>': -				// build an end-of-word recognizer */ -				build_char(&new_machine, RE_WORD_END); - -				// it can't be postfixed - just concatenate it -				concat_onto(&cur_machine, &new_machine); -				break; - -			case 'w': -				// word character -				build_char(&new_machine, RE_WORD_CHAR); -				goto apply_postfix; - -			case 'W': -				// non-word character -				build_char(&new_machine, RE_NON_WORD_CHAR); -				goto apply_postfix; - -			case 'b': -				// word boundary -				build_char(&new_machine, RE_WORD_BOUNDARY); - -				// it can't be postfixed -				concat_onto(&cur_machine, &new_machine); -				break; - -			case 'B': -				// not a word boundary -				build_char(&new_machine, RE_NON_WORD_BOUNDARY); - -				// it can't be postfixed -				concat_onto(&cur_machine, &new_machine); -				break; - -			default: -				// build a new literal character recognizer -				build_char(&new_machine, *expr); - -				// apply any postfix expression to the character -				goto apply_postfix; -			} -			break; - -		case '.': -			/*  -			 * wildcard character - build a single character recognizer -			 * for the special wildcard symbol, then go check it for a -			 * postfix operator  -			 */ -			build_char(&new_machine, RE_WILDCARD); -			goto apply_postfix; -			break; - -		case '[': { -			// range expression -			int is_exclusive = false; -			unsigned char set[32]; - -			// clear out the set of characters in the range -			memset(set, 0, sizeof(set)); - -			// first, skip the open bracket -			++expr; -			--exprlen; - -			// check to see if starts with the exclusion character -			if (exprlen != 0 && *expr == '^') { -				// skip the exclusion specifier -				++expr; -				--exprlen; - -				// note it -				is_exclusive = true; -			} - -			// if the first character is a ']', include it in the range  -			if (exprlen != 0 && *expr == ']') { -				re_set_bit(set, (int)']'); -				++expr; -				--exprlen; -			} - -			// if the next character is a '-', include it in the range         -			if (exprlen != 0 && *expr == '-') { -				re_set_bit(set, (int)'-'); -				++expr; -				--exprlen; -			} - -			// scan the character set -			while (exprlen != 0 && *expr != ']') { -				int ch; - -				// note this character -				ch = (int)(unsigned char)*expr; - -				// set it -				re_set_bit(set, ch); - -				// skip this character of the expression -				++expr; -				--exprlen; - -				// check for a range -				if (exprlen != 0 && *expr == '-') { -					int ch2; -	 -					// skip the '-' -					++expr; -					--exprlen; -					if (exprlen != 0) { -						// get the other end of the range -						ch2 = (int)(unsigned char)*expr; - -						// skip the second character -						++expr; -						--exprlen; - -						// if the range is reversed, swap it -						if (ch > ch2) -							SWAP(ch, ch2); - -						// fill in the range -						for ( ; ch <= ch2 ; ++ch) -							re_set_bit(set, ch); -					} -				} -			} - -			// create a character range machine -			build_char_range(&new_machine, set, is_exclusive); - -			// apply any postfix operator -			goto apply_postfix; -			break; -		} - -		default: -		normal_char: -			/*  -			 * it's an ordinary character - build a single character -			 * recognizer machine, and then concatenate it onto any -			 * existing machine  -			 */ -			build_char(&new_machine, *expr); - -		apply_postfix: -			/* -			 * Check for a postfix operator, and apply it to the machine -			 * in 'new_machine' if present.  In any case, concatenate -			 * the 'new_machine' (modified by a postix operator or not) -			 * to the current machien.   -			 */ -			if (exprlen > 1) { -				switch(*(expr+1)) { -				case '*': -				case '+': -				case '?': -					/* -					 * We have a postfix closure operator.  Build a new -					 * closure machine out of 'new_machine'.   -					 */ -					{ -						re_machine closure_machine; -						 -						// move onto the closure operator -						++expr; -						--exprlen; -						 -						// build the closure machine -						build_closure(&closure_machine, &new_machine, *expr); -						 -						// replace the original machine with the closure -						new_machine = closure_machine; -						 -						/*  -						 * skip any redundant closure symbols, keeping -						 * only the first one we saw  -						 */ -						while (exprlen > 1 && (*(expr+1) == '?' -											   || *(expr+1) == '+' -											   || *(expr+1) == '*')) { -							++expr; -							--exprlen; -						} -					} -					break; -					 -				default: -					/* no postfix operator */ -					break; -				} -			} - -			/* -			 * Concatenate the new machine onto the current machine -			 * under construction.   -			 */ -			concat_onto(&cur_machine, &new_machine); -			break; -		} -	} - -	// complete any pending alternation -	alternate_onto(&alter_machine, &cur_machine); - -	// store the resulting machine in the caller's machine descriptor -	*result_machine = alter_machine; - -	// no errors encountered -	return RE_STATUS_SUCCESS; +    ((((unsigned char *)(set))[(bit) >> 3] & (1 << ((bit) & 7))) != 0) + +/* ------------------------------------------------------------------------ */ +/* + *   Compile an expression  + */ +static re_status_t re_compile(re_context *ctx, +                              const char *expr, size_t exprlen, +                              re_machine *result_machine) +{ +    re_machine cur_machine; +    re_machine alter_machine; +    re_machine new_machine; +    size_t group_stack_level; +    struct +    { +        re_machine old_cur; +        re_machine old_alter; +        int group_id; +    } group_stack[50]; + +    /* reset everything */ +    re_reset(ctx); + +    /* start out with no current machine and no alternate machine */ +    re_build_null_machine(ctx, &cur_machine); +    re_build_null_machine(ctx, &alter_machine); + +    /* nothing on the stack yet */ +    group_stack_level = 0; + +    /* loop until we run out of expression to parse */ +    for ( ; exprlen != 0 ; ++expr, --exprlen) +    { +        switch(*expr) +        { +        case '^': +            /* +             *   beginning of line - if we're not at the beginning of the +             *   current expression (i.e., we already have some +             *   concatentations accumulated), treat it as an ordinary +             *   character  +             */ +            if (!re_is_machine_null(ctx, &cur_machine)) +                goto normal_char; + +            /* build a new start-of-text recognizer */ +            re_build_char(ctx, &new_machine, RE_TEXT_BEGIN); + +            /*  +             *   concatenate it onto the string - note that this can't +             *   have any postfix operators  +             */ +            re_concat_onto(ctx, &cur_machine, &new_machine); +            break; + +        case '$': +            /* +             *   End of line specifier - if there's anything left after +             *   the '$' other than a close parens or alternation +             *   specifier, great it as a normal character  +             */ +            if (exprlen > 1 +                && (*(expr+1) != ')' && *(expr+1) != '|')) +                goto normal_char; + +            /* build a new end-of-text recognizer */ +            re_build_char(ctx, &new_machine, RE_TEXT_END); + +            /*  +             *   concatenate it onto the string - note that this can't +             *   have any postfix operators  +             */ +            re_concat_onto(ctx, &cur_machine, &new_machine); +            break; +             +        case '(': +            /*  +             *   Add a nesting level.  Push the current machine and +             *   alternate machines onto the group stack, and clear +             *   everything out for the new group.  +             */ +            if (group_stack_level +                > sizeof(group_stack)/sizeof(group_stack[0])) +            { +                /* we cannot proceed - return an error */ +                return RE_STATUS_GROUP_NESTING_TOO_DEEP; +            } + +            /* save the current state on the stack */ +            group_stack[group_stack_level].old_cur = cur_machine; +            group_stack[group_stack_level].old_alter = alter_machine; + +            /*  +             *   Assign the group a group ID - groups are numbered in +             *   order of their opening (left) parentheses, so we want to +             *   assign a group number now.  We won't actually need to +             *   know the group number until we get to the matching close +             *   paren, but we need to assign it now, so store it in the +             *   group stack.  +             */ +            group_stack[group_stack_level].group_id = ctx->cur_group; + +            /* consume the group number */ +            ctx->cur_group++; + +            /* push the level */ +            ++group_stack_level; + +            /* start the new group with empty machines */ +            re_build_null_machine(ctx, &cur_machine); +            re_build_null_machine(ctx, &alter_machine); +            break; + +        case ')': +            /* if there's nothing on the stack, ignore this */ +            if (group_stack_level == 0) +                break; + +            /* take a level off the stack */ +            --group_stack_level; + +            /*  +             *   Remove a nesting level.  If we have a pending alternate +             *   expression, build the alternation expression.  This will +             *   leave the entire group expression in alter_machine, +             *   regardless of whether an alternation was in progress or +             *   not.   +             */ +            re_alternate_onto(ctx, &alter_machine, &cur_machine); + +            /* +             *   Create a group machine that encloses the group and marks +             *   it with a group number.  We assigned the group number +             *   when we parsed the open paren, so read that group number +             *   from the stack. +             *    +             *   Note that this will leave 'new_machine' with the entire +             *   group machine.   +             */ +            re_build_group(ctx, &new_machine, &alter_machine, +                           group_stack[group_stack_level].group_id); + +            /* +             *   Pop the stack - restore the alternation and current +             *   machines that were in progress before the group started.  +             */ +            cur_machine = group_stack[group_stack_level].old_cur; +            alter_machine = group_stack[group_stack_level].old_alter; + +            /* +             *   Check the group expression (in new_machine) for postfix +             *   expressions  +             */ +            goto apply_postfix; + +        case '|': +            /*  +             *   Start a new alternation.  This ends the current +             *   alternation; if we have a previous pending alternate, +             *   build an alternation machine out of the previous +             *   alternate and the current machine and move that to the +             *   alternate; otherwise, simply move the current machine to +             *   the pending alternate.  +             */ +            re_alternate_onto(ctx, &alter_machine, &cur_machine); + +            /*  +             *   the alternation starts out with a blank slate, so null +             *   out the current machine  +             */ +            re_build_null_machine(ctx, &cur_machine); +            break; + +        case '%': +            /*  +             *   quoted character - skip the quote mark and see what we +             *   have  +             */ +            ++expr; +            --exprlen; + +            /* check to see if we're at the end of the expression */ +            if (exprlen == 0) +            { +                /*  +                 *   end of the string - ignore it, but undo the extra +                 *   increment of the expression index so that we exit the +                 *   enclosing loop properly  +                 */ +                --expr; +                ++exprlen; +                break; +            } + +            /* see what we have */ +            switch(*expr) +            { +            case '1': +            case '2': +            case '3': +            case '4': +            case '5': +            case '6': +            case '7': +            case '8': +            case '9': +                /* group match - build a new literal group recognizer */ +                re_build_group_matcher(ctx, &new_machine, (int)(*expr - '1')); + +                /* apply any postfix expression to the group recognizer */ +                goto apply_postfix; + +            case '<': +                /* build a beginning-of-word recognizer */ +                re_build_char(ctx, &new_machine, RE_WORD_BEGIN); + +                /* it can't be postfixed - just concatenate it */ +                re_concat_onto(ctx, &cur_machine, &new_machine); +                break; + +            case '>': +                /* build an end-of-word recognizer */ +                re_build_char(ctx, &new_machine, RE_WORD_END); + +                /* it can't be postfixed - just concatenate it */ +                re_concat_onto(ctx, &cur_machine, &new_machine); +                break; + +            case 'w': +                /* word character */ +                re_build_char(ctx, &new_machine, RE_WORD_CHAR); +                goto apply_postfix; + +            case 'W': +                /* non-word character */ +                re_build_char(ctx, &new_machine, RE_NON_WORD_CHAR); +                goto apply_postfix; + +            case 'b': +                /* word boundary */ +                re_build_char(ctx, &new_machine, RE_WORD_BOUNDARY); + +                /* it can't be postfixed */ +                re_concat_onto(ctx, &cur_machine, &new_machine); +                break; + +            case 'B': +                /* not a word boundary */ +                re_build_char(ctx, &new_machine, RE_NON_WORD_BOUNDARY); + +                /* it can't be postfixed */ +                re_concat_onto(ctx, &cur_machine, &new_machine); +                break; + +            default: +                /* build a new literal character recognizer */ +                re_build_char(ctx, &new_machine, *expr); + +                /* apply any postfix expression to the character */ +                goto apply_postfix; +            } +            break; + +        case '.': +            /*  +             *   wildcard character - build a single character recognizer +             *   for the special wildcard symbol, then go check it for a +             *   postfix operator  +             */ +            re_build_char(ctx, &new_machine, RE_WILDCARD); +            goto apply_postfix; +            break; + +        case '[': +            /* range expression */ +            { +                int is_exclusive = FALSE; +                unsigned char set[32]; + +                /* clear out the set of characters in the range */ +                memset(set, 0, sizeof(set)); + +                /* first, skip the open bracket */ +                ++expr; +                --exprlen; + +                /* check to see if starts with the exclusion character */ +                if (exprlen != 0 && *expr == '^') +                { +                    /* skip the exclusion specifier */ +                    ++expr; +                    --exprlen; + +                    /* note it */ +                    is_exclusive = TRUE; +                } + +                /*  +                 *   if the first character is a ']', include it in the +                 *   range  +                 */ +                if (exprlen != 0 && *expr == ']') +                { +                    re_set_bit(set, (int)']'); +                    ++expr; +                    --exprlen; +                } + +                /* +                 *   if the next character is a '-', include it in the +                 *   range  +                 */ +                if (exprlen != 0 && *expr == '-') +                { +                    re_set_bit(set, (int)'-'); +                    ++expr; +                    --exprlen; +                } + +                /* scan the character set */ +                while (exprlen != 0 && *expr != ']') +                { +                    int ch; +                     +                    /* note this character */ +                    ch = (int)(unsigned char)*expr; + +                    /* set it */ +                    re_set_bit(set, ch); + +                    /* skip this character of the expression */ +                    ++expr; +                    --exprlen; + +                    /* check for a range */ +                    if (exprlen != 0 && *expr == '-') +                    { +                        int ch2; +                         +                        /* skip the '-' */ +                        ++expr; +                        --exprlen; +                        if (exprlen != 0) +                        { +                            /* get the other end of the range */ +                            ch2 = (int)(unsigned char)*expr; + +                            /* skip the second character */ +                            ++expr; +                            --exprlen; + +                            /* if the range is reversed, swap it */ +                            if (ch > ch2) +                            { +                                int tmp = ch; +                                ch = ch2; +                                ch2 = tmp; +                            } + +                            /* fill in the range */ +                            for ( ; ch <= ch2 ; ++ch) +                                re_set_bit(set, ch); +                        } +                    } +                } + +                /* create a character range machine */ +                re_build_char_range(ctx, &new_machine, set, is_exclusive); + +                /* apply any postfix operator */ +                goto apply_postfix; +            }             +            break; + +        default: +        normal_char: +            /*  +             *   it's an ordinary character - build a single character +             *   recognizer machine, and then concatenate it onto any +             *   existing machine  +             */ +            re_build_char(ctx, &new_machine, *expr); + +        apply_postfix: +            /* +             *   Check for a postfix operator, and apply it to the machine +             *   in 'new_machine' if present.  In any case, concatenate +             *   the 'new_machine' (modified by a postix operator or not) +             *   to the current machien.   +             */ +            if (exprlen > 1) +            { +                switch(*(expr+1)) +                { +                case '*': +                case '+': +                case '?': +                    /* +                     *   We have a postfix closure operator.  Build a new +                     *   closure machine out of 'new_machine'.   +                     */ +                    { +                        re_machine closure_machine; +                         +                        /* move onto the closure operator */ +                        ++expr; +                        --exprlen; +                         +                        /* build the closure machine */ +                        re_build_closure(ctx, &closure_machine, +                                         &new_machine, *expr); +                         +                        /* replace the original machine with the closure */ +                        new_machine = closure_machine; +                         +                        /*  +                         *   skip any redundant closure symbols, keeping +                         *   only the first one we saw  +                         */ +                        while (exprlen > 1 && (*(expr+1) == '?' +                                               || *(expr+1) == '+' +                                               || *(expr+1) == '*')) +                        { +                            ++expr; +                            --exprlen; +                        } +                    } +                    break; +                     +                default: +                    /* no postfix operator */ +                    break; +                } +            } + +            /* +             *   Concatenate the new machine onto the current machine +             *   under construction.   +             */ +            re_concat_onto(ctx, &cur_machine, &new_machine); +            break; +        } +    } + +    /* complete any pending alternation */ +    re_alternate_onto(ctx, &alter_machine, &cur_machine); + +    /* store the resulting machine in the caller's machine descriptor */ +    *result_machine = alter_machine; + +    /* no errors encountered */ +    return RE_STATUS_SUCCESS;  } -void re_context::note_group(re_group_register *regs, re_state_id id, const char *p) { -	int group_index; - -	/* -	 * Check to see if this is a valid state and it's a group marker - -	 * if not, there's nothing to do  -	 */ -	if (id == RE_STATE_INVALID -		|| !(_tuple_arr[id].flags -			 & (RE_STATE_GROUP_BEGIN | RE_STATE_GROUP_END)) -		|| (group_index = (int)_tuple_arr[id].ch) >= RE_GROUP_REG_CNT) -		return; - -	// It's a valid group marker - note the appropriate register value  -	if ((_tuple_arr[id].flags & RE_STATE_GROUP_BEGIN) != 0) -		regs[group_index].start_ofs = p; -	else -		regs[group_index].end_ofs = p; + +/* ------------------------------------------------------------------------ */ +/* + *   Pattern recognizer  + */ + +/* + *   Note a group position if appropriate  + */ +static void re_note_group(re_context *ctx, re_group_register *regs, +                          re_state_id id, const char *p) +{ +    int group_index; + +    /* +     *   Check to see if this is a valid state and it's a group marker - +     *   if not, there's nothing to do  +     */ +    if (id == RE_STATE_INVALID +        || !(ctx->tuple_arr[id].flags +             & (RE_STATE_GROUP_BEGIN | RE_STATE_GROUP_END)) +        || (group_index = (int)ctx->tuple_arr[id].ch) >= RE_GROUP_REG_CNT) +        return; + +    /* +     *   It's a valid group marker - note the appropriate register value  +     */ +    if ((ctx->tuple_arr[id].flags & RE_STATE_GROUP_BEGIN) != 0) +        regs[group_index].start_ofs = p; +    else +        regs[group_index].end_ofs = p;  } -bool re_context::is_word_char(char c) const { -	return Common::isAlnum(c); +/* + *   Determine if a character is part of a word.  We consider letters and + *   numbers to be word characters.   + */ +static int re_is_word_char(char c) { +	return Common::isAlnum((unsigned char)c);  } -int re_context::match(const char *entire_str, const char *str, size_t origlen, -		const re_machine *machine, re_group_register *regs) { -	re_state_id cur_state; -	const char *p; -	size_t curlen; - -	// start at the machine's initial state -	cur_state = machine->init; - -	// start at the beginning of the string -	p = str; -	curlen = origlen; - -	// note any group involved in the initial state -	note_group(regs, cur_state, p); - -	/*  -	 * if we're starting in the final state, immediately return success -	 * with a zero-length match  -	 */ -	if (cur_state == machine->final) { -		// return success with a zero-length match -		return 0; -	} - -	// run the machine -	for (;;) { -		re_tuple *tuple; - -		// get the tuple for this state -		tuple = &_tuple_arr[cur_state]; - -		// if this is a group state, adjust the group registers -		note_group(regs, cur_state, p); -		 -		// see what kind of state we're in -		if (!(tuple->flags & (RE_STATE_GROUP_BEGIN | RE_STATE_GROUP_END)) -			&& tuple->ch != RE_EPSILON) { -			/* -			 * This is a character or group recognizer state.  If we -			 * match the character or group, continue on to the next -			 * state; otherwise, return failure. -			 */ -			switch(tuple->ch) { -			case RE_GROUP_MATCH_0: -			case RE_GROUP_MATCH_0 + 1: -			case RE_GROUP_MATCH_0 + 2: -			case RE_GROUP_MATCH_0 + 3: -			case RE_GROUP_MATCH_0 + 4: -			case RE_GROUP_MATCH_0 + 5: -			case RE_GROUP_MATCH_0 + 6: -			case RE_GROUP_MATCH_0 + 7: -			case RE_GROUP_MATCH_0 + 8: -			case RE_GROUP_MATCH_0 + 9: { -				int group_num; -				re_group_register *group_reg; -				size_t reg_len; - -				// it's a group - get the group number -				group_num = tuple->ch - RE_GROUP_MATCH_0; -				group_reg = ®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,38 +23,35 @@  #ifndef GLK_TADS_TADS2  #define GLK_TADS_TADS2 -#include "glk/tads/tads2/os.h" -#include "glk/tads/tads2/ler.h" +#include "glk/tads/tads.h" +#include "glk/tads/tads2/error_handling.h" +#include "glk/tads/tads2/appctx.h"  namespace Glk {  namespace TADS {  namespace TADS2 { -/** - * map a native character (read externally) into an internal character +/* + *   Run-time version number    */ -#define cmap_n2i(c) (G_cmap_input[(unsigned char)(c)]) +#define  TADS_RUNTIME_VERSION   "2.5.17" -/** - * map an internal character into a native character (for display) - */ -#define cmap_i2n(c) (G_cmap_output[(unsigned char)(c)]) - -/** - * the full name (for display purposes) of the loaded character set - */ -#define CMAP_LDESC_MAX_LEN  40 - -/** - * Maximum expansion for an HTML entity mapping  - */ -#define CMAP_MAX_ENTITY_EXPANSION  50 +# define TRD_HEAPSIZ  4096 +# define TRD_STKSIZ   200 +# define TRD_UNDOSIZ  (16 * 1024) +# define TDD_HEAPSIZ  4096 +# define TDD_STKSIZ   200 +# define TDD_UNDOSIZ  (16 * 1024) +# define TDD_POOLSIZ  (2 * 1024) +# define TDD_LCLSIZ   0 +# define ERR_TRUS_OS_FIRST    100 +# define ERR_TRUS_OS_LAST      99  /**   * TADS 2 game interpreter   */ -class TADS2 : public OS { +class TADS2 : public TADS {  private:  	// STUBS  	void os_printz(const Common::String &s) {} @@ -62,41 +59,6 @@ private:  		const char *expansion, size_t expansion_len) {}  private:  	/** -	 * \defgroup cmap -	 * @{ -	 */ - -	/** -	 * flag: true -> a character set has been explicitly loaded, so we -	 * should ignore any game character set setting  -	 */ -	bool S_cmap_loaded; - -	/** -	 * input-mapping table - for native character 'n', cmap_input[n] yields -	 * the internal character code  -	 */ -	unsigned char G_cmap_input[256]; - -	/** -	 * output-mapping table - for internal character 'n', cmap_output[n] -	 * yields the output character code  -	 */ -	unsigned char G_cmap_output[256]; - -	/** -	 * the ID of the loaded character set -	 */ -	char G_cmap_id[5]; - -	/** -	 * the full name (for display purposes) of the loaded character set -	 */ -	char G_cmap_ldesc[CMAP_LDESC_MAX_LEN + 1]; - -	/**@}*/ -private: -	/**  	 * \defgroup trd  	 * @{  	 */ @@ -109,55 +71,6 @@ private:  	void trdptf(const char *fmt, ...);  	/**@}*/ - -	/** -	 * \defgroup cmap -	 * @{ -	 */ - -	/** -	 * Initialize the default character mappings.  If no mapping file is to -	 * be read, this function will establish identify mappings that leave -	 * characters untranslated.  -	 */ -	void cmap_init_default(); - -	/** -	 * Load a character map file.  Returns zero on success, non-zero on -	 * failure.  If filename is null, we'll use the default mapping. -	 */ -	int cmap_load(const char *filename); - -	/** -	 * Turn off character translation.  This overrides any game character -	 * set that we find and simply uses the default translation.  -	 */ -	void cmap_override(void); - -	/** -	 * Set the game's internal character set.  This should be called when a -	 * game is loaded, and the game specifies an internal character set.  If -	 * there is no character map file explicitly loaded, we will attempt to -	 * load a character mapping file that maps this character set to the -	 * current native character set.  Signals an error on failure.  This -	 * routine will succeed (without doing anything) if a character set has -	 * already been explicitly loaded, since an explicitly-loaded character -	 * set overrides the automatic character set selection that we attempt -	 * when loading a game. -	 *  -	 * argv0 must be provided so that we know where to look for our mapping -	 * file on systems where mapping files are stored in the same directory -	 * as the TADS executables.   -	 */ -	void cmap_set_game_charset(errcxdef *errctx, const char *internal_id, -		const char *internal_ldesc, const char *argv0); - -	/** -	 * Internal routine to load a character map from a file  -	 */ -	int cmap_load_internal(const char *filename); - -	/**@}*/  public:  	/**  	 * Constructor @@ -175,7 +88,7 @@ public:  	virtual InterpreterType getInterpreterType() const override { return INTERPRETER_TADS2; }  }; -typedef TADS2 appctxdef; +//typedef TADS2 appctxdef;  } // End of namespace TADS2  } // End of namespace TADS diff --git a/engines/glk/tads/tads2/tads2_cmap.cpp b/engines/glk/tads/tads2/tads2_cmap.cpp deleted file mode 100644 index 68945f7336..0000000000 --- a/engines/glk/tads/tads2/tads2_cmap.cpp +++ /dev/null @@ -1,268 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "glk/tads/tads2/tads2.h" -#include "glk/tads/tads2/types.h" - -namespace Glk { -namespace TADS { -namespace TADS2 { - -/** - * Signatures for character map files.  The signature is stored at the - * beginning of the file. - */ -// single-byte character map version 1.0.0 -#define CMAP_SIG_S100  "TADS2 charmap S100\n\r\01a" - -void TADS2::cmap_init_default() { -	size_t i; - -	// initialize the input table -	for (i = 0 ; i < sizeof(G_cmap_input)/sizeof(G_cmap_input[0]) ; ++i) -		G_cmap_input[i] = (unsigned char)i; - -	// initialize the output table -	for (i = 0 ; i < sizeof(G_cmap_output)/sizeof(G_cmap_output[0]) ; ++i) -		G_cmap_output[i] = (unsigned char)i; - -	// we have a null ID -	memset(G_cmap_id, 0, sizeof(G_cmap_id)); - -	// indicate that it's the default -	strcpy(G_cmap_ldesc, "(native/no mapping)"); - -	// note that we have no character set loaded -	S_cmap_loaded = false; -} - -int TADS2::cmap_load_internal(const char *filename) { -	osfildef *fp; -	static char sig1[] = CMAP_SIG_S100; -	char buf[256]; -	uchar lenbuf[2]; -	size_t len; -	int sysblk; - -	// if there's no mapping file, use the default mapping -	if (filename == 0) { -		// initialize with the default mapping -		cmap_init_default(); - -		// return success -		return 0; -	} - -	// open the file -	fp = osfoprb(filename, OSFTCMAP); -	if (fp == 0) -		return 1; - -	// check the signature -	if (osfrb(fp, buf, sizeof(sig1)) -		|| memcmp(buf, sig1, sizeof(sig1)) != 0) { -		osfcls(fp); -		return 2; -	} - -	// load the ID -	G_cmap_id[4] = '\0'; -	if (osfrb(fp, G_cmap_id, 4)) { -		osfcls(fp); -		return 3; -	} - -	// load the long description -	if (osfrb(fp, lenbuf, 2) -		|| (len = osrp2(lenbuf)) > sizeof(G_cmap_ldesc) -		|| osfrb(fp, G_cmap_ldesc, len)) { -		osfcls(fp); -		return 4; -	} - -	// load the two tables - input, then output -	if (osfrb(fp, G_cmap_input, sizeof(G_cmap_input)) -		|| osfrb(fp, G_cmap_output, sizeof(G_cmap_output))) { -		osfcls(fp); -		return 5; -	} - -	// read the next section header -	if (osfrb(fp, buf, 4)) { -		osfcls(fp); -		return 6; -	} - -	// if it's "SYSI", read the system information string -	if (!memcmp(buf, "SYSI", 4)) { -		// read the length prefix, then the string -		if (osfrb(fp, lenbuf, 2) -			|| (len = osrp2(lenbuf)) > sizeof(buf) -			|| osfrb(fp, buf, len)) { -			osfcls(fp); -			return 7; -		} - -		// we have a system information block -		sysblk = true; -	} else { -		// there's no system information block -		sysblk = false; -	} - -	/* -	 * call the OS code, so that it can do any system-dependent -	 * initialization for the new character mapping -	 */ -	os_advise_load_charmap(G_cmap_id, G_cmap_ldesc, sysblk ? buf : ""); - -	// read the next section header -	if (sysblk && osfrb(fp, buf, 4)) { -		osfcls(fp); -		return 8; -	} - -	// see if we have an entity list -	if (!memcmp(buf, "ENTY", 4)) { -		// read the entities -		for (;;) { -			unsigned int cval; -			char expansion[CMAP_MAX_ENTITY_EXPANSION]; - -			// read the next item's length and character value -			if (osfrb(fp, buf, 4)) { -				osfcls(fp); -				return 9; -			} - -			// decode the values -			len = osrp2(buf); -			cval = osrp2(buf+2); - -			// if we've reached the zero marker, we're done -			if (len == 0 && cval == 0) -				break; - -			// read the string -			if (len > CMAP_MAX_ENTITY_EXPANSION -				|| osfrb(fp, expansion, len)) { -				osfcls(fp); -				return 10; -			} - -			// tell the output code about the expansion -			tio_set_html_expansion(cval, expansion, len); -		} -	} - -	/* -	 * ignore anything else we find - if the file format is updated to -	 * include extra information in the future, and this old code tries -	 * to load an updated file, we'll just ignore the new information, -	 * which should always be placed after the "SYSI" block (if present) -	 * to ensure compatibility with past versions (such as this code) -	 */ -	// no problems - close the file and return success -	osfcls(fp); -	return 0; -} - -int TADS2::cmap_load(const char *filename) { -	int err; - -	// try loading the file -	if ((err = cmap_load_internal(filename)) != 0) -		return err; - -	/* -	 * note that we've explicitly loaded a character set, if they named -	 * a character set (if not, this simply establishes the default -	 * setting, so we haven't explicitly loaded anything) -	 */ -	if (filename != nullptr) -		S_cmap_loaded = true; - -	// success -	return 0; -} - -void TADS2::cmap_override() { -	// apply the default mapping -	cmap_init_default(); - -	/* -	 * pretend we have a character map loaded, so that we don't try to -	 * load another one if the game specifies a character set -	 */ -	S_cmap_loaded = true; -} - -void TADS2::cmap_set_game_charset(errcxdef *ec, const char *internal_id, -		const char *internal_ldesc, const char *argv0) { -	char filename[OSFNMAX]; - -	/* -	 * If a character set is already explicitly loaded, ignore the -	 * game's character set - the player asked us to use a particular -	 * mapping, so ignore what the game wants.  (This will probably -	 * result in incorrect display of non-ASCII character values, but -	 * the player is most likely to use this to avoid errors when an -	 * appropriate mapping file for the game is not available.  In this -	 * case, the player informs us by setting the option that he or she -	 * knows and accepts that the game will not look exactly right.) -	 */ -	if (S_cmap_loaded) -		return; - -	/* -	 * ask the operating system to name the mapping file -- this routine -	 * will determine, if possible, the current native character set, -	 * and apply a system-specific naming convention to tell us what -	 * mapping file we should open -	 */ -	os_gen_charmap_filename(filename, internal_id, argv0); - -	// try loading the mapping file -	if (cmap_load_internal(filename)) -		errsig2(ec, ERR_CHRNOFILE, -				ERRTSTR, errstr(ec, filename, strlen(filename)), -				ERRTSTR, errstr(ec, internal_ldesc, strlen(internal_ldesc))); - -	/** -	 * We were successful - the game's internal character set is now -	 * mapped to the current native character set.  Even though we -	 * loaded an ldesc from the mapping file, forget that and store the -	 * internal ldesc that the game specified.  The reason we do this is -	 * that it's possible that the player will dynamically switch native -	 * character sets in the future, at which point we'll need to -	 * re-load the mapping table, which could raise an error if a -	 * mapping file for the new character set isn't available.  So, we -	 * may need to provide the same explanation later that we needed to -	 * provide here.  Save the game's character set ldesc for that -	 * eventuality, since it describes exactly what the *game* wanted. -	 */ -	strcpy(G_cmap_ldesc, internal_ldesc); -} - -} // End of namespace TADS2 -} // End of namespace TADS -} // End of namespace Glk diff --git a/engines/glk/tads/tads2/text_io.h b/engines/glk/tads/tads2/text_io.h new file mode 100644 index 0000000000..33a914e0ba --- /dev/null +++ b/engines/glk/tads/tads2/text_io.h @@ -0,0 +1,233 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_TADS_TADS2_TEXT_IO +#define GLK_TADS_TADS2_TEXT_IO + +/* + * Text I/O interface + * + * Formatted text input and output interface definition + */ + +#include "glk/tads/tads.h" +#include "glk/tads/tads2/error_handling.h" +#include "glk/tads/tads2/run.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* forward decls */ +struct runcxdef; + +/** + * Text i/o context + */ +struct tiocxdef { +    errcxdef *tiocxerr;                           /* error handling context */ +}; + +/** + *   Initialize the output formatter subsystem.  This must be called once + *   at startup.  + */ +void out_init(); + + +/* redirect all tioxxx routines to TADS v1.x outxxx equivalents */ +#define tioflushn(ctx, nl) outflushn(nl) +#define tioflush(ctx)      outflush() +#define tioblank(ctx)      outblank() +#define tioreset(ctx)      outreset() +#define tiogets(ctx, prompt, str, siz) getstring(prompt, str, siz) +#define tioputs(ctx, str)  outformat(str) +#define tioputslen(ctx, str, len) outformatlen(str, len) +#define tiocaps(ctx)       outcaps() +#define tionocaps(ctx)     outnocaps() +#define tioshow(ctx)       outshow() +#define tiohide(ctx)       outhide() +#define tioscore(ctx, s1, s2) os_score(s1, s2) +#define tiostrsc(ctx, s)   os_strsc(s) + +/* set up format strings in output subsystem */ +void tiosetfmt(tiocxdef *ctx, runcxdef *rctx, uchar *fmtbase, +               uint fmtlen); + +/* tell tio subsystem the current actor */ +void tiosetactor(tiocxdef *ctx, objnum actor); + +/* get the current tio subsystem actor */ +objnum tiogetactor(tiocxdef *ctx); + +/* turn output capture on/off */ +void tiocapture(tiocxdef *tioctx, mcmcxdef *memctx, int flag); + +/* get the capture object handle */ +mcmon tiogetcapture(tiocxdef *ctx); + +/* get the amount of text captured */ +uint tiocapturesize(tiocxdef *ctx); + +/* format a length-prefixed (runtime-style) string to the display */ +void outfmt(tiocxdef *ctx, uchar *txt); + +/* format a null-terminated (C-style) string to the display */ +int outformat(char *s); + +/* format a counted-length string, which may not be null-terminated */ +int outformatlen(char *s, uint len); + +/* flush output, with specified newline mode */ +void outflushn(int nl); + +/* flush output */ +void outflush(void); + +/* reset output state */ +void outreset(void); + +/*  + *   Get a string from the keyboard.  Returns non-zero if an error occurs + *   (in particular, if no more input is available from the keyboard), + *   zero on success.   + */ +int getstring(char *prompt, char *buf, int bufl); + +/* set capitalize-next-character mode on/off */ +void outcaps(void); +void outnocaps(void); + +/* open/close output log file */ +int tiologopn(tiocxdef *ctx, char *fn); +int tiologcls(tiocxdef *ctx); + +/* + *   Write text explicitly to the log file.  This can be used to add + *   special text (such as prompt text) that would normally be suppressed + *   from the log file.  When more mode is turned off, we don't + *   automatically copy text to the log file; any text that the caller + *   knows should be in the log file during times when more mode is turned + *   off can be explicitly added with this function. + *    + *   If nl is true, we'll add a newline at the end of this text.  The + *   caller should not include any newlines in the text being displayed + *   here.   + */ +void out_logfile_print(char *txt, int nl); + + +/* + *   Check output status.  Indicate whether output is currently hidden, + *   and whether any hidden output has occurred.  + */ +void outstat(int *hidden, int *output_occurred); + +/* hide/show output */ +void outhide(void); +int outshow(void); + +/* set the flag to indicate that output has occurred */ +void outsethidden(void); + +/* write a blank line */ +void outblank(void); + +/* start/end watchpoint evaluation */ +void outwx(int flag); + +/* Begin/end capturing */ +void tiocapture(tiocxdef *tioctx, mcmcxdef *memctx, int flag); + +/* clear all captured output */ +void tioclrcapture(tiocxdef *tioctx); + +/*  + *   clear captured output back to a given point -- this can be used to + *   remove captured output in an inner capture from an enclosing capture  + */ +void tiopopcapture(tiocxdef *tioctx, uint orig_size); + +/* get the object handle of the captured output */ +mcmon tiogetcapture(tiocxdef *ctx); + +/* get the amount of text captured */ +uint tiocapturesize(tiocxdef *ctx); + +/* turn MORE mode on or off */ +int setmore(int state); + +/* explicitly activate the "MORE" prompt */ +void out_more_prompt(); + +/* + *   QA controller functions + */ +int qasopn(char *scrnam, int quiet); +void qasclose(void); +char *qasgets(char *buf, int bufl); + +/* + *   Set an HTML entity expansion.  This is called during initialization + *   when we read a character mapping table that includes HTML entity + *   expansions.  The HTML run-time uses its own expansion mechanism, so + *   it will ignore this information.  The standard character-mode TADS + *   run-time, however, uses this information to map HTML entities to the + *   local character set.  + */ +void tio_set_html_expansion(unsigned int html_char_val, +                            const char *expansion, size_t expansion_len); + +/* check for HTML mode - returns true if an "\H+" sequence is active */ +int tio_is_html_mode(); + +/* set the user output filter function */ +void out_set_filter(objnum filter_fn); + +/* set the double-space mode */ +void out_set_doublespace(int dbl); + +/* + *   Ask for a filename, using a system-defined dialog (via os_askfile) if + *   possible.  Uses the same interface as os_askfile(), which we will + *   call directly for graphical implementations.  We'll use formatted + *   text for text-only implementations.   + */ +int tio_askfile(const char *prompt, char *fname_buf, int fname_buf_len, +                int prompt_type, os_filetype_t file_type); + +/* + *   Display a dialog, using a system-defined dialog (via os_input_dialog) + *   if possible.  Uses the same interface as os_input_dialog(), which we + *   will call directly for graphical implementations.  We'll use + *   formatted text for text-only implementations.   + */ +int tio_input_dialog(int icon_id, const char *prompt, int standard_button_set, +                     const char **buttons, int button_count, +                     int default_index, int cancel_index); + + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/tokenizer.cpp b/engines/glk/tads/tads2/tokenizer.cpp new file mode 100644 index 0000000000..59dceebaf6 --- /dev/null +++ b/engines/glk/tads/tads2/tokenizer.cpp @@ -0,0 +1,33 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/tads/tads2/tokenizer.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +// TODO: Rest of tokenizer stuff + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk diff --git a/engines/glk/tads/tads2/tokenizer.h b/engines/glk/tads/tads2/tokenizer.h new file mode 100644 index 0000000000..50a8492cd3 --- /dev/null +++ b/engines/glk/tads/tads2/tokenizer.h @@ -0,0 +1,473 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_TADS_TADS2_TOKENIZER +#define GLK_TADS_TADS2_TOKENIZER + +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/error_handling.h" +#include "glk/tads/tads2/line_source.h" +#include "glk/tads/tads2/memory_cache.h" + +namespace Glk { +namespace TADS { +namespace TADS2 { + +/* number of entries in hash table - must be power of 2 */ +#define TOKHASHSIZE 256 + +/* symbol types */ +#define TOKSTUNK      0                  /* unknown symbol, not yet defined */ +#define TOKSTFUNC     1                 /* function; value is object number */ +#define TOKSTOBJ      2                   /* object; value is object number */ +#define TOKSTPROP     3               /* property; value is property number */ +#define TOKSTLOCAL    4             /* a local variable or formal parameter */ +#define TOKSTSELF     5                         /* the pseudo-object "self" */ +#define TOKSTBIFN     6                              /* a built-in function */ +#define TOKSTFWDOBJ   7                        /* forward-referenced object */ +#define TOKSTFWDFN    8                        /* forward-referenced object */ +#define TOKSTINHERIT  9                    /* the pseudo-object "inherited" */ +#define TOKSTEXTERN  10                             /* an external function */ +#define TOKSTKW      11                   /* keyword; value is token number */ +#define TOKSTLABEL   12                                  /* statement label */ +#define TOKSTARGC    13                       /* 'argcount' pseudo-variable */ +#define TOKSTPROPSPEC 14                 /* speculative evaluation property */ + +/* token types */ +#define TOKTEOF     1 + +/* binary operators - keep these together (see prsbopl[] in prs.c) */ +#define TOKTPLUS    2 +#define TOKTMINUS   3 +#define TOKTDIV     4 +#define TOKTTIMES   5 +#define TOKTNOT     6                                         /* ! or "not" */ +#define TOKTEQ      7 +#define TOKTNE      8 +#define TOKTGT      9 +#define TOKTGE      10 +#define TOKTLT      11 +#define TOKTLE      12 +#define TOKTMOD     13 +#define TOKTBAND    14 +#define TOKTBOR     15 +#define TOKTXOR     16 +#define TOKTSHL     17 +#define TOKTSHR     18 +#define TOKTTILDE   30 + +/*  + *   special 'dot' replacement for speculative evaluation mode -- this is + *   strictly for marking parse tree nodes, and has the same meaning in a + *   parse tree node as a regular TOKTDOT, but generates code that can't + *   call methods  + */ +#define TOKTDOTSPEC 31 + +/* special node marker for explicit superclass inheritance nodes */ +#define TOKTEXPINH  32 + +#define TOKTLPAR    50                                                 /* ( */ +#define TOKTRPAR    51                                                 /* ) */ +#define TOKTCOLON   52 +#define TOKTDSTRING 53                           /* string in double quotes */ +#define TOKTSSTRING 54                           /* string in single quotes */ +#define TOKTNUMBER  55 +#define TOKTSYMBOL  56 +#define TOKTINVALID 57                             /* invalid lexical token */ +#define TOKTLBRACK  58                                                 /* [ */ +#define TOKTRBRACK  59                                                 /* ] */ +#define TOKTLBRACE  60                                                 /* { */ +#define TOKTRBRACE  61                                                 /* } */ +#define TOKTSEM     62                                                 /* ; */ +#define TOKTCOMMA   63 +#define TOKTDOT     64                                                 /* . */ +#define TOKTOR      65                                         /* | or "if" */ +#define TOKTAND     66                                        /* & or "and" */ +#define TOKTIF      67                                          /* keywords */ +#define TOKTELSE    68 +#define TOKTWHILE   69 +#define TOKTFUNCTION 70 +#define TOKTRETURN  71 +#define TOKTLOCAL   72 +#define TOKTOBJECT  73 +#define TOKTBREAK   74 +#define TOKTCONTINUE 75 +#define TOKTLIST    76                                            /* a list */ +#define TOKTNIL     77 +#define TOKTTRUE    78 +#define TOKTPASS    79 +#define TOKTCLASS   80 +#define TOKTEXIT    81 +#define TOKTABORT   82 +#define TOKTASKDO   83 +#define TOKTASKIO   84 +#define TOKTPOUND   85                                                 /* # */ +#define TOKTQUESTION 86                                                /* ? */ +#define TOKTCOMPOUND 87 +#define TOKTIOSYN   88 +#define TOKTDOSYN   89 +#define TOKTEXTERN  90 +#define TOKTFORMAT  91 +#define TOKTDO      92 +#define TOKTFOR     93 +#define TOKTNEW     94 +#define TOKTDELETE  95 + +/* assignment operators - keep these together */ +#define TOKTINC     150                                               /* ++ */ +#define TOKTPOSTINC 151                              /* MUST BE TOKTINC + 1 */ +#define TOKTDEC     152                                               /* -- */ +#define TOKTPOSTDEC 153                              /* MUST BE TOKTDEC + 1 */ +#define TOKTPLEQ    154                                               /* += */ +#define TOKTMINEQ   155                                               /* -= */ +#define TOKTDIVEQ   156                                               /* /= */ +#define TOKTTIMEQ   157                                               /* *= */ +#define TOKTASSIGN  158                                /* simple assignment */ +#define TOKTMODEQ   159                     /* %= (mod and assign) operator */ +#define TOKTBANDEQ  160                                               /* &= */ +#define TOKTBOREQ   161                                               /* |= */ +#define TOKTXOREQ   162                              /* ^= (xor and assign) */ +#define TOKTSHLEQ   163                      /* <<= (shift left and assign) */ +#define TOKTSHREQ   164                      /* >>= (shift right and assign */ + +#define TOKTSWITCH  200 +#define TOKTCASE    201 +#define TOKTDEFAULT 202 +#define TOKTGOTO    203 +#define TOKTELLIPSIS 204                                             /* ... */ +#define TOKTSPECIAL 205                                   /* "specialWords" */ +#define TOKTREPLACE 206                                          /* replace */ +#define TOKTMODIFY  207                                           /* modify */ + +#define TOKTEQEQ    208                                /* the '==' operator */ +#define TOKTPOINTER 209                                  /* the -> operator */ + +/* the longest a symbol name can be */ +#define TOKNAMMAX 39 + +/* symbol table entry */ +struct toksdef { +    uchar    tokstyp;                                 /* type of the symbol */ +    uchar    tokshsh;                               /* hash value of symbol */ +    ushort   toksval;              /* value of the symbol (depends on type) */ +    ushort   toksfr;               /* frame offset of symbol (for debugger) */ +    uchar    tokslen;                        /* length of the symbol's name */ +    char     toksnam[TOKNAMMAX];                          /* name of symbol */ +}; + +/* symbol table entry without 'name' portion - for allocation purposes */ +struct toks1def { +    uchar    tokstyp; +    uchar    tokshsh; +    ushort   toksval; +    ushort   toksfr; +    uchar    tokslen; +    char     toksnam[1]; +}; + +/* generic symbol table object - other symbol tables are subclasses */ +struct toktdef { +    void     (*toktfadd)(toktdef *tab, char *name, int namel, int typ, +                         int val, int hash);                  /* add symbol */ +    int      (*toktfsea)(toktdef *tab, char *name, int namel, int hash, +                         toksdef *ret);              /* search symbol table */ +    void     (*toktfset)(toktdef *tab, toksdef *sym); +                             /* update val & typ of symbol to those in *sym */ +    void     (*toktfeach)(toktdef *tab, +                          void (*fn)(void *ctx, toksdef *sym), +                          void *fnctx);            /* call fn for each sym */ +    toktdef   *toktnxt;                 /* next symbol table to be searched */ +    errcxdef  *tokterr;                           /* error handling context */ +}; + +/* maximum number of pools (TOKTSIZE bytes each) for symbols */ +#define TOKPOOLMAX 128 + +/* pointer to a symbol in a hashed symbol table */ +struct tokthpdef { +    mcmon  tokthpobj;                /* cache manager object number of page */ +    uint   tokthpofs;                  /* offset within page of this symbol */ +}; + +/* extended symbol entry in a hashed symbol table */ +struct tokshdef { +    tokthpdef tokshnxt;              /* pointer to next symbol in the table */ +    toksdef   tokshsc;                  /* superclass - normal symbol entry */ +}; + +/* hashing symbol table (subclass of generic symbol table) */ +struct tokthdef { +    toktdef   tokthsc;              /* generic symbol table superclass data */ +    mcmcxdef *tokthmem;                           /* memory manager context */ +    tokthpdef tokthhsh[TOKHASHSIZE];                          /* hash table */ +    uint      tokthpcnt;            /* number of memory pools for toksdef's */ +    mcmon     tokthpool[TOKPOOLMAX];          /* memory pools for toksdef's */ +    uint      tokthfinal[TOKPOOLMAX];        /* actual sizes of these pools */ +    uchar    *tokthcpool;                           /* current pool pointer */ +    ushort    tokthsize;               /* remaining size of top memory pool */ +    ushort    tokthofs;             /* allocation offset in top memory pool */ +}; + +/* size of toksdef pools to allocate for hashed symbol tables */ +#define TOKTHSIZE 4096 + +/* + *   Linear cache-object-embedded symbol table.  This type of symbol + *   table is used for frame parameter/local variable lists.  It is best + *   for small tables, because it isn't broken up into hash buckets, so it + *   is searched linearly.  As a result, it's small enough to be embedded + *   in code. + */ +struct toktldef { +    toktdef   toktlsc;              /* generic symbol table superclass data */ +    uchar    *toktlptr;                      /* base of linear symbol table */ +    uchar    *toktlnxt;                          /* next free byte in table */ +    uint      toktlcnt;                   /* number of objects in the table */ +    uint      toktlsiz;                     /* bytes remaining in the table */ +}; + +struct tokdef { +    int      toktyp;                                   /* type of the token */ +    int      toklen;           /* length of token text, if a symbolic token */ +    long     tokval;                        /* numeric value, if applicable */ +    ushort   tokofs; +    uint     tokhash;              /* token hash value, if a symbolic token */ +    char     toknam[TOKNAMMAX+1];     /* text of token, if a symbolic token */ +    toksdef  toksym;                    /* symbol from table matching token */ +}; + +/* special character sequence */ +#define TOKSCMAX  3            /* maximum length of a special char sequence */ +struct tokscdef { +    tokscdef *tokscnxt;          /* next sequence with same first character */ +    int       toksctyp;             /* token type corresponding to sequence */ +    int       toksclen;                           /* length of the sequence */ +    char      tokscstr[TOKSCMAX+1];                  /* the sequence itself */ +}; + +/* + *   Compare a special character sequence - for efficiency, define + *   something special for the maximum length available (TOKSCMAX). + *   Note that the first character will always be equal, or the + *   string wouldn't even get to the point of being tested by this + *   macro. + */ +#if TOKSCMAX == 3 +# define toksceq(str1, str2, len1, len2) \ +    ((len2) >= (len1) \ +     && ((len1) == 1 \ +         || ((str1)[1] == (str2)[1] \ +             && ((len1) == 2 \ +                 || (str1)[2] == (str2)[2])))) +#endif /* TOKSCMAX == 3 */ +#ifndef toksceq +# define toksceq(str1, str2, len) (!memcmp(str1, str2, (size_t)(len))) +#endif /* toksceq */ + +/* special character sequence list table entry */ +struct tokldef { +    int  tokltyp;                   /* token type corresponding to sequence */ +    char toklstr[TOKSCMAX+1];                   /* the text of the sequence */ +}; + +/* include path structure */ +struct tokpdef { +    tokpdef *tokpnxt;                                  /* next path in list */ +    int      tokplen;                           /* length of directory name */ +    char     tokpdir[1];                             /* directory to search */ +}; + +/* #define symbol structure */ +struct tokdfdef { +    tokdfdef *nxt;                    /* next symbol in the same hash chain */ +    char     *nm;                                     /* name of the symbol */ +    int       len;                                  /* length of the symbol */ +    int       explen;                            /* length of the expansion */ +    char      expan[1];                                 /* expansion buffer */ +}; + +/* #define hash table information */ +#define TOKDFHSHSIZ   64 +#define TOKDFHSHMASK  63 + +/* maximum #if nesting */ +#define TOKIFNEST     64 + +/* #if state */ +#define TOKIF_IF_YES    1             /* processing a true #if/#ifdef block */ +#define TOKIF_IF_NO     2            /* processing a false #if/#ifdef block */ +#define TOKIF_ELSE_YES  3                   /* processing a true #else part */ +#define TOKIF_ELSE_NO   4                  /* processing a false #else part */ + +/* maximum macro expansion nesting */ +#define TOKMACNEST  20 + +/* lexical analysis context */ +struct tokcxdef { +    errcxdef *tokcxerr;                           /* error handling context */ +    mcmcxdef *tokcxmem;                            /* cache manager context */ +    struct    dbgcxdef *tokcxdbg;                       /* debugger context */ +    lindef   *tokcxlin;                                      /* line source */ +    tokpdef  *tokcxinc;                        /* head of include path list */ +    toktdef  *tokcxstab;              /* current head of symbol table chain */ +    void     *tokcxscx;    /* context for string storage callback functions */ +    ushort  (*tokcxsst)(void *ctx); +               /* start storing a string; return offset of string's storage */ +    void    (*tokcxsad)(void *ctx, char *str, ushort len); +                                              /* add characters to a string */ +    void    (*tokcxsend)(void *ctx);               /* finish storing string */ +    char     *tokcxmsav[TOKMACNEST]; /* saved positions for macro expansion */ +    ushort    tokcxmsvl[TOKMACNEST];   /* saved lengths for macro expansion */ +    int       tokcxmlvl;                             /* macro nesting level */ +    int       tokcxflg;                                            /* flags */ +#   define    TOKCXFINMAC    0x01         /* doing <<expr>> macro expansion */ +#   define    TOKCXCASEFOLD  0x02              /* fold upper and lower case */ +#   define    TOKCXFCMODE    0x04                /* parse using C operators */ +#   define    TOKCXF_EMBED_PAREN_PRE 0x08        /* embedded expr - did '(' */ +#   define    TOKCXF_EMBED_PAREN_AFT 0x10    /* embedded expr - must do ')' */ +#   define    TOKCXFLIN2     0x20                 /* new-style line records */ +    tokdef    tokcxcur;                                    /* current token */ +    char     *tokcxbuf;                            /* buffer for long lines */ +    ushort    tokcxbsz;                         /* size of long line buffer */ +    char     *tokcxptr;                         /* pointer into line source */ +    ushort    tokcxlen;                         /* length of text in buffer */ +    uchar     tokcxinx[256];                   /* special character indices */ +    tokdfdef *tokcxdf[TOKDFHSHSIZ];       /* hash table for #define symbols */ +    int       tokcxifcnt;           /* number of #endif's we expect to find */ +    char      tokcxif[TOKIFNEST];       /* #if state for each nesting level */ +    int       tokcxifcur;             /* current #if state, obeying nesting */ +    struct    linfdef  *tokcxhdr;    /* list of previously included headers */ +    tokscdef *tokcxsc[1];                        /* special character table */ +}; + + +/* allocate and initialize a lexical analysis context */ +tokcxdef *tokcxini(errcxdef *errctx, mcmcxdef *mctx, tokldef *sctab); + +/* add an include path to a token handling context */ +void tokaddinc(tokcxdef *ctx, char *path, int pathlen); + +/* compute the hash value of a string */ +uint tokhsh(char *nam); + +/*  + *   Fold case of a token if we're in case-insensitive mode.  This should + *   be called any time a token is constructed artificially; it need not + *   be used the token is read through the tokenizer, because the + *   tokenizer will always adjust a token as needed before returning it.  + */ +void tok_case_fold(tokcxdef *ctx, tokdef *tok); + +/* initialize a hashed symbol table */ +void tokthini(errcxdef *errctx, mcmcxdef *memctx, toktdef *toktab1); + +/* add a symbol to a hashed symbol table */ +void tokthadd(toktdef *toktab, char *name, int namel, +              int typ, int val, int hash); + +/* update a symbol in a hashed symbol table */ +void tokthset(toktdef *toktab, toksdef *sym); + +/* search a hashed symbol table for a symbol */ +int tokthsea(toktdef *tab, char *name, int namel, int hash, +             toksdef *ret); + +/* call a function for each symbol in a hashed symbol table */ +void toktheach(toktdef *tab, void (*cb)(void *ctx, toksdef *sym), +               void *ctx); + +/* find a symbol given type and value */ +int tokthfind(toktdef *tab, int typ, uint val, toksdef *sym); + +/* initialize a linear symbol table */ +void toktlini(errcxdef *errctx, toktldef *toktab, +              uchar *mem, uint siz); + +/* add a symbol to a linear symbol table */ +void toktladd(toktdef *toktab, char *name, int namel, +              int typ, int val, int hash); +                 +/* search a linear symbol table */ +int toktlsea(toktdef *tab, char *name, int namel, int hash, +             toksdef *ret); + +/* update a symbol in a linear symbol table */ +void toktlset(toktdef *toktab, toksdef *sym); + +/* call a function for each symbol in a local symbol table */ +void toktleach(toktdef *tab, void (*cb)(void *ctx, toksdef *sym), +               void *ctx); + +/* delete all symbols from a linear table */ +void toktldel(toktldef *tab); + +/* get next token, removing it from input stream */ +int toknext(tokcxdef *ctx); + +/* general function to get/peek at next token */ +int tokget1(tokcxdef *ctx, tokdef *tok, int consume); + +/* add a symbol to the #define symbol table */ +void tok_add_define(tokcxdef *ctx, char *sym, int len, +                    char *expan, int explen); + +/*  + *   add a symbol to the #define symbol table, folding case if we're + *   operating in case-insensitive mode  + */ +void tok_add_define_cvtcase(tokcxdef *ctx, char *sym, int len, +                            char *expan, int explen); + +/* add a symbol to the #define symbol table as a number */ +void tok_add_define_num_cvtcase(tokcxdef *ctx, char *sym, int len, int num); + +/* undefine a #define symbol */ +void tok_del_define(tokcxdef *ctx, char *sym, int len); + +/* read/write preprocessor symbols from/to a file */ +void tok_read_defines(tokcxdef *ctx, osfildef *fp, errcxdef *ec); + +/* write preprocessor state to a file */ +void tok_write_defines(tokcxdef *ctx, osfildef *fp, errcxdef *ec); + + +/* determine if a char is a valid non-initial character in a symbol name */ +#define TOKISSYM(c) \ +    (isalpha((uchar)(c)) || isdigit((uchar)(c)) || (c)=='_' || (c)=='$') + +/* numeric conversion and checking macros */ +#define TOKISHEX(c) \ +    (isdigit((uchar)(c))||((c)>='a'&&(c)<='f')||((c)>='A'&&(c)<='F')) +#define TOKISOCT(c) \ +    (isdigit((uchar)(c))&&!((c)=='8'||(c)=='9')) + +#define TOKHEX2INT(c) \ +    (isdigit((uchar)c)?(c)-'0':((c)>='a'?(c)-'a'+10:(c)-'A'+10)) +#define TOKOCT2INT(c) ((c)-'0') +#define TOKDEC2INT(c) ((c)-'0') + +} // End of namespace TADS2 +} // End of namespace TADS +} // End of namespace Glk + +#endif diff --git a/engines/glk/tads/tads2/types.h b/engines/glk/tads/tads2/types.h deleted file mode 100644 index 5da759fda7..0000000000 --- a/engines/glk/tads/tads2/types.h +++ /dev/null @@ -1,190 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifndef GLK_TADS_TADS2_TYPES -#define GLK_TADS_TADS2_TYPES - -#include "common/stream.h" - -namespace Glk { -namespace TADS { -namespace TADS2 { - -typedef unsigned char uchar; -typedef Common::SeekableReadStream osfildef; - -/** - * Allocate a memory block - */ -#define mchalo(CTX, SIZE, COMMENT) ((byte *)new byte[SIZE]) - -/** - * Free a memory block - */ -#define mchfre(PTR) delete[] (byte *)PTR - -/** - * Close a file - */ -#define osfcls(FP) delete FP - -/** - * Read from a file - */ -#define osfrb(FP, BUF, SIZE) FP->read(BUF, SIZE) - -/** - * Read a 16-bit integer from memory - */ -#define osrp2(MEM) READ_LE_UINT16(MEM) - -/** - * The character (or characters) which mark the beginning of a special fileref string. - * The important thing is that the string be one that is either not allowed in - * filenames on your platform or is unlikely to be the first part of a filename. - */ -#define OSS_FILEREF_STRING_PREFIX ":" - -/** - * The character (or characters) which mark the end of a special fileref string. - * Using this and OSS_FILEREF_STRING_PREFIX, you should be able to come up with - * something which forms an invalid filename - */ -#define OSS_FILEREF_STRING_SUFFIX "" - -/** - * Maximum length of status line text - */ -#define OSS_STATUS_STRING_LEN 80 - -/** - * Maximum size for filenames - */ -#define OSFNMAX 1024 - -/** - * Important note: do not change these values when porting TADS.  These - * values can be used by games, so they must be the same on all platforms. - */ -enum { -	OS_AFP_OPEN = 1,			///< choose an existing file to open for reading -	OS_AFP_SAVE = 2				///< choose a filename for saving to a file -}; - -/** - * File types.These type codes are used when opening or creating a file, - * so that the OS routine can set appropriate file system metadata - * to describe or find the file type. - * - * The type os_filetype_t is defined for documentary purposes; it's - * always just an int. - */ -enum os_filetype_t { -	OSFTGAME  =  0,  ///< a game data file (.gam) -	OSFTSAVE  =  1,  ///< a saved game (.sav) -	OSFTLOG   =  2,  ///< a transcript (log) file -	OSFTSWAP  =  3,  ///< swap file -	OSFTDATA  =  4,  ///< user data file (used with the TADS fopen() call) -	OSFTCMD   =  5,  ///< QA command/log file -	OSFTERRS  =  6,  ///< error message file -	OSFTTEXT  =  7,  ///< text file - used for source files -	OSFTBIN   =  8,  ///< binary file of unknown type - resources, etc -	OSFTCMAP  =  9,  ///< character mapping file -	OSFTPREF  = 10,  ///< preferences file -	OSFTUNK   = 11,  ///< unknown - as a filter, matches any file type -	OSFTT3IMG = 12,  ///< T3 image file (.t3 - formerly .t3x) -	OSFTT3OBJ = 13,  ///< T3 object file (.t3o) -	OSFTT3SYM = 14,  ///< T3 symbol export file (.t3s) -	OSFTT3SAV = 15   ///< T3 saved state file (.t3v) -}; - -/** - * Constants for os_getc() when returning commands.  When used for command line - * editing, special keys (arrows, END, etc.)  should cause os_getc() to return 0, - * and return the appropriate CMD_ value on the NEXT call.  Hence, os_getc() must - * keep the appropriate information around statically for the next call when a - * command key is issued. - * - * The comments indicate which CMD_xxx codes are "translated" codes and which are - * "raw"; the difference is that, when a particular keystroke could be interpreted - * as two different CMD_xxx codes, one translated and the other raw, os_getc() - * should always return the translated version of the key, and os_getc_raw() - * should return the raw version. - */ -enum KeyCmd { -	CMD_UP = 1,		    ///< move up/up arrow (translated) -	CMD_DOWN = 2,		///< move down/down arrow (translated) -	CMD_RIGHT = 3,		///< move right/right arrow (translated) -	CMD_LEFT = 4,		///< move left/left arrow (translated) -	CMD_END = 5,		///< move cursor to end of line (translated) -	CMD_HOME = 6,		///< move cursor to start of line (translated) -	CMD_DEOL = 7,		///< delete to end of line (translated) -	CMD_KILL = 8,		///< delete entire line (translated) -	CMD_DEL = 9,		///< delete current character (translated) -	CMD_SCR = 10,		///< toggle scrollback mode (translated) -	CMD_PGUP = 11,		///< page up (translated) -	CMD_PGDN = 12,		///< page down (translated) -	CMD_TOP = 13,		///< top of file (translated) -	CMD_BOT = 14,		///< bottom of file (translated) -	CMD_F1 = 15,		///< function key F1 (raw) -	CMD_F2 = 16,		///< function key F2 (raw) -	CMD_F3 = 17,		///< function key F3 (raw) -	CMD_F4 = 18,		///< function key F4 (raw) -	CMD_F5 = 19,		///< function key F5 (raw) -	CMD_F6 = 20,		///< function key F6 (raw) -	CMD_F7 = 21,		///< function key F7 (raw) -	CMD_F8 = 22,		///< function key F8 (raw) -	CMD_F9 = 23,		///< function key F9 (raw) -	CMD_F10 = 24,		///< function key F10 (raw) -	CMD_CHOME = 25,		///< control-home (raw) -	CMD_TAB = 26,		///< tab (translated) -	CMD_SF2 = 27,		///< shift-F2 (raw) -						///< not used (obsolete) - 28 -	CMD_WORD_LEFT = 29,	///< word left (ctrl-left on dos) (translated) -	CMD_WORD_RIGHT = 30,///< word right (ctrl-right on dos) (translated) -	CMD_WORDKILL = 31,	///< delete word right (translated) -	CMD_EOF = 32,       ///< end-of-file (raw) -	CMD_BREAK = 33,     ///< break (Ctrl-C or local equivalent) (translated) -	CMD_INS = 34,       ///< insert key (raw) - -					/** -					 * ALT-keys - add alphabetical code to CMD_ALT: ALT-A == CMD_ALT + 0, -					 * ALT-B == CMD_ALT + 1, ALT-C == CMD_ALT + 2, etc -					 * -					 * These keys are all raw (untranslated). -					 */ -	CMD_ALT = 128       ///< start of ALT keys -}; - -/** - * Status mode codes - */ -enum StatusMode { -	OSS_STATUS_MODE_STORY = 0, -	OSS_STATUS_MODE_STATUS = 1 -}; - -} // End of namespace TADS2 -} // End of namespace TADS -} // End of namespace Glk - -#endif diff --git a/engines/glk/tads/tads2/vocabulary.cpp b/engines/glk/tads/tads2/vocabulary.cpp index 4647b1bd30..e20bbe3de8 100644 --- a/engines/glk/tads/tads2/vocabulary.cpp +++ b/engines/glk/tads/tads2/vocabulary.cpp @@ -20,6 +20,7 @@   *   */ +#include "glk/tads/tads2/run.h"  #include "glk/tads/tads2/vocabulary.h"  namespace Glk { diff --git a/engines/glk/tads/tads2/vocabulary.h b/engines/glk/tads/tads2/vocabulary.h index b4079932a5..5574675aa2 100644 --- a/engines/glk/tads/tads2/vocabulary.h +++ b/engines/glk/tads/tads2/vocabulary.h @@ -24,19 +24,774 @@   * Defines TADS vocabulary (player command parser) functionality   */ -#ifndef GLK_TADS_TADS2_OS -#define GLK_TADS_TADS2_OS +#ifndef GLK_TADS_TADS2_VOCABULARY +#define GLK_TADS_TADS2_VOCABULARY -#include "glk/tads/tads2/types.h" +#include "glk/tads/tads2/lib.h" +#include "glk/tads/tads2/object.h" +#include "glk/tads/tads2/property.h" +#include "glk/tads/tads2/run.h"  namespace Glk {  namespace TADS {  namespace TADS2 { -// TODO: Rest of vocabulary stuff +/* + *   Cover macro for parser errors.  Any parser error should be covered + *   with this macro for documentation and search purposes.  (The macro + *   doesn't do anything - this is just something to search for when we're + *   trying to enumerate parser error codes.)  + */ +#define VOCERR(errcode) errcode + +/* maximum number of objects matching an ambiguous word */ +#define VOCMAXAMBIG 200 + +/* size of input buffer */ +#define VOCBUFSIZ 128 + +/* + *   Vocabulary relation structure - this structure relates a vocabulary + *   word to an object and part of speech.  A list of these structures is + *   attached to each vocabulary word structure to provide the word's + *   meanings. + */ +struct vocwdef { +    uint   vocwnxt;      /* index of next vocwdef attached to the same word */ +    objnum vocwobj;                      /* object associated with the word */ +    uchar  vocwtyp;   /* property associated with the word (part of speech) */ +    uchar  vocwflg;                                   /* flags for the word */ +#define VOCFCLASS  1                          /* word is for a class object */ +#define VOCFINH    2                 /* word is inherited from a superclass */ +#define VOCFNEW    4                          /* word was added at run-time */ +#define VOCFDEL    8                               /* word has been deleted */ +}; + +/* vocabulary word structure */ +struct vocdef { +    vocdef *vocnxt;                         /* next word at same hash value */ +    uchar   voclen;                                   /* length of the word */ +    uchar   vocln2;          /* length of second word (0 if no second word) */ +    uint    vocwlst;      /* head of list of vocwdef's attached to the word */ +    uchar   voctxt[1];                                  /* text of the word */ +}; + +/* vocabulary inheritance cell */ +struct vocidef { +    uchar            vocinsc;   /* # of superclasses (gives size of record) */ +    union { +        struct { +            uchar    vociusflg;                          /* flags for entry */ +#define VOCIFCLASS  1  /* entry refers to a class object (loc records only) */ +#define VOCIFVOC    2                 /* entry has vocabulary words defined */ +#define VOCIFXLAT   4  /* superclasses must be translated from portable fmt */ +#define VOCIFLOCNIL 8                  /* location is explicitly set to nil */ +#define VOCIFNEW    16       /* object was allocated at run-time with "new" */ +            objnum   vociusloc;                   /* location of the object */ +            objnum   vociusilc;                       /* inherited location */ +            objnum   vociussc[1];                  /* array of superclasses */ +        } vocius; +        vocidef     *vociunxt; +    } vociu; +#define   vociflg vociu.vocius.vociusflg +#define   vociloc vociu.vocius.vociusloc +#define   vociilc vociu.vocius.vociusilc +#define   vocisc  vociu.vocius.vociussc +#define   vocinxt vociu.vociunxt +}; + +/* size of a page in a vocabulary pool */ +#define VOCPGSIZ  8192 + +/* number of bytes in an inheritance cell page */ +#define VOCISIZ 8192 + +/* maximum number of inheritance pages */ +#define VOCIPGMAX 32 + +/* maximum number of inheritance pages (256 objects per page) */ +#define VOCINHMAX 128 + +/* size of vocabulary hash table */ +#define VOCHASHSIZ  256 + +/* size of a template structure */  #define VOCTPLSIZ 10 + +/* new-style template structure */  #define VOCTPL2SIZ  16 + +/* + *   vocwdef's are fixed in size.  They're allocated in a set of arrays + *   (the voccxwp member of the voc context has the list of arrays).  Each + *   array is of a fixed number of vocwdef entries; a maximum number of + *   vocwdef arrays is possible.  + */ +#define VOCWPGSIZ   2000                   /* number of vocwdef's per array */ +#define VOCWPGMAX   16                  /* maximum number of vocwdef arrays */ + +/* + *   To find a vocwdef entry given its index, divide the index by the + *   number of entries per array to find the array number, and use the + *   remainder to find the index within that array.  + */ +/*#define VOCW_IN_CACHE*/ +#ifdef VOCW_IN_CACHE +vocwdef *vocwget(struct voccxdef *ctx, uint idx); +#else +#define vocwget(ctx, idx) \ +    ((idx) == VOCCXW_NONE ? (vocwdef *)0 : \ +      ((ctx)->voccxwp[(idx)/VOCWPGSIZ] + ((idx) % VOCWPGSIZ))) +#endif + +/* + *   Special values for vocdtim - these values indicate that the daemon + *   does not have a normal turn-based expiration time.  + */ +#define VOCDTIM_EACH_TURN  0xffff            /* the daemon fires every turn */ + +/* daemon/fuse/alarm slot */ +struct vocddef { +    objnum   vocdfn;             /* object number of function to be invoked */ +    runsdef  vocdarg;                  /* argument for daemon/fuse function */ +    prpnum   vocdprp;             /* property number (used only for alarms) */ +    uint     vocdtim;  /* time for fuses/alarms (0xffff -> each-turn alarm) */ +}; + +/* vocabulary object list entry */ +struct vocoldef { +    objnum  vocolobj;                           /* object matching the word */ +    char   *vocolfst;         /* first word in cmd[] that identified object */ +    char   *vocollst;          /* last word in cmd[] that identified object */ +    char   *vocolhlst;      /* hypothetical last word, if we trimmed a prep */ +    int     vocolflg;                           /* special flags (ALL, etc) */ +}; + +/* vocabulary context */ +struct voccxdef { +    errcxdef  *voccxerr;                          /* error handling context */ +    tiocxdef  *voccxtio;                                /* text i/o context */ +    runcxdef  *voccxrun;                               /* execution context */ +    mcmcxdef  *voccxmem;                          /* memory manager context */ +    objucxdef *voccxundo;                                   /* undo context */ +    uchar     *voccxpool;                  /* next free byte in vocdef pool */ +    vocdef    *voccxfre;                        /* head of vocdef free list */ +    char      *voccxcpp;                   /* pointer to compound word area */ +    int        voccxcpl;                    /* length of compound word area */ +    char      *voccxspp;                    /* pointer to special word area */ +    int        voccxspl;                     /* length of special word area */ +    uint       voccxrem;        /* number of bytes remaining in vocdef pool */ +    vocidef  **voccxinh[VOCINHMAX];     /* vocidef page table: 256 per page */ +    uchar     *voccxip[VOCIPGMAX];                 /* inheritance cell pool */ +    vocidef   *voccxifr;              /* head of inheritance cell free list */ +    uint       voccxiplst;          /* last inheritance cell page allocated */ +    uint       voccxilst;      /* next unused byte in last inheritance page */ +    int        voccxredo;                   /* flag: redo command in buffer */ + +    /*  +     *   redo buffer - if voccxredo is set, and this buffer is not empty, +     *   we'll redo the command in this buffer rather than the one in our +     *   internal stack buffer  +     */ +    char       voccxredobuf[VOCBUFSIZ]; + +    /* +     *   "again" buffer - when we save the last command for repeating via +     *   the "again" command, we'll save the direct and indirect object +     *   words here, so that they can be recovered if "again" is used  +     */ +    char       voccxagainbuf[VOCBUFSIZ]; + +    vocdef    *voccxhsh[VOCHASHSIZ];                          /* hash table */ + +#ifdef VOCW_IN_CACHE +    mcmon      voccxwp[VOCWPGMAX];        /* list of pages of vocab records */ +    mcmon      voccxwplck;                  /* locked page of vocab records */ +    vocwdef   *voccxwpgptr;             /* pointer to currently locked page */ +#else +    vocwdef   *voccxwp[VOCWPGMAX];                  /* vocabulary word pool */ +#endif + +    uint       voccxwalocnt;             /* number of vocwdef's used so far */ +    uint       voccxwfre;            /* index of first vocwdef in free list */ +#define VOCCXW_NONE  ((uint)(-1))     /* index value indicating end of list */ + +    vocddef   *voccxdmn;                           /* array of daemon slots */ +    uint       voccxdmc;                 /* number of slots in daemon array */ +    vocddef   *voccxfus;                             /* array of fuse slots */ +    uint       voccxfuc;                   /* number of slots in fuse array */ +    vocddef   *voccxalm;                            /* array of alarm slots */ +    uint       voccxalc;                  /* number of slots in alarm array */ +    char       voccxtim[26];            /* game's timestamp (asctime value) */ +     +    objnum     voccxvtk;                /* object number of "take" deepverb */ +    objnum     voccxme;                      /* object number of "Me" actor */ +    objnum     voccxme_init;                     /* initial setting of "Me" */ +    objnum     voccxstr;                       /* object number of "strObj" */ +    objnum     voccxnum;                       /* object number of "numObj" */ +    objnum     voccxit;                                  /* last "it" value */ +    objnum     voccxhim;                                /* last "him" value */ +    objnum     voccxher;                                /* last "her" value */ +    objnum     voccxthc;                   /* count of items in "them" list */ +    objnum     voccxthm[VOCMAXAMBIG];            /* list of items in "them" */ +    objnum     voccxprd;                 /* "pardon" function object number */ +    objnum     voccxpre;               /* "preparse" function object number */ +    objnum     voccxppc;            /* "preparseCmd" function object number */ +    objnum     voccxpre2;           /* "preparseExt" function object number */ +    objnum     voccxvag;                             /* "again" verb object */ +    objnum     voccxini;                                 /* "init" function */ +    objnum     voccxper;             /* "parseError" function object number */ +    objnum     voccxprom;             /* "cmdPrompt" function object number */ +    objnum     voccxpostprom;     /* "cmdPostPrompt" function object number */ +    objnum     voccxpdis;                         /* parseDisambig function */ +    objnum     voccxper2;                           /* parseError2 function */ +    objnum     voccxperp;                       /* parseErrorParam function */ +    objnum     voccxpdef;                          /* parseDefault function */ +    objnum     voccxpdef2;                      /* parseDefaultExt function */ +    objnum     voccxpask;                           /* parseAskobj function */ +    objnum     voccxpask2;                     /* parseAskobjActor function */ +    objnum     voccxpask3;                  /* parseAskobjIndirect function */ +    objnum     voccxinitrestore;    /* "initRestore" function object number */ +    objnum     voccxpuv;         /* parseUnknownVerb function object number */ +    objnum     voccxpnp;          /* parseNounPhrase function object number */ +    objnum     voccxpostact;           /* postAction function object number */ +    objnum     voccxprecmd;            /* preCommand function object number */ +    objnum     voccxendcmd;            /* endCommand function object number */ + +    /* current command word list values */ +    vocoldef  *voccxdobj;                /* current direct object word list */ +    vocoldef  *voccxiobj;              /* current indirect object word list */ + +    /* current command objects */ +    objnum     voccxactor;                                 /* current actor */ +    objnum     voccxverb;                       /* current command deepverb */ +    objnum     voccxprep;                    /* current command preposition */ +     +    /* previous command values - used by "again" */ +    objnum     voccxlsa;                                  /* previous actor */ +    objnum     voccxlsv;                                   /* previous verb */ +    vocoldef   voccxlsd;                          /* previous direct object */ +    vocoldef   voccxlsi;                        /* previous indirect object */ +    objnum     voccxlsp;                                     /* preposition */ +    int        voccxlssty;              /* style (new/old) of last template */ +    uchar      voccxlst[VOCTPL2SIZ];                            /* template */ + +    objnum     voccxpreinit;                            /* preinit function */ + +    /* special flags */ +    uchar      voccxflg; +#define VOCCXFCLEAR    1      /* ignore remainder of command line (restore) */ +#define VOCCXFVWARN    2                /* generate redundant verb warnings */ +#define VOCCXFDBG      4           /* debug mode:  show parsing information */ +#define VOCCXAGAINDEL  8             /* "again" lost due to object deletion */ + +    /* number of remaining unresolved unknown words in the command */ +    int        voccxunknown; + +    /* total number of unresolved words in the last command */ +    int        voccxlastunk; + +    /* parser stack area */ +    uchar *voc_stk_ptr; +    uchar *voc_stk_cur; +    uchar *voc_stk_end; +}; + +/* allocate and push a list, returning a pointer to the list's memory */ +uchar *voc_push_list_siz(voccxdef *ctx, uint lstsiz); + +/* push a list of objects from a vocoldef array */ +void voc_push_vocoldef_list(voccxdef *ctx, vocoldef *objlist, int cnt); + +/* push a list of objects from an objnum array */ +void voc_push_objlist(voccxdef *ctx, objnum objlist[], int cnt); + +/* change the player character ("Me") object */ +void voc_set_me(voccxdef *ctx, objnum new_me); + +/* add a vocabulary word */ +void vocadd(voccxdef *ctx, prpnum p, objnum objn, +            int classflag, char *wrdval); + +/* internal addword - must already be split into two words and lengths */ +void vocadd2(voccxdef *ctx, prpnum p, objnum objn, int classflg, +             uchar *wrd1, int len1, uchar *wrd2, int len2); + +/* delete vocabulary for a given object */ +void vocdel(voccxdef *ctx, objnum objn); + +/* lower-level vocabulary deletion routine */ +void vocdel1(voccxdef *ctx, objnum objn, char *wrd, prpnum prp, +             int really_delete, int revert, int keep_undo); + +/* delete all inherited vocabulary */ +void vocdelinh(voccxdef *ctx); + +/* allocate space for an inheritance record if needed */ +void vocialo(voccxdef *ctx, objnum obj); + +/* add an inheritance/location record */ +void vociadd(voccxdef *ctx, objnum obj, objnum loc, +             int numsc, objnum *sc, int flags); + +/* delete inheritance records for an object */ +void vocidel(voccxdef *ctx, objnum chi); + +/* renumber an object's inheritance records - used for 'modify' */ +void vociren(voccxdef *ctx, objnum oldnum, objnum newnum); + +/* caller-provided context structure for vocffw/vocfnw searches */ +struct vocseadef { +    vocdef  *v; +    vocwdef *vw; +    uchar   *wrd1; +    int      len1; +    uchar   *wrd2; +    int      len2; +}; + +/* find first word matching a given word */ +vocwdef *vocffw(voccxdef *ctx, char *wrd, int len, char *wrd2, int len2, +                int p, vocseadef *search_ctx); + +/* find next word */ +vocwdef *vocfnw(voccxdef *voccx, vocseadef *search_ctx); + +/* read a line of input text */ +int vocread(voccxdef *ctx, objnum actor, objnum verb, +            char *buf, int bufl, int type); +#define VOCREAD_OK    0 +#define VOCREAD_REDO  1 + +/* compute size of a vocoldef list */ +int voclistlen(vocoldef *lst); + +/* tokenize an input buffer */ +int voctok(voccxdef *ctx, char *cmd, char *outbuf, +           char **wrd, int lower, int cvt_ones, int show_errors); + +/* get types for a word list */ +int vocgtyp(voccxdef *ctx, char **cmd, int *types, char *orgbuf); + +/* execute a player command */ +int voccmd(voccxdef *ctx, char *cmd, uint cmdlen); + +/* disambiguator */ +int vocdisambig(voccxdef *ctx, vocoldef *outlist, vocoldef *inlist, +                prpnum defprop, prpnum accprop, prpnum verprop, +                char *cmd[], objnum otherobj, objnum cmdActor, +                objnum cmdVerb, objnum cmdPrep, char *cmdbuf, +                int silent); + +/* display a multiple-object prefix */ +void voc_multi_prefix(voccxdef *ctx, objnum objn, +                      int show_prefix, int multi_flags, +                      int cur_index, int count); + +/* low-level executor */ +int execmd(voccxdef *ctx, objnum actor, objnum prep, +           char *vverb, char *vprep, vocoldef *dolist, vocoldef *iolist, +           char **cmd, int *typelist, +           char *cmdbuf, int wrdcnt, uchar **preparse_list, int *next_start); + +/* recursive command execution */ +int execmd_recurs(voccxdef *ctx, objnum actor, objnum verb, +                  objnum dobj, objnum prep, objnum iobj, +                  int validate_dobj, int validate_iobj); + +/* try running preparseCmd user function */ +int try_preparse_cmd(voccxdef *ctx, char **cmd, int wrdcnt, +                     uchar **preparse_list); + +/* + *   Handle an unknown verb or sentence structure.  We'll call this when + *   we encounter a sentence where we don't know the verb word, or we + *   don't know the combination of verb and verb preposition, or we don't + *   recognize the sentence structure (for example, an indirect object is + *   present, but we don't have a template defined using an indirect + *   object for the verb). + *    + *   'wrdcnt' is the number of words in the cmd[] array.  If wrdcnt is + *   zero, we'll automatically count the array entries, with the end of + *   the array indicated by a null pointer entry. + *    + *   If do_fuses is true, we'll execute the fuses and daemons if the + *   function exists and doesn't throw an ABORT error, or any other + *   run-time error other than EXIT. + *    + *   This function calls the game-defined function parseUnknownVerb, if it + *   exists.  If the function doesn't exist, we'll simply display the + *   given error message, using the normal parseError mechanism.  The + *   function should use "abort" or "exit" if it wants to cancel further + *   processing of the command. + *    + *   We'll return true if the function exists and executes successfully, + *   in which case normal processing should continue with any remaining + *   command on the command line.  We'll return false if the function + *   doesn't exist or throws an error other than EXIT, in which case the + *   remainder of the command should be aborted.   + */ +int try_unknown_verb(voccxdef *ctx, objnum actor, +                     char **cmd, int *typelist, int wrdcnt, int *next_start, +                     int do_fuses, int err, char *msg, ...); + +/* find a template */ +int voctplfnd(voccxdef *ctx, objnum verb_in, objnum prep, +              uchar *tplout, int *newstyle); + +/* build a printable name for an object from the words in a command list */ +void voc_make_obj_name(voccxdef *ctx, char *namebuf, char *cmd[], +                       int firstwrd, int lastwrd); +void voc_make_obj_name_from_list(voccxdef *ctx, char *namebuf, +                                 char *cmd[], char *firstwrd, char *lastwrd); + +/* + *   check noun - determines whether the next set of words is a valid noun + *   phrase.  No complaint is issued if not; this check is generally made + *   to figure out what type of sentence we're dealing with.  This is + *   simple; we just call vocgobj() with the complaint flag turned off. + */ +/* int vocchknoun(voccxdef *ctx, char **cmd, int *typelist, int cur, +                  int *next, vocoldef *nounlist, int chkact); */ +#define vocchknoun(ctx, cmd, typelist, cur, next, nounlist, chkact) \ + vocgobj(ctx, cmd, typelist, cur, next, FALSE, nounlist, TRUE, chkact, 0) +#define vocchknoun2(ctx, cmd, typlst, cur, next, nounlist, chkact, nomatch) \ + vocgobj(ctx, cmd, typlst, cur, next, FALSE, nounlist, TRUE, chkact, nomatch) + +/* + *   get noun - reads an object list.  We simply call vocgobj() with the + *   complaint and multiple-noun flags turned on. + */ +/* int vocgetnoun(voccxdef *ctx, char **cmd, int *typelist, int cur, +                  int *next, vocoldef *nounlist); */ +#define vocgetnoun(ctx, cmd, typelist, cur, next, nounlist) \ + vocgobj(ctx, cmd, typelist, cur, next, TRUE, nounlist, TRUE, FALSE, 0) + +/* get object */ +int vocgobj(voccxdef *ctx, char **cmd, int *typelist, int cur, +            int *next, int complain, vocoldef *nounlist, +            int multi, int chkact, int *nomatch); + +/* tokenize a string - TADS program code interface */ +void voc_parse_tok(voccxdef *ctx); + +/* get token types - TADS program code interface */ +void voc_parse_types(voccxdef *ctx); + +/* get objects matching all of the given words - TADS program code interface */ +void voc_parse_dict_lookup(voccxdef *ctx); + +/* parse a noun list - TADS program code interface */ +void voc_parse_np(voccxdef *ctx); + +/* disambiguate a noun list - TADS program code interface */ +void voc_parse_disambig(voccxdef *ctx); + +/* replace the current command - TADS program code interface */ +void voc_parse_replace_cmd(voccxdef *ctx); + +/* check access to an object */ +int vocchkaccess(voccxdef *ctx, objnum obj, prpnum verprop, +                 int seqno, objnum actor, objnum verb); + +/* check to see if an object is visible */ +int vocchkvis(voccxdef *ctx, objnum obj, objnum cmdActor); + +/* display an appropriate message for an unreachable object */ +void vocnoreach(voccxdef *ctx, objnum *list1, int cnt, +                objnum actor, objnum verb, objnum prep, prpnum defprop, +                int show_multi_prefix, int multi_flags, +                int multi_base_index, int multi_total_count); + +/* set {numObj | strObj}.value, as appropriate */ +void vocsetobj(voccxdef *ctx, objnum obj, dattyp typ, void *val, +               vocoldef *inobj, vocoldef *outobj); + +/* macros to read values out of templates */ +#define voctplpr(tpl) ((objnum)osrp2(((uchar *)tpl)))        /* preposition */ +#define voctplvi(tpl) ((prpnum)osrp2(((uchar *)tpl) + 2))      /* verIoVerb */ +#define voctplio(tpl) ((prpnum)osrp2(((uchar *)tpl) + 4))         /* ioVerb */ +#define voctplvd(tpl) ((prpnum)osrp2(((uchar *)tpl) + 6))      /* verDoVerb */ +#define voctpldo(tpl) ((prpnum)osrp2(((uchar *)tpl) + 8))         /* doVerb */ +#define voctplflg(tpl) (*(((uchar *)tpl) + 10))                    /* flags */ + +/* flag values for the voctplflg */ +#define VOCTPLFLG_DOBJ_FIRST   0x01     /* disambiguate direct object first */ + + +/* word type flags */ +#define VOCT_ARTICLE  1 +#define VOCT_ADJ      2 +#define VOCT_NOUN     4 +#define VOCT_PREP     8 +#define VOCT_VERB     16 +#define VOCT_SPEC     32            /* special words - "of", ",", ".", etc. */ +#define VOCT_PLURAL   64 +#define VOCT_UNKNOWN  128                                /* word is unknown */ + +/* special type flags */ +#define VOCS_ALL     1                                             /* "all" */ +#define VOCS_EXCEPT  2                                          /* "except" */ +#define VOCS_IT      4                                              /* "it" */ +#define VOCS_THEM    8                                            /* "them" */ +#define VOCS_NUM     16                                         /* a number */ +#define VOCS_COUNT   32                   /* a number being used as a count */ +#define VOCS_PLURAL  64                                           /* plural */ +#define VOCS_ANY     128                                           /* "any" */ +#define VOCS_HIM     256                                           /* "him" */ +#define VOCS_HER     512                                           /* "her" */ +#define VOCS_STR     1024                                /* a quoted string */ +#define VOCS_UNKNOWN 2048           /* noun phrase contains an unknown word */ +#define VOCS_ENDADJ  4096        /* word matched adjective at end of phrase */ +#define VOCS_TRUNC   8192    /* truncated match - word is leading substring */ +#define VOCS_TRIMPREP 16384 /* trimmed prep phrase: assumed it was for verb */ + +/* special internally-defined one-character word flags */ +#define VOCW_AND     ',' +#define VOCW_THEN    '.' +#define VOCW_OF      'O' +#define VOCW_ALL     'A' +#define VOCW_BOTH    'B' +#define VOCW_IT      'I' +#define VOCW_HIM     'M' +#define VOCW_ONE     'N' +#define VOCW_ONES    'P' +#define VOCW_HER     'R' +#define VOCW_THEM    'T' +#define VOCW_BUT     'X' +#define VOCW_ANY     'Y' + +/* structure for special internal word table */ +struct vocspdef { +    char *vocspin; +    char  vocspout; +}; + +/* check if a word is a special word - true if word is given special word */ +/* int vocspec(char *wordptr, int speccode); */ +#define vocspec(w, s) (*(w) == (s)) + +/*  + *   Set a fuse/daemon/notifier. + */ +void vocsetfd(voccxdef *ctx, vocddef *what, objnum func, prpnum prop, +              uint tm, runsdef *val, int err); + +/* remove a fuse/daemon/notifier */ +void vocremfd(voccxdef *ctx, vocddef *what, objnum func, prpnum prop, +              runsdef *val, int err); + +/* count a turn (down all fuse/notifier timers) */ +void vocturn(voccxdef *ctx, int turncnt, int do_fuses); + +/* initialize voc context */ +void vocini(voccxdef *vocctx, errcxdef *errctx, mcmcxdef *memctx, +            runcxdef *runctx, objucxdef *undoctx, int fuses, +            int daemons, int notifiers); + +/* clean up the voc context - frees memory allocated by vocini() */ +void vocterm(voccxdef *vocctx); + +/* allocate/free fuse/daemon/notifier array for voc ctx initialization */ +void vocinialo(voccxdef *ctx, vocddef **what, int cnt); +void voctermfree(vocddef *what); + +/* get a vocidef given an object number */ +/* vocidef *vocinh(voccxdef *ctx, objnum obj); */ +#define vocinh(ctx, obj) ((ctx)->voccxinh[(obj) >> 8][(obj) & 255]) + +/* revert all objects back to original state, using inheritance records */ +void vocrevert(voccxdef *ctx); + +/* clear all fuses/daemons/notifiers (useful for restarting) */ +void vocdmnclr(voccxdef *ctx); + +/* display a parser error message */ +void vocerr(voccxdef *ctx, int err, char *f, ...); + +/*  + *   display a parser informational error message - this will display the + *   message whether or not we're suppressing messages due to unknown + *   words, and should be used when providing information, such as objects + *   we're assuming by default  + */ +void vocerr_info(voccxdef *ctx, int err, char *f, ...); + +/* client undo callback - undoes a daemon/fuse/notifier */ +void vocdundo(void *ctx, uchar *data); + +/* client undo size figuring callback - return size of client undo record */ +ushort OS_LOADDS vocdusz(void *ctx, uchar *data); + +/* save undo for object creation */ +void vocdusave_newobj(voccxdef *ctx, objnum objn); + +/* save undo for adding a word */ +void vocdusave_addwrd(voccxdef *ctx, objnum objn, prpnum typ, int flags, +                      char *wrd); + +/* save undo for deleting a word */ +void vocdusave_delwrd(voccxdef *ctx, objnum objn, prpnum typ, int flags, +                      char *wrd); + +/* save undo for object deletion */ +void vocdusave_delobj(voccxdef *ctx, objnum objn); + +/* save undo for changing the "Me" object */ +void vocdusave_me(voccxdef *ctx, objnum old_me); + +/* compute vocabulary word hash value */ +uint vochsh(uchar *t, int len); + +/* TADS versions of isalpha, isspace, isdigit, etc */ +#define vocisupper(c) ((uchar)(c) <= 127 && isupper((uchar)(c))) +#define vocislower(c) ((uchar)(c) <= 127 && islower((uchar)(c))) +#define vocisalpha(c) ((uchar)(c) > 127 || isalpha((uchar)(c))) +#define vocisspace(c) ((uchar)(c) <= 127 && isspace((uchar)(c))) +#define vocisdigit(c) ((uchar)(c) <= 127 && isdigit((uchar)(c))) + + +/* + *   Undo types for voc subsystem  + */ +#define VOC_UNDO_DAEMON   1                    /* fuse/daemon status change */ +#define VOC_UNDO_NEWOBJ   2                              /* object creation */ +#define VOC_UNDO_DELOBJ   3                              /* object deletion */ +#define VOC_UNDO_ADDVOC   4                  /* add vocabulary to an object */ +#define VOC_UNDO_DELVOC   5             /* delete vocabulary from an object */ +#define VOC_UNDO_SETME    6                          /* set the "Me" object */ + + +/* + *   Our own stack.  We need to allocate some fairly large structures + *   (for the disambiguation lists, mostly) in a stack-like fashion, and + *   we don't want to consume vast quantities of the real stack, because + *   some machines have relatively restrictive limitations on stack usage. + *   To provide some elbow room, we'll use a stack-like structure of our + *   own: we'll allocate out of this structure as needed, and whenever we + *   leave a C stack frame, we'll also leave our own stack frame.  + */ + +/* re-initialize the stack, allocating space for it if needed */ +void  voc_stk_ini(voccxdef *ctx, uint siz); + +/* enter a stack frame, marking our current position */ +#define voc_enter(ctx, marker)  (*(marker) = (ctx)->voc_stk_cur) + +/* leave a stack frame, restoring the entry position */ +#define voc_leave(ctx, marker)  ((ctx)->voc_stk_cur = marker) + +/* return a value */ +#define VOC_RETVAL(ctx, marker, retval) \ +       voc_leave(ctx, marker); return retval + +/* allocate space from the stack */ +void *voc_stk_alo(voccxdef *ctx, uint siz); + +/* allocation cover macros */ +#define VOC_STK_ARRAY(ctx, typ, var, cnt) \ +    (var = (typ *)voc_stk_alo(ctx, (uint)((cnt) * sizeof(typ)))) + +#define VOC_MAX_ARRAY(ctx, typ, var) \ +    VOC_STK_ARRAY(ctx, typ, var, VOCMAXAMBIG) + +/* + *   Stack size for the vocab stack.  We'll scale our stack needs based + *   on the size of the vocoldef structure, since this is the most common + *   item to be allocated on the vocab stack.  We'll also scale based on + *   the defined VOCMAXAMBIG parameter, since it is the number of elements + *   usually allocated.  The actual amount of space needed depends on how + *   the functions in vocab.c and execmd.c work, so this parameter may + *   need to be adjusted for changes to the player command parser.  + */ +#define VOC_STACK_SIZE  (16 * VOCMAXAMBIG * sizeof(vocoldef)) + +/* + *   Execute all fuses and daemons, then execute the endCommand user + *   function.  Returns zero on success, or ERR_ABORT if 'abort' was + *   thrown during execution.  This is a convenient cover single function + *   to do all end-of-turn processing; this calls exefuse() and exedaem() + *   as needed, trapping any 'abort' or 'exit' errors that occur. + *    + *   If 'do_fuses' is true, we'll run fuses and daemons.  Otherwise,  + */ +int exe_fuses_and_daemons(voccxdef *ctx, int err, int do_fuses, +                          objnum actor, objnum verb, +                          vocoldef *dobj_list, int do_cnt, +                          objnum prep, objnum iobj); + +/* + *   Execute any pending fuses.  Return TRUE if any fuses were executed, + *   FALSE otherwise.   + */ +int exefuse(voccxdef *ctx, int do_run); + +/* + *   Execute daemons  + */ +void exedaem(voccxdef *ctx); + +/* + *   Get the number and size of words defined for an object.  The size + *   returns the total byte count from all the words involved.  Do not + *   include deleted words in the count.   + */ +void voc_count(voccxdef *ctx, objnum objn, prpnum prp, int *cnt, int *siz); + +/* + *   Iterate through all words for a particular object, calling a + *   function with each vocwdef found.  If objn == MCMONINV, we'll call + *   the callback for every word.   + */ +void voc_iterate(voccxdef *ctx, objnum objn, +                 void (*fn)(void *, vocdef *, vocwdef *), void *fnctx); + +/* ------------------------------------------------------------------------ */ +/* + *   disambiguation status codes - used for disambigDobj and disambigIobj + *   methods in the deepverb + */ + +/* continue with disambiguation process (using possibly updated list) */ +#define VOC_DISAMBIG_CONT     1 + +/* done - the list is fully resolved; return with (possibly updated) list */ +#define VOC_DISAMBIG_DONE     2 + +/* error - abort the command */ +#define VOC_DISAMBIG_ERROR    3 + +/* parse string returned in second element of list as interactive response */ +#define VOC_DISAMBIG_PARSE_RESP  4 + +/* already asked for an interactive response, but didn't read it yet */ +#define VOC_DISAMBIG_PROMPTED 5 + + +/* ------------------------------------------------------------------------ */ +/* + *   parseNounPhrase status codes  + */ + +/* parse error occurred */ +#define VOC_PNP_ERROR    1 + +/* use built-in default parser */ +#define VOC_PNP_DEFAULT  2 + +/* successful parse */ +#define VOC_PNP_SUCCESS  3 + + +/* ------------------------------------------------------------------------ */ +/* + *   parserResolveObjects usage codes  + */ +#define VOC_PRO_RESOLVE_DOBJ  1                            /* direct object */ +#define VOC_PRO_RESOLVE_IOBJ  2                          /* indirect object */ +#define VOC_PRO_RESOLVE_ACTOR 3                                    /* actor */ +  } // End of namespace TADS2  } // End of namespace TADS  } // End of namespace Glk  | 
